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 }