Coverage for sm / settings.py: 83%
104 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 13:46 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-17 13:46 +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
8# Build paths inside the project like this: BASE_DIR / 'subdir'.
9BASE_DIR = Path(__file__).resolve().parent.parent
11# Environment-based configuration
12SECRET_KEY = config("SECRET_KEY", default="django-insecure-default-key-for-dev")
13DEBUG = config("DEBUG", default=False, 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)
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")
22# Version information
23APP_VERSION = config("APP_VERSION", default="unknown")
24APP_MODIFICATION_DATE = config("APP_MODIFICATION_DATE", default="unknown")
26if APP_VERSION == "unknown" or APP_MODIFICATION_DATE == "unknown":
27 try:
28 import subprocess
30 if APP_VERSION == "unknown":
31 APP_VERSION = (
32 subprocess.check_output(
33 ["git", "rev-parse", "--short", "HEAD"], stderr=subprocess.DEVNULL
34 )
35 .decode("utf-8")
36 .strip()
37 )
38 if APP_MODIFICATION_DATE == "unknown":
39 APP_MODIFICATION_DATE = (
40 subprocess.check_output(
41 ["git", "log", "-1", "--format=%cd"], stderr=subprocess.DEVNULL
42 )
43 .decode("utf-8")
44 .strip()
45 )
46 except Exception:
47 pass
49DISABLE_SOCIAL_AUTH = config("DISABLE_SOCIAL_AUTH", default=False, cast=bool)
51# Analytics settings
52ANALYTICS_ENABLED = config("ANALYTICS_ENABLED", default=False, cast=bool)
53ANALYTICS_ID = config("ANALYTICS_ID", default=None)
54ANALYTICS_BASE_URL = config("ANALYTICS_BASE_URL", default="https://api.swetrix.com")
56# Application definition
58INSTALLED_APPS = [
59 "whitenoise.runserver_nostatic",
60 "django.contrib.admin",
61 "django.contrib.auth",
62 "django.contrib.contenttypes",
63 "django.contrib.sessions",
64 "django.contrib.messages",
65 "django.contrib.staticfiles",
66 "django.contrib.sites",
67 "django.contrib.admindocs",
68 "rest_framework",
69 "bootstrap4",
70 "django_countries",
71 "taggit",
72 "simple_history",
73 "drf_spectacular",
74 # Allauth
75 "allauth",
76 "allauth.account",
77 "allauth.socialaccount",
78 "allauth.mfa",
79 "social_django",
80]
82if DEBUG:
83 INSTALLED_APPS += ["debug_toolbar"]
85if not DISABLE_SOCIAL_AUTH:
86 INSTALLED_APPS += [
87 "allauth.socialaccount.providers.google",
88 "allauth.socialaccount.providers.openid_connect",
89 ]
90 SOCIALACCOUNT_ENABLED = True
91else:
92 SOCIALACCOUNT_ENABLED = False
94INSTALLED_APPS += [
95 # Project Apps
96 "cluster",
97 "operatingsystem",
98 "clusterpackage",
99 "patchtime",
100 "location",
101 "servermodel",
102 "server",
103 "status",
104 "domain",
105 "clustersoftware",
106 "clusterpackagetype",
107 "vendor",
108 "sm.apps.SmConfig",
109]
111MIDDLEWARE = [
112 "django.middleware.security.SecurityMiddleware",
113 "whitenoise.middleware.WhiteNoiseMiddleware",
114 "django.contrib.sessions.middleware.SessionMiddleware",
115 "django.middleware.common.CommonMiddleware",
116 "django.middleware.csrf.CsrfViewMiddleware",
117 "django.contrib.auth.middleware.AuthenticationMiddleware",
118 "django.contrib.messages.middleware.MessageMiddleware",
119 "django.middleware.clickjacking.XFrameOptionsMiddleware",
120 "htmlmin.middleware.HtmlMinifyMiddleware",
121 "htmlmin.middleware.MarkRequestMiddleware",
122 "django.contrib.sites.middleware.CurrentSiteMiddleware",
123 "simple_history.middleware.HistoryRequestMiddleware",
124 # Allauth middleware
125 "allauth.account.middleware.AccountMiddleware",
126]
128if DEBUG:
129 # Insert DebugToolbarMiddleware after SessionMiddleware but before CommonMiddleware
130 try:
131 session_idx = MIDDLEWARE.index(
132 "django.contrib.sessions.middleware.SessionMiddleware"
133 )
134 MIDDLEWARE.insert(
135 session_idx + 1, "debug_toolbar.middleware.DebugToolbarMiddleware"
136 )
137 except ValueError:
138 MIDDLEWARE.append("debug_toolbar.middleware.DebugToolbarMiddleware")
140ROOT_URLCONF = "sm.urls"
142TEMPLATES = [
143 {
144 "BACKEND": "django.template.backends.django.DjangoTemplates",
145 "DIRS": [BASE_DIR / "sm" / "templates"],
146 "OPTIONS": {
147 "context_processors": [
148 "django.template.context_processors.debug",
149 "django.template.context_processors.request",
150 "django.contrib.auth.context_processors.auth",
151 "django.contrib.messages.context_processors.messages",
152 "django.template.context_processors.i18n",
153 "django.template.context_processors.media",
154 "django.template.context_processors.static",
155 "django.template.context_processors.tz",
156 "sm.context_processors.theme_settings",
157 ],
158 "loaders": [
159 "django.template.loaders.filesystem.Loader",
160 "sm.template.loaders.app_directories_enhanced.Loader",
161 "django.template.loaders.app_directories.Loader",
162 ],
163 },
164 },
165]
167WSGI_APPLICATION = "sm.wsgi.application"
169# Database
170# https://docs.djangoproject.com/en/stable/ref/settings/#databases
172DATABASE_URL = config("DATABASE_URL", default=None)
174if DATABASE_URL:
175 DATABASES = {
176 "default": dj_database_url.config(default=DATABASE_URL, conn_max_age=600)
177 }
178elif "test" in argv:
179 DATABASES = {
180 "default": {
181 "ENGINE": "django.db.backends.sqlite3",
182 "NAME": ":memory:",
183 }
184 }
185elif platform == "darwin":
186 DATABASES = {
187 "default": {
188 "ENGINE": "django.db.backends.sqlite3",
189 "NAME": BASE_DIR / "db.sqlite3",
190 }
191 }
192else:
193 # Use config_local or environment variables for MySQL if needed
194 DATABASES = {
195 "default": {
196 "ENGINE": "django.db.backends.sqlite3",
197 "NAME": BASE_DIR / "db.sqlite3",
198 }
199 }
201# Password validation
202# https://docs.djangoproject.com/en/stable/ref/settings/#auth-password-validators
204AUTH_PASSWORD_VALIDATORS = [
205 {
206 "NAME": (
207 "django.contrib.auth.password_validation."
208 "UserAttributeSimilarityValidator"
209 )
210 },
211 {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"},
212 {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
213 {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
214]
216# Internationalization
217# https://docs.djangoproject.com/en/stable/topics/i18n/
219LANGUAGE_CODE = "en-us"
220TIME_ZONE = "UTC"
221USE_I18N = True
222USE_TZ = True
224# Static files (CSS, JavaScript, Images)
225# https://docs.djangoproject.com/en/stable/howto/static-files/
227STATIC_URL = "/static/"
228STATIC_ROOT = BASE_DIR / "static"
229STATICFILES_DIRS = [
230 BASE_DIR / "sm" / "static",
231]
233STORAGES = {
234 "default": {
235 "BACKEND": "django.core.files.storage.FileSystemStorage",
236 },
237 "staticfiles": {
238 "BACKEND": "whitenoise.storage.CompressedStaticFilesStorage",
239 },
240}
242# Default primary key field type
243# https://docs.djangoproject.com/en/stable/ref/settings/#default-auto-field
245DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
247# Extra settings
249SITE_ID = 1
251AUTHENTICATION_BACKENDS = [
252 # Needed to login by username in Django admin, regardless of `allauth`
253 "django.contrib.auth.backends.ModelBackend",
254 # `allauth` specific authentication methods, such as login by e-mail
255 "allauth.account.auth_backends.AuthenticationBackend",
256]
258# Allauth settings
259LOGIN_REDIRECT_URL = "/"
260ACCOUNT_LOGIN_METHODS = {"username", "email"}
261ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]
262ACCOUNT_EMAIL_VERIFICATION = "optional"
263ACCOUNT_EMAIL_CONFIRMATION_ANONYMOUS_REDIRECT_URL = "/"
264ACCOUNT_EMAIL_CONFIRMATION_AUTHENTICATED_REDIRECT_URL = "/"
265ACCOUNT_LOGIN_REDIRECT_URL = "/"
266ACCOUNT_LOGOUT_REDIRECT_URL = "/"
268# Social Auth (Legacy/Transition)
269SOCIAL_AUTH_STRATEGY = "social_django.strategy.DjangoStrategy"
270SOCIAL_AUTH_STORAGE = "social_django.models.DjangoStorage"
272# Disable MFA features that might cause issues if allauth.mfa is not fully configured
273MFA_PASSKEY_LOGIN_ENABLED = False
274MFA_SUPPORTED_TYPES = ["totp", "recovery_codes"]
276if not DISABLE_SOCIAL_AUTH:
277 SOCIALACCOUNT_AUTO_SIGNUP = True
278 SOCIALACCOUNT_ADAPTER = "sm.adapter.MySocialAccountAdapter"
280 SOCIALACCOUNT_PROVIDERS = {}
282 # Google Auth
283 google_id = config("GOOGLE_CLIENT_ID", default=None)
284 google_secret = config("GOOGLE_SECRET", default=None)
285 if google_id and google_secret:
286 SOCIALACCOUNT_PROVIDERS["google"] = {
287 "APPS": [
288 {
289 "client_id": google_id,
290 "secret": google_secret,
291 "settings": {
292 "scope": ["profile", "email"],
293 "auth_params": {"access_type": "online"},
294 },
295 }
296 ]
297 }
299 # Generic OIDC
300 oidc_id = config("OIDC_CLIENT_ID", default=None)
301 oidc_secret = config("OIDC_SECRET", default=None)
302 oidc_url = config("OIDC_URL", default=None)
303 oidc_name = config("OIDC_NAME", default="Localghost SSO")
305 if oidc_id and oidc_secret and oidc_url:
306 SOCIALACCOUNT_PROVIDERS["openid_connect"] = {
307 "APPS": [
308 {
309 "provider_id": "oidc",
310 "name": oidc_name,
311 "client_id": oidc_id,
312 "secret": oidc_secret,
313 "settings": {"server_url": oidc_url},
314 }
315 ]
316 }
318INTERNAL_IPS = [
319 "127.0.0.1",
320]
322MESSAGE_TAGS = {messages.ERROR: "danger"}
324MESSAGE_STORAGE = "django.contrib.messages.storage.fallback.FallbackStorage"
326TAGGIT_CASE_INSENSITIVE = True
328# REST Framework Settings
329REST_FRAMEWORK = {
330 "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
331}
333SPECTACULAR_SETTINGS = {
334 "TITLE": "ServerManager API",
335 "DESCRIPTION": "API for managing servers, clusters, and infrastructure.",
336 "VERSION": "1.0.0",
337 "SERVE_INCLUDE_SCHEMA": False,
338}
340# Bootstrap 4 settings
341BOOTSTRAP4 = {
342 "error_css_class": "bootstrap4-error",
343 "required_css_class": "bootstrap4-required",
344 "javascript_in_head": True,
345 "success_css_class": "bootstrap4-bound",
346}
348HTML_MINIFY = True
350if not DISABLE_SOCIAL_AUTH and os.path.isfile(BASE_DIR / "config_local.py"):
351 from config_local import * # noqa
353# Redefine to ensure no social_core backends survive in quick test mode
354if DISABLE_SOCIAL_AUTH:
355 AUTHENTICATION_BACKENDS = [
356 "django.contrib.auth.backends.ModelBackend",
357 "allauth.account.auth_backends.AuthenticationBackend",
358 ]