"""Django settings for the FablePool backend. Configuration is environment-driven (twelve-factor): every deployment-specific value reads from an environment variable with a development-safe default. Production deployments MUST set FABLEPOOL_SECRET_KEY and FABLEPOOL_DEBUG=false. """ from __future__ import annotations import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent def _env_bool(name: str, default: bool) -> bool: raw = os.environ.get(name) if raw is None: return default return raw.strip().lower() in {"1", "true", "yes", "on"} def _env_list(name: str, default: list[str]) -> list[str]: raw = os.environ.get(name) if raw is None: return default return [item.strip() for item in raw.split(",") if item.strip()] # --------------------------------------------------------------------------- core SECRET_KEY = os.environ.get( "FABLEPOOL_SECRET_KEY", "dev-only-insecure-key-do-not-use-in-production", ) DEBUG = _env_bool("FABLEPOOL_DEBUG", True) ALLOWED_HOSTS = _env_list("FABLEPOOL_ALLOWED_HOSTS", ["localhost", "127.0.0.1"]) INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.postgres", "rest_framework", # FablePool apps "apps.core", "apps.accounts", "apps.taxonomy", "apps.content", "apps.learning", "apps.community", ] MIDDLEWARE = [ "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 = "fablepool.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "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 = "fablepool.wsgi.application" ASGI_APPLICATION = "fablepool.asgi.application" # --------------------------------------------------------------------------- database DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql", "NAME": os.environ.get("FABLEPOOL_DB_NAME", "fablepool"), "USER": os.environ.get("FABLEPOOL_DB_USER", "fablepool"), "PASSWORD": os.environ.get("FABLEPOOL_DB_PASSWORD", "fablepool"), "HOST": os.environ.get("FABLEPOOL_DB_HOST", "localhost"), "PORT": os.environ.get("FABLEPOOL_DB_PORT", "5432"), "CONN_MAX_AGE": int(os.environ.get("FABLEPOOL_DB_CONN_MAX_AGE", "60")), } } DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # --------------------------------------------------------------------------- auth AUTH_USER_MODEL = "accounts.User" AUTH_PASSWORD_VALIDATORS = [ {"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"}, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 10}, }, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] # --------------------------------------------------------------------------- i18n LANGUAGE_CODE = "en" TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True # --------------------------------------------------------------------------- static & media STATIC_URL = "static/" STATIC_ROOT = BASE_DIR / "staticfiles" MEDIA_URL = "media/" MEDIA_ROOT = BASE_DIR / "mediafiles" # In production point DEFAULT file storage at S3/MinIO via the STORAGES setting # (django-storages); the development default keeps everything on local disk so a # clean checkout works without object storage. # --------------------------------------------------------------------------- caching CACHES = { "default": { # Development default; production deployments swap this for # django.core.cache.backends.redis.RedisCache via FABLEPOOL_REDIS_URL. "BACKEND": "django.core.cache.backends.locmem.LocMemCache", } } _redis_url = os.environ.get("FABLEPOOL_REDIS_URL") if _redis_url: CACHES["default"] = { "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": _redis_url, } # --------------------------------------------------------------------------- DRF REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "rest_framework.authentication.SessionAuthentication", "rest_framework.authentication.TokenAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly", ], "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.CursorPagination", "PAGE_SIZE": 20, "DEFAULT_THROTTLE_CLASSES": [ "rest_framework.throttling.AnonRateThrottle", "rest_framework.throttling.UserRateThrottle", ], "DEFAULT_THROTTLE_RATES": { # Matches the rate-limit tiers documented in docs/api-conventions.md # and advertised via X-RateLimit-* headers in api/openapi.yaml. "anon": "60/min", "user": "240/min", }, "DEFAULT_RENDERER_CLASSES": [ "rest_framework.renderers.JSONRenderer", ], "EXCEPTION_HANDLER": "rest_framework.views.exception_handler", } # --------------------------------------------------------------------------- FablePool # Directory containing the canonical JSON Schemas for structured content documents. FABLEPOOL_SCHEMA_DIR = BASE_DIR / "schemas" / "v1" # Default content license applied to new containers (SPDX identifier). FABLEPOOL_DEFAULT_CONTENT_LICENSE = "CC-BY-SA-4.0" # --------------------------------------------------------------------------- logging 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": os.environ.get("FABLEPOOL_LOG_LEVEL", "INFO")}, }