Coverage for sm / settings.py: 89%

70 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-24 12:43 +0000

1import os 

2import dj_database_url 

3from pathlib import Path 

4from sys import platform, argv 

5import django.contrib.messages as messages 

6from decouple import config, Csv 

7 

8# Build paths inside the project like this: BASE_DIR / 'subdir'. 

9BASE_DIR = Path(__file__).resolve().parent.parent 

10 

11# Environment-based configuration 

12SECRET_KEY = config("SECRET_KEY", default="django-insecure-default-key-for-dev") 

13DEBUG = config("DEBUG", default=True, cast=bool) 

14ALLOWED_HOSTS = config("ALLOWED_HOSTS", default="localhost,127.0.0.1", cast=Csv()) 

15EMAIL_BACKEND = config( 

16 "EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" 

17) 

18 

19THEME_CONTACT_EMAIL = config("THEME_CONTACT_EMAIL", default="oliver@linux-kernel.at") 

20THEME_GITHUB_URL = config("THEME_GITHUB_URL", default="https://github.com/ofalk/sm") 

21 

22DISABLE_SOCIAL_AUTH = config("DISABLE_SOCIAL_AUTH", default=False, cast=bool) 

23 

24# Application definition 

25 

26INSTALLED_APPS = [ 

27 "django.contrib.admin", 

28 "django.contrib.auth", 

29 "django.contrib.contenttypes", 

30 "django.contrib.sessions", 

31 "django.contrib.messages", 

32 "django.contrib.staticfiles", 

33 "django.contrib.sites", 

34 "django.contrib.admindocs", 

35 "rest_framework", 

36 "bootstrap4", 

37 "debug_toolbar", 

38 "django_countries", 

39 "whitenoise.runserver_nostatic", 

40 "taggit", 

41 "simple_history", 

42 "drf_spectacular", 

43 # Allauth 

44 "allauth", 

45 "allauth.account", 

46 "allauth.socialaccount", 

47 "allauth.mfa", 

48 "social_django", 

49] 

50 

51if not DISABLE_SOCIAL_AUTH: 

52 INSTALLED_APPS += [ 

53 # "allauth.socialaccount.providers.facebook", 

54 "allauth.socialaccount.providers.google", 

55 ] 

56 SOCIALACCOUNT_ENABLED = True 

57else: 

58 SOCIALACCOUNT_ENABLED = False 

59 

60INSTALLED_APPS += [ 

61 # Project Apps 

62 "cluster", 

63 "operatingsystem", 

64 "clusterpackage", 

65 "patchtime", 

66 "location", 

67 "servermodel", 

68 "server", 

69 "status", 

70 "domain", 

71 "clustersoftware", 

72 "clusterpackagetype", 

73 "vendor", 

74 "sm.apps.SmConfig", 

75] 

76 

77MIDDLEWARE = [ 

78 "django.middleware.security.SecurityMiddleware", 

79 "whitenoise.middleware.WhiteNoiseMiddleware", 

80 "django.contrib.sessions.middleware.SessionMiddleware", 

81 "django.middleware.common.CommonMiddleware", 

82 "django.middleware.csrf.CsrfViewMiddleware", 

83 "django.contrib.auth.middleware.AuthenticationMiddleware", 

84 "django.contrib.messages.middleware.MessageMiddleware", 

85 "django.middleware.clickjacking.XFrameOptionsMiddleware", 

86 "debug_toolbar.middleware.DebugToolbarMiddleware", 

87 "htmlmin.middleware.HtmlMinifyMiddleware", 

88 "htmlmin.middleware.MarkRequestMiddleware", 

89 "django.contrib.sites.middleware.CurrentSiteMiddleware", 

90 "simple_history.middleware.HistoryRequestMiddleware", 

91 # Allauth middleware 

92 "allauth.account.middleware.AccountMiddleware", 

93] 

94 

95ROOT_URLCONF = "sm.urls" 

96 

97TEMPLATES = [ 

98 { 

99 "BACKEND": "django.template.backends.django.DjangoTemplates", 

100 "DIRS": [BASE_DIR / "sm" / "templates"], 

101 "OPTIONS": { 

102 "context_processors": [ 

103 "django.template.context_processors.debug", 

104 "django.template.context_processors.request", 

105 "django.contrib.auth.context_processors.auth", 

106 "django.contrib.messages.context_processors.messages", 

107 "django.template.context_processors.i18n", 

108 "django.template.context_processors.media", 

109 "django.template.context_processors.static", 

110 "django.template.context_processors.tz", 

111 "sm.context_processors.theme_settings", 

112 ], 

113 "loaders": [ 

114 "django.template.loaders.filesystem.Loader", 

115 "sm.template.loaders.app_directories_enhanced.Loader", 

116 "django.template.loaders.app_directories.Loader", 

117 ], 

118 }, 

119 }, 

120] 

121 

122WSGI_APPLICATION = "sm.wsgi.application" 

123 

124# Database 

125# https://docs.djangoproject.com/en/stable/ref/settings/#databases 

126 

127DATABASE_URL = config("DATABASE_URL", default=None) 

128 

129if DATABASE_URL: 

130 DATABASES = { 

131 "default": dj_database_url.config(default=DATABASE_URL, conn_max_age=600) 

132 } 

133elif "test" in argv: 

134 DATABASES = { 

135 "default": { 

136 "ENGINE": "django.db.backends.sqlite3", 

137 "NAME": ":memory:", 

138 } 

139 } 

140elif platform == "darwin": 

141 DATABASES = { 

142 "default": { 

143 "ENGINE": "django.db.backends.sqlite3", 

144 "NAME": BASE_DIR / "db.sqlite3", 

145 } 

146 } 

147else: 

148 # Use config_local or environment variables for MySQL if needed 

149 DATABASES = { 

150 "default": { 

151 "ENGINE": "django.db.backends.sqlite3", 

152 "NAME": BASE_DIR / "db.sqlite3", 

153 } 

154 } 

155 

156# Password validation 

157# https://docs.djangoproject.com/en/stable/ref/settings/#auth-password-validators 

158 

159AUTH_PASSWORD_VALIDATORS = [ 

160 { 

161 "NAME": ( 

162 "django.contrib.auth.password_validation." 

163 "UserAttributeSimilarityValidator" 

164 ) 

165 }, 

166 {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, 

167 {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, 

168 {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, 

169] 

170 

171# Internationalization 

172# https://docs.djangoproject.com/en/stable/topics/i18n/ 

173 

174LANGUAGE_CODE = "en-us" 

175TIME_ZONE = "UTC" 

176USE_I18N = True 

177USE_TZ = True 

178 

179# Static files (CSS, JavaScript, Images) 

180# https://docs.djangoproject.com/en/stable/howto/static-files/ 

181 

182STATIC_URL = "/static/" 

183STATIC_ROOT = BASE_DIR / "static" 

184 

185# Default primary key field type 

186# https://docs.djangoproject.com/en/stable/ref/settings/#default-auto-field 

187 

188DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 

189 

190# Extra settings 

191 

192SITE_ID = 1 

193 

194AUTHENTICATION_BACKENDS = [ 

195 # Needed to login by username in Django admin, regardless of `allauth` 

196 "django.contrib.auth.backends.ModelBackend", 

197 # `allauth` specific authentication methods, such as login by e-mail 

198 "allauth.account.auth_backends.AuthenticationBackend", 

199] 

200 

201# Allauth settings 

202LOGIN_REDIRECT_URL = "/" 

203ACCOUNT_LOGIN_METHODS = {"username", "email"} 

204ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"] 

205ACCOUNT_EMAIL_VERIFICATION = "optional" 

206ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "/" 

207ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = "/" 

208ACCOUNT_LOGIN_REDIRECT_URL = "/" 

209ACCOUNT_LOGOUT_REDIRECT_URL = "/" 

210 

211# Social Auth (Legacy/Transition) 

212SOCIAL_AUTH_STRATEGY = "social_django.strategy.DjangoStrategy" 

213SOCIAL_AUTH_STORAGE = "social_django.models.DjangoStorage" 

214 

215# Disable MFA features that might cause issues if allauth.mfa is not fully configured 

216MFA_PASSKEY_LOGIN_ENABLED = False 

217MFA_SUPPORTED_TYPES = ["totp", "recovery_codes"] 

218 

219if not DISABLE_SOCIAL_AUTH: 

220 SOCIALACCOUNT_AUTO_SIGNUP = True 

221 SOCIALACCOUNT_ADAPTER = "sm.adapter.MySocialAccountAdapter" 

222 

223 SOCIALACCOUNT_PROVIDERS = { 

224 # "facebook": { 

225 # "METHOD": "oauth2", 

226 # "SCOPE": ["email", "public_profile"], 

227 # "AUTH_PARAMS": {"auth_type": "reauthenticate"}, 

228 # "INIT_PARAMS": {"cookie": True}, 

229 # "FIELDS": [ 

230 # "id", 

231 # "first_name", 

232 # "last_name", 

233 # "middle_name", 

234 # "name", 

235 # "name_format", 

236 # "picture", 

237 # "short_name", 

238 # ], 

239 # "EXCHANGE_TOKEN": True, 

240 # "VERIFIED_EMAIL": False, 

241 # "VERSION": "v13.0", 

242 # }, 

243 "google": { 

244 "SCOPE": [ 

245 "profile", 

246 "email", 

247 ], 

248 "AUTH_PARAMS": { 

249 "access_type": "online", 

250 }, 

251 "AUTH_PKCE_ENABLED": True, 

252 "FETCH_USERINFO": True, 

253 } 

254 } 

255 

256INTERNAL_IPS = [ 

257 "127.0.0.1", 

258] 

259 

260MESSAGE_TAGS = {messages.ERROR: "danger"} 

261 

262MESSAGE_STORAGE = "django.contrib.messages.storage.fallback.FallbackStorage" 

263 

264TAGGIT_CASE_INSENSITIVE = True 

265 

266# REST Framework Settings 

267REST_FRAMEWORK = { 

268 "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", 

269} 

270 

271SPECTACULAR_SETTINGS = { 

272 "TITLE": "ServerManager API", 

273 "DESCRIPTION": "API for managing servers, clusters, and infrastructure.", 

274 "VERSION": "1.0.0", 

275 "SERVE_INCLUDE_SCHEMA": False, 

276} 

277 

278# Bootstrap 4 settings 

279BOOTSTRAP4 = { 

280 "error_css_class": "bootstrap4-error", 

281 "required_css_class": "bootstrap4-required", 

282 "javascript_in_head": True, 

283 "success_css_class": "bootstrap4-bound", 

284} 

285 

286HTML_MINIFY = True 

287 

288if not DISABLE_SOCIAL_AUTH and os.path.isfile(BASE_DIR / "config_local.py"): 

289 from config_local import * # noqa 

290 

291# Redefine to ensure no social_core backends survive in quick test mode 

292if DISABLE_SOCIAL_AUTH: 

293 AUTHENTICATION_BACKENDS = [ 

294 "django.contrib.auth.backends.ModelBackend", 

295 "allauth.account.auth_backends.AuthenticationBackend", 

296 ]