Files
ping-mee-self-service-platform/portal/config/settings.py
T
ebbe.bassandClaude Sonnet 5.5 0dc079a8ac feat: add page shell, preferences and translations
Base layout with top bar, role-based menu, profile menu and phone layout; theme, language and German form of address kept in cookies; German formal, German informal and English text; Fluent avatar bundled in the image by a Node build stage; placeholder pages for the Publisher and Admin groups.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3t4WL7bd9bdHHNjEsxzqC
2026-10-17 21:05:40 +02:00

125 lines
4.0 KiB
Python

"""Django settings. All deployment values come from environment variables (AD-13)."""
import os
import sys
from pathlib import Path
from .env import ConfigError, load_config
from .logging import build_logging
try:
_config = load_config(os.environ)
except ConfigError as exc:
# One clear message, no traceback, no values.
print(str(exc), file=sys.stderr)
sys.exit(1)
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = _config.secret_key
DEBUG = _config.debug
ALLOWED_HOSTS = _config.allowed_hosts
# The client-IP rule (AD-7) trusts X-Forwarded-For only from these peers.
TRUSTED_PROXIES = _config.trusted_proxies
DATA_DIR = _config.data_dir
DISPLAY_TIME_ZONE = _config.default_timezone
DIRECTORY = _config.directory
OIDC = _config.oidc
INSTALLED_APPS = [
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.sessions",
"django.contrib.staticfiles",
"portal.audit",
"portal.accounts",
"portal.shell",
]
MIDDLEWARE = [
"portal.config.middleware.RequestIdMiddleware",
"portal.accounts.middleware.ClientIpMiddleware",
"portal.config.middleware.SecurityHeadersMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"portal.accounts.middleware.AbsoluteSessionLimitMiddleware",
"portal.accounts.middleware.CurrentUserMiddleware",
"portal.shell.middleware.PreferencesMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
]
ROOT_URLCONF = "portal.config.urls"
WSGI_APPLICATION = "portal.config.wsgi.application"
TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [BASE_DIR / "portal" / "templates"],
"APP_DIRS": True,
"OPTIONS": {
"context_processors": [
"django.template.context_processors.request",
"django.template.context_processors.i18n",
"portal.shell.context.shell",
],
},
}
]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": DATA_DIR / "db.sqlite3",
"OPTIONS": {
"transaction_mode": "IMMEDIATE",
"timeout": 20,
"init_command": "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;",
},
}
}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Time and language (AD-15): store UTC, translate every string.
USE_TZ = True
TIME_ZONE = "UTC"
USE_I18N = True
LANGUAGE_CODE = "en"
LANGUAGES = [("en", "English"), ("de", "Deutsch")]
LOCALE_PATHS = [BASE_DIR / "portal" / "locale"]
# Static files served by whitenoise; assets are built into the image.
STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "portal" / "static"]
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
# Sessions in the database; cookies secure, HttpOnly, SameSite=Lax (AD-6).
SESSION_ENGINE = "portal.accounts.session_store"
SESSION_COOKIE_AGE = 60 * 60 # idle limit: 1 hour, renewed on every request
SESSION_SAVE_EVERY_REQUEST = True
SESSION_ABSOLUTE_SECONDS = 4 * 60 * 60 # absolute limit: 4 hours
# How long roles and account state are trusted before the directory is asked again (AD-5).
ROLE_CACHE_SECONDS = 60
SESSION_COOKIE_SECURE = not DEBUG
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = not DEBUG
CSRF_TRUSTED_ORIGINS = [OIDC.public_base_url]
CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SAMESITE = "Lax"
# TLS ends at the reverse proxy.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = "same-origin"
SECURE_HSTS_SECONDS = 0 if DEBUG else 31536000
X_FRAME_OPTIONS = "DENY"
LOGGING = build_logging(_config.log_level)