github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/django.go (about)

     1  package ddevapp
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/ddev/ddev/pkg/fileutil"
     6  	"github.com/ddev/ddev/pkg/nodeps"
     7  	"github.com/ddev/ddev/pkg/output"
     8  	"github.com/ddev/ddev/pkg/util"
     9  	"os"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"text/template"
    14  )
    15  
    16  const siteSettingsDdevPy = "settings.ddev.py"
    17  
    18  // isDjango4App returns true if the app is of type django4
    19  func isDjango4App(app *DdevApp) bool {
    20  	return fileutil.FileExists(filepath.Join(app.AppRoot, app.Docroot, "manage.py"))
    21  }
    22  
    23  // django4ConfigOverrideAction sets up webserverType and anything else
    24  // we might need for django.
    25  func django4ConfigOverrideAction(app *DdevApp) error {
    26  	if app.WebserverType == nodeps.WebserverDefault {
    27  		app.WebserverType = nodeps.WebserverNginxGunicorn
    28  	}
    29  	if app.Database == DatabaseDefault {
    30  		app.Database.Type = nodeps.Postgres
    31  		app.Database.Version = nodeps.Postgres14
    32  	}
    33  
    34  	return nil
    35  }
    36  
    37  // django4PostConfigAction reminds people that they may need DJANGO_SETTINGS_MODULE env var
    38  func django4PostConfigAction(_ *DdevApp) error {
    39  	util.Warning("Your project may need a DJANGO_SETTINGS_MODULE environment variable to work correctly")
    40  	return nil
    41  }
    42  
    43  // django4SettingsCreator creates the settings.ddev.py
    44  func django4SettingsCreator(app *DdevApp) (string, error) {
    45  	// Return early because we aren't expected to manage settings.
    46  	if app.DisableSettingsManagement {
    47  		return "", nil
    48  	}
    49  
    50  	err := writeDjango4SettingsDdevPy(app.GetConfigPath("settings/settings.ddev.py"), app)
    51  	if err != nil {
    52  		return "", err
    53  	}
    54  	return app.GetConfigPath("settings/settings.ddev.py"), nil
    55  }
    56  
    57  // django4PostStartAction handles creating settings for the project
    58  func django4PostStartAction(app *DdevApp) error {
    59  	// Return early because we aren't expected to manage settings.
    60  	if app.DisableSettingsManagement {
    61  		return nil
    62  	}
    63  
    64  	settingsFile, _, err := app.Exec(&ExecOpts{
    65  		Cmd: "find-django-settings-file.py",
    66  	})
    67  	if err != nil {
    68  		util.Warning("Unable to find django settings file: %v", err)
    69  	}
    70  
    71  	settingsFile = strings.Trim(settingsFile, "\n")
    72  	app.SiteSettingsPath = strings.TrimPrefix(settingsFile, "/var/www/html/")
    73  	app.SiteSettingsPath = filepath.Join(app.AppRoot, app.SiteSettingsPath)
    74  
    75  	app.SiteDdevSettingsFile = "/mnt/ddev_config/settings/settings.ddev.py"
    76  
    77  	django4SettingsIncludeStanza := fmt.Sprintf(`
    78  
    79  # #ddev-generated code to import DDEV settings
    80  import os
    81  if os.environ.get('IS_DDEV_PROJECT') == 'true':
    82      from pathlib import Path
    83      import importlib.util
    84      import sys
    85  
    86      s = Path('%s')
    87      if s.is_file():
    88          spec = importlib.util.spec_from_file_location("ddev_settings", s)
    89          ddev_settings = importlib.util.module_from_spec(spec)
    90          spec.loader.exec_module(ddev_settings)
    91  
    92          # Get the current module to set attributes
    93          current_module = sys.modules[__name__]
    94  
    95          # Add or update attributes from the ddev_settings module
    96          for name, value in vars(ddev_settings).items():
    97              if not name.startswith("__"):  # Exclude special attributes
    98                  setattr(current_module, name, value)
    99  # End DDEV-generated code
   100  `, app.SiteDdevSettingsFile)
   101  
   102  	included, err := fileutil.FgrepStringInFile(app.SiteSettingsPath, nodeps.DdevFileSignature)
   103  	if err != nil {
   104  		return err
   105  	}
   106  
   107  	if included {
   108  		output.UserOut.Printf("Existing %s includes %s", app.SiteSettingsPath, app.SiteDdevSettingsFile)
   109  	} else {
   110  		output.UserOut.Printf("Existing %s file does not include %s, modifying to include DDEV settings", app.SiteSettingsPath, app.SiteDdevSettingsFile)
   111  
   112  		// Add the inclusion
   113  		file, err := os.OpenFile(app.SiteSettingsPath, os.O_RDWR|os.O_APPEND, 0644)
   114  		if err != nil {
   115  			return err
   116  		}
   117  		defer util.CheckClose(file)
   118  
   119  		_, err = file.Write([]byte(django4SettingsIncludeStanza))
   120  		if err != nil {
   121  			return err
   122  		}
   123  
   124  		err = app.MutagenSyncFlush()
   125  		if err != nil {
   126  			return err
   127  		}
   128  	}
   129  
   130  	return err
   131  }
   132  
   133  // reloadGunicorn hits Gunicorn process with HUP, reloading
   134  func reloadGunicorn(app *DdevApp) {
   135  	stdout, stderr, err := app.Exec(&ExecOpts{
   136  		Cmd: "pkill -HUP gunicorn",
   137  	})
   138  	if err != nil {
   139  		util.Warning("Failed to reload Gunicorn configuration: stdout='%s', stderr='%s': %v", stdout, stderr, err)
   140  	}
   141  }
   142  
   143  // writeDjango4SettingsDdevPy dynamically produces valid settings.ddev.py file by combining a configuration
   144  // object with a data-driven template.
   145  func writeDjango4SettingsDdevPy(filePath string, app *DdevApp) error {
   146  	if fileutil.FileExists(filePath) {
   147  		// Check if the file is managed by ddev.
   148  		signatureFound, err := fileutil.FgrepStringInFile(filePath, nodeps.DdevFileSignature)
   149  		if err != nil {
   150  			return err
   151  		}
   152  
   153  		// If the signature wasn't found, warn the user and return.
   154  		if !signatureFound {
   155  			util.Warning("%s already exists and is managed by the user.", filepath.Base(filePath))
   156  			return nil
   157  		}
   158  	}
   159  
   160  	t, err := template.New("settings.ddev.py").ParseFS(bundledAssets, path.Join("django4", "settings.ddev.py"))
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	// Ensure target directory exists and is writable
   166  	dir := filepath.Dir(filePath)
   167  	if err = os.Chmod(dir, 0755); os.IsNotExist(err) {
   168  		if err = os.MkdirAll(dir, 0755); err != nil {
   169  			return err
   170  		}
   171  	} else if err != nil {
   172  		return err
   173  	}
   174  
   175  	file, err := os.Create(filePath)
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	settings := map[string]string{
   181  		"host":     "db",
   182  		"user":     "db",
   183  		"password": "db",
   184  		"database": "db",
   185  		"engine":   "django.db.backends.postgresql",
   186  	}
   187  	if app.Database.Type == nodeps.MySQL || app.Database.Type == nodeps.MariaDB {
   188  		settings["engine"] = "django.db.backends.mysql"
   189  	}
   190  	err = t.Execute(file, settings)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	util.CheckClose(file)
   195  	return nil
   196  }