""" Django-instellingen voor de Roostersoftware. De architectuur draait om een kleine KERN plus losse MODULES. Modules zijn gewone Django-apps die zich bij opstarten aanmelden bij het plugin-register (zie de app 'plugins'). Of een module zichtbaar/actief is, bepaalt een beheerder per school via een aan/uit-vlag in de database. """ import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent # --- Beveiliging ---------------------------------------------------------- # Instellingen komen uit omgevingsvariabelen, met veilige dev-defaults. # In productie/op de test-server zet je deze via het .env-bestand. def _env_bool(name, default): return os.environ.get(name, str(default)).strip().lower() in ("1", "true", "yes", "on") # Standaard False (veilig). Lokaal zet je DJANGO_DEBUG=1. DEBUG = _env_bool("DJANGO_DEBUG", False) # Geheime sleutel: VERPLICHT via omgeving zodra DEBUG uit staat. # Een stille terugval op een bekende dev-sleutel zou sessies en tokens # vervalsbaar maken; daarom weigert de app dan te starten. SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "") if not SECRET_KEY: if DEBUG: SECRET_KEY = "dev-only-change-me-in-productie" else: raise RuntimeError( "DJANGO_SECRET_KEY ontbreekt. Genereer er een met " "'python -c \"import secrets; print(secrets.token_urlsafe(50))\"' " "en zet hem in het .env-bestand." ) # Komma-gescheiden lijst hosts, bv. "rooster-test.example.nl,localhost". ALLOWED_HOSTS = [h.strip() for h in os.environ.get( "DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1" ).split(",") if h.strip()] # Nodig achter nginx voor POST-verzoeken naar /admin en de API. CSRF_TRUSTED_ORIGINS = [o.strip() for o in os.environ.get( "DJANGO_CSRF_TRUSTED_ORIGINS", "" ).split(",") if o.strip()] # --- Applicaties ---------------------------------------------------------- DJANGO_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ] THIRD_PARTY_APPS = [ "rest_framework", "corsheaders", ] # De kern van het roostersysteem + het plugin-framework. CORE_APPS = [ "plugins", # het plugin-framework zelf (moduleregister + beheer-API) "core", # kerndomein (altijd aan) ] # Losse modules. Eén regel toevoegen = module beschikbaar maken. # Aan/uit zetten gebeurt daarna per school via het beheerscherm. MODULE_APPS = [ "modules.printing", # Printen/Exporteren "modules.accounts", # Multi-user & toegang "modules.leerplein", # Leerplein (groepsstructuur) "modules.stage", # Stage (intern/extern) ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + CORE_APPS + MODULE_APPS MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "config.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "config.wsgi.application" # --- Database ------------------------------------------------------------- # Als POSTGRES_DB gezet is (test-server/productie) gebruiken we PostgreSQL; # anders valt het terug op SQLite voor lokaal ontwikkelen. if os.environ.get("POSTGRES_DB"): DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.environ["POSTGRES_DB"], "USER": os.environ.get("POSTGRES_USER", "rooster"), "PASSWORD": os.environ.get("POSTGRES_PASSWORD", ""), "HOST": os.environ.get("POSTGRES_HOST", "db"), "PORT": os.environ.get("POSTGRES_PORT", "5432"), } } else: DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.environ.get("ROOSTER_DB_PATH", str(BASE_DIR / "db.sqlite3")), } } # --- Wachtwoordvalidatie -------------------------------------------------- AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] # --- Internationalisatie -------------------------------------------------- LANGUAGE_CODE = "nl-nl" TIME_ZONE = "Europe/Amsterdam" USE_I18N = True USE_TZ = True # --- Statische bestanden -------------------------------------------------- # collectstatic verzamelt hier; nginx serveert deze map op /static/ # (o.a. de CSS van de Django-admin). STATIC_URL = "/static/" STATIC_ROOT = os.environ.get("DJANGO_STATIC_ROOT", str(BASE_DIR / "staticfiles")) DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # --- API ------------------------------------------------------------------ # Veiligheid eerst: alles vereist standaard een ingelogde gebruiker. # Uitzonderingen (zoals het login-endpoint) krijgen expliciet AllowAny. REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticated", ], "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", ], # JSON altijd; de browsbare API alleen tijdens ontwikkelen. "DEFAULT_RENDERER_CLASSES": ( [ "rest_framework.renderers.JSONRenderer", "rest_framework.renderers.BrowsableAPIRenderer", ] if DEBUG else ["rest_framework.renderers.JSONRenderer"] ), # Eenvoudige rem op misbruik (bv. wachtwoord-raden op het login-endpoint). "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { "anon": "20/min", "user": "300/min", }, # Achter nginx zien we anders het proxy-IP voor iedereen: dan zou één # throttle-emmer voor de hele school gelden. nginx zet X-Forwarded-For. "NUM_PROXIES": 1, } # Frontend (React dev-server) mag de API benaderen. CORS_ALLOWED_ORIGINS = [ "http://localhost:5173", "http://127.0.0.1:5173", ] CORS_ALLOW_CREDENTIALS = True # --- HTTPS-hardening -------------------------------------------------------- # Zet DJANGO_SECURE=1 zodra de server achter TLS draait (zie DEPLOY.md). # Staat los van DEBUG omdat de test-server nu nog op http://...:8080 draait. if _env_bool("DJANGO_SECURE", False): SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_SSL_REDIRECT = True SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 60 * 60 * 24 * 365 SECURE_HSTS_INCLUDE_SUBDOMAINS = True # Sessie-cookies nooit leesbaar voor JavaScript; de CSRF-cookie moet juist # wél leesbaar zijn (de frontend stuurt hem terug in de X-CSRFToken-header). SESSION_COOKIE_HTTPONLY = True CSRF_COOKIE_HTTPONLY = False # --- Logging ---------------------------------------------------------------- # Console-logging zodat 'docker compose logs' een bruikbaar spoor geeft, # o.a. van mislukte logins en serverfouten. LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "simple": {"format": "{levelname} {asctime} {name} {message}", "style": "{"}, }, "handlers": { "console": {"class": "logging.StreamHandler", "formatter": "simple"}, }, "root": {"handlers": ["console"], "level": "INFO"}, "loggers": { "django.request": {"level": "WARNING"}, "roosterwijs": {"level": "INFO"}, }, }