Coverage for backend/django/CoreRoot/settings.py: 85%
94 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-11 21:43 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-02-11 21:43 +0000
1"""
2Django settings for CoreRoot project.
4Generated by 'django-admin startproject' using Django 4.2.7.
6For more information on this file, see
7https://docs.djangoproject.com/en/4.2/topics/settings/
9For the full list of settings and their values, see
10https://docs.djangoproject.com/en/4.2/ref/settings/
11"""
12import logging
13from pathlib import Path
14from django.db.backends.postgresql.psycopg_any import IsolationLevel
15import os
16import warnings
17from dotenv import load_dotenv
19# Build paths inside the project like this: BASE_DIR / 'subdir'.
20BASE_DIR = Path(__file__).resolve().parent.parent
22load_dotenv(dotenv_path=BASE_DIR / ".env") # optional base
24DEBUG = os.getenv("PRODUCTION", "False").lower() in ('false', '0')
26if DEBUG: 26 ↛ 29line 26 didn't jump to line 29 because the condition on line 26 was always true
27 load_dotenv(dotenv_path=BASE_DIR / ".env.development.local")
29DIAGNOSTICS_RULES_PATH = os.getenv(
30 "DIAGNOSTICS_RULES_PATH",
31 str((BASE_DIR / "diagnostics" / "rules" / "validation_rules.jdm").resolve()),
32)
34# Quick-start development settings - unsuitable for production
35# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
37# SECURITY WARNING: keep the secret key used in production secret!
38SECRET_KEY = 'django-insecure-6zboi37mw#!#6e^qx2gk#0t@wqv5_3*yh8u@^^smbzgk6enq^('
40# SECURITY WARNING: don't run with debug turned on in production!
43DAPR_APP_API_TOKEN = os.getenv("DAPR_APP_API_TOKEN", None)
44if DEBUG and DAPR_APP_API_TOKEN is None:
45 DAPR_APP_API_TOKEN = "BlYMxKQgDWt+NDVa7NsNBw==" # This must be the same as defined in django-dapr's env variable in the docker-compose file
47PROFILING_ENABLED = os.getenv("PROFILING_ENABLED", "False").lower() in ('true', '1')
49# Get string list from comma-separated list of allowed hosts,
50# e.g. "localhost,api.ahuora.co.nz,127.0.0.1" turns into ["localhost", "api.ahuora.co.nz", "127.0.0.1"]
51ALLOWED_HOSTS = list(filter(None, os.getenv("ALLOWED_HOSTS", "").split(",")))
52if DEBUG: 52 ↛ 55line 52 didn't jump to line 55 because the condition on line 52 was always true
53 ALLOWED_HOSTS += ["host.docker.internal", "localhost", "127.0.0.1"]
55CODE_COVERAGE_ENABLED = os.getenv("CODE_COVERAGE_ENABLED", "False").lower() in ('true', '1')
56if CODE_COVERAGE_ENABLED:
57 # Code coverage can be run for Django in two ways:
58 # 1. Using `coverage run ...` to start the server
59 # 2. By setting the COVERAGE_PROCESS_START environment variable and manually starting coverage.
60 # Number 1 is used when running tests, while number 2 is used when running the server via Granian,
61 # as `coverage run ...` does not seem to be able to discover Python subprocesses started by a non-Python program.
63 direct_code_coverage_active = os.getenv("COVERAGE_PROCESS_CONFIG")
64 indirect_code_coverage_active = os.getenv("COVERAGE_PROCESS_START")
66 if direct_code_coverage_active: 66 ↛ 68line 66 didn't jump to line 68 because the condition on line 66 was always true
67 logging.info("Detected code coverage running via `coverage run ...`")
68 elif indirect_code_coverage_active:
69 logging.info("Detected code coverage running via COVERAGE_PROCESS_START environment variable (likely sitepackages script injection)")
70 else:
71 import coverage
73 # Set default coverage config file name
74 os.environ.setdefault("COVERAGE_PROCESS_START", ".coveragerc")
76 coverage.process_startup()
77 logging.info("Manually started code coverage measurement")
79 if not DEBUG: 79 ↛ 80line 79 didn't jump to line 80 because the condition on line 79 was never true
80 warnings.warn("Code coverage measurement running in production mode. This may impact performance.")
82# Application definition
84INSTALLED_APPS = [
85 'django.contrib.auth',
86 'django.contrib.contenttypes',
87 'django.contrib.sessions',
88 'django.contrib.messages',
89 'django.contrib.staticfiles',
90 'rest_framework',
91 'drf_spectacular',
92 'corsheaders',
93 'core',
94 'core.auxiliary',
95 'authentication',
96 'authentication.user',
97 'flowsheetInternals',
98 'flowsheetInternals.unitops',
99 'flowsheetInternals.graphicData',
100 'flowsheetInternals.propertyPackages',
101 'PinchAnalysis',
102 'django_bleach',
103 'diagnostics',
104]
106CHANNEL_LAYERS = {
107 "default": {
108 "BACKEND": "channels_redis.core.RedisChannelLayer",
109 "CONFIG": {
110 "hosts": [(
111 os.getenv("CHANNELS_REDIS_HOST", "localhost"),
112 os.getenv("CHANNELS_REDIS_PORT", 6379))
113 ],
114 },
115 },
116}
118REST_FRAMEWORK = {
119 'DEFAULT_AUTHENTICATION_CLASSES': (
120 'authentication.custom_drf_authentication.AhuoraRemoteUserAuthentication',
121 ),
122 'EXCEPTION_HANDLER': 'core.exceptions.otel_trace_exception_handler',
123 'DEFAULT_RENDERER_CLASSES': (
124 'rest_framework.renderers.JSONRenderer',
125 ),
126 'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
127 'DEFAULT_PERMISSION_CLASSES': [
128 'rest_framework.permissions.IsAuthenticated',
129 ],
130 'DEFAULT_PARSER_CLASSES': [
131 'rest_framework.parsers.JSONParser',
132 'core.parsers.CloudEventsParser'
133 ]
134}
136SPECTACULAR_SETTINGS = {
137 'TITLE': 'Ahuora API',
138 'DESCRIPTION': 'Your project description',
139 'VERSION': '1.0.0',
140 'SERVE_INCLUDE_SCHEMA': False,
141}
143MIDDLEWARE = [
144 'django.middleware.security.SecurityMiddleware',
145 'django.contrib.sessions.middleware.SessionMiddleware',
146 'corsheaders.middleware.CorsMiddleware',
147 'django.middleware.common.CommonMiddleware',
148 'django.middleware.csrf.CsrfViewMiddleware',
149 'django.contrib.auth.middleware.AuthenticationMiddleware',
150 'authentication.middleware.AhuoraRemoteUserMiddleware',
151 'django.contrib.messages.middleware.MessageMiddleware',
152 'django.middleware.clickjacking.XFrameOptionsMiddleware',
153]
155SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
157CORS_ALLOWED_ORIGINS = [
158 "http://localhost:19006",# Dev server
159 "http://127.0.0.1:19006",
160 "http://localhost:19005",# E2E Test server
161 "http://127.0.0.1:19005",
162 "http://localhost:3000",
163 "http://front-end:19006",
164 "http://172.29.171.74:19006",
165 "https://ahuora.org.nz", # Production server
166 "https://www.ahuora.org.nz",
167 "http://ahuora.org.nz",
168 "http://www.ahuora.org.nz"
169]
171CORS_ALLOW_CREDENTIALS = True
173ROOT_URLCONF = 'CoreRoot.urls'
175TEMPLATES = [
176 {
177 'BACKEND': 'django.template.backends.django.DjangoTemplates',
178 'DIRS': [],
179 'APP_DIRS': True,
180 'OPTIONS': {
181 'context_processors': [
182 'django.template.context_processors.debug',
183 'django.template.context_processors.request',
184 'django.contrib.auth.context_processors.auth',
185 'django.contrib.messages.context_processors.messages',
186 ],
187 },
188 },
189]
191OPEN_TELEMETRY_TRACER_NAME = "ahuora-api"
193WSGI_APPLICATION = 'CoreRoot.wsgi.application'
195FIXTURE_DIRS = [
196 BASE_DIR / '/fixtures'
197]
198#Renewable Ninja Token
200RENEWABLES_NINJA_TOKEN = os.getenv("RENEWABLES_NINJA_TOKEN")
201if not RENEWABLES_NINJA_TOKEN: 201 ↛ 208line 201 didn't jump to line 208 because the condition on line 201 was always true
202 warnings.warn("RENEWABLES_NINJA_TOKEN is not set in environment variables.", UserWarning)
205# Database
206# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
208DATABASES = {
209 'default': {
210 'ENGINE': 'django.db.backends.postgresql',
211 'NAME': os.getenv("POSTGRES_DB", "postgres"),
212 'HOST': os.getenv("POSTGRES_HOST", "localhost"),
213 'USER': os.getenv("POSTGRES_USER", "postgres"),
214 'PASSWORD': os.getenv("POSTGRES_PASSWORD", "postgres"),
215 'OPTIONS': {
216 'isolation_level': IsolationLevel.READ_COMMITTED,
217 }
218 }
219}
221LOGGING = {
222 "version": 1,
223 "disable_existing_loggers": False,
224 "formatters": {
225 "trace_formatter": {
226 'format': '[%(asctime)s] %(levelname)s [%(filename)s:%(lineno)d] [trace_id=%(otelTraceID)s span_id=%(otelSpanID)s] [%(funcName)s] %(message)s', # optional, default is logging.BASIC_FORMAT
227 'datefmt': '%Y-%m-%d %H:%M:%S', # optional, default is '%Y-%m-%d %H:%M:%S'
228 },
229 },
230 "handlers": {
231 "console": {
232 "class": "logging.StreamHandler",
233 "formatter": "trace_formatter",
234 },
235 },
236 "root": {
237 "handlers": ["console"],
238 "level": "INFO",
239 },
240 "loggers": {
241 "django": {
242 "handlers": ["console"],
243 "level": "INFO",
244 "propagate": False,
245 }
246 },
247}
249# Password validation
250# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
252AUTH_PASSWORD_VALIDATORS = [
253 {
254 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
255 },
256 {
257 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
258 },
259 {
260 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
261 },
262 {
263 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
264 },
265]
267# Password hashers/KDFs to enable.
268# The first entry will be used when creating new passwords.
269PASSWORD_HASHERS = [
270 "django.contrib.auth.hashers.Argon2PasswordHasher"
271]
273# Internationalization
274# https://docs.djangoproject.com/en/4.2/topics/i18n/
276LANGUAGE_CODE = 'en-us'
278TIME_ZONE = 'UTC'
280USE_I18N = True
282USE_TZ = True
284# Static files (CSS, JavaScript, Images)
285# https://docs.djangoproject.com/en/4.2/howto/static-files/
287STATIC_URL = 'static/'
289SILKY_META = True
291# Default primary key field type
292# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
294DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
296AUTH_USER_MODEL = 'authentication_user.User'
298AUTHENTICATION_BACKENDS = [
299 "authentication.remote_user_backend.RemoteUserBackendWithEmail",
300]
302PLATFORM_TEST_EMAIL = os.getenv("PLATFORM_TEST_EMAIL", "test@ahuoratech.app")
304PLATFORM_ADMINISTRATORS_GROUP = os.getenv("PLATFORM_ADMINISTRATORS_GROUP", "/PlatformAdministrators")
305PLATFORM_TESTERS_GROUP = os.getenv("PLATFORM_TESTERS_GROUP", "/PlatformTesters")
307REMOTE_USER_HEADER = "HTTP_X_AUTH_REQUEST_USER"
308ASGI_REMOTE_USER_HEADER = "x-auth-request-user"
309REMOTE_USER_EMAIL_HEADER = "HTTP_X_AUTH_REQUEST_EMAIL"
310ASGI_REMOTE_USER_EMAIL_HEADER = "x-auth-request-email"
311REMOTE_USER_GROUPS_HEADER = "HTTP_X_AUTH_REQUEST_GROUPS"
312ASGI_REMOTE_USER_GROUPS_HEADER = "x-auth-request-groups"
313REMOTE_USER_ACCESS_TOKEN_HEADER = "HTTP_X_AUTH_REQUEST_ACCESS_TOKEN"
314ASGI_REMOTE_USER_ACCESS_TOKEN_HEADER = "x-auth-request-access-token"
316def __insert_middleware(middleware_name: str, before_middleware_name: str):
317 index = MIDDLEWARE.index(before_middleware_name)
318 MIDDLEWARE.insert(index, middleware_name)
320def set_dapr_endpoints():
321 # Monkey-patch Dapr SDK config to avoid the need to set environment variables externally when
322 # running the API server. We still check for environment variables to allow for overriding.
323 from dapr.conf import settings
325 if os.getenv("DAPR_HTTP_ENDPOINT") is None:
326 settings.DAPR_HTTP_ENDPOINT = "http://localhost:3501"
328 if os.getenv("DAPR_GRPC_ENDPOINT") is None:
329 settings.DAPR_GRPC_ENDPOINT = "localhost:50001"
331if DEBUG: 331 ↛ 348line 331 didn't jump to line 348 because the condition on line 331 was always true
332 set_dapr_endpoints()
334 # Add the dummy auth header middleware and the remote user middleware to the middleware list
335 __insert_middleware(
336 'authentication.middleware.dummy_auth_header_middleware',
337 'authentication.middleware.AhuoraRemoteUserMiddleware'
338 )
340 # Only allow profiling with Silk if we're both in debug mode and profiling is enabled
341 if PROFILING_ENABLED: 341 ↛ 342line 341 didn't jump to line 342 because the condition on line 341 was never true
342 __insert_middleware(
343 'silk.middleware.SilkyMiddleware',
344 'django.contrib.auth.middleware.AuthenticationMiddleware'
345 )
346 INSTALLED_APPS.append('silk')
348BLEACH_ALLOWED_TAGS = [
349 'a', 'abbr', 'acronym', 'b', 'blockquote', 'code', 'em', 'i', 'li', 'ol', 'strong', 'ul', 'p', 'br', 'u'
350]
351BLEACH_ALLOWED_ATTRIBUTES = {
352 '*': ['class', 'id', 'style'],
353 'a': ['href', 'rel'],
354}
355BLEACH_ALLOWED_STYLES = [
356 'color', 'font-weight', 'text-decoration',
357]
358BLEACH_STRIP_TAGS = True
359BLEACH_STRIP_COMMENTS = True