""" Base Django settings for thrillwiki project. This file contains only core Django settings that are common across all environments. Environment-specific settings are in local.py, production.py, and test.py. Modular configuration is imported from config/settings/. Structure: - Core settings (SECRET_KEY, DEBUG, ALLOWED_HOSTS) - Application definition (INSTALLED_APPS, MIDDLEWARE) - URL and template configuration - Internationalization - Imports from modular settings (database, cache, security, etc.) """ import sys from pathlib import Path from decouple import config # ============================================================================= # Path Configuration # ============================================================================= # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent.parent # Add apps directory to sys.path so Django can find the apps apps_dir = BASE_DIR / "apps" if apps_dir.exists() and str(apps_dir) not in sys.path: sys.path.insert(0, str(apps_dir)) # ============================================================================= # Core Settings # ============================================================================= # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = config("SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! DEBUG = config("DEBUG", default=True, cast=bool) # Allowed hosts (comma-separated in .env) ALLOWED_HOSTS = config( "ALLOWED_HOSTS", default="localhost,127.0.0.1", cast=lambda v: [s.strip() for s in v.split(",") if s.strip()] ) # CSRF trusted origins (comma-separated in .env) CSRF_TRUSTED_ORIGINS = config( "CSRF_TRUSTED_ORIGINS", default="", cast=lambda v: [s.strip() for s in v.split(",") if s.strip()] ) # ============================================================================= # Application Definition # ============================================================================= DJANGO_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", "django.contrib.sites", "django.contrib.gis", # GeoDjango ] THIRD_PARTY_APPS = [ # Django Cloudflare Images Toolkit - moved to top to avoid circular imports "django_cloudflareimages_toolkit", "rest_framework", # Django REST Framework # Token authentication (kept for backward compatibility) "rest_framework.authtoken", "rest_framework_simplejwt", # JWT authentication "rest_framework_simplejwt.token_blacklist", # JWT token blacklist "dj_rest_auth", # REST authentication with JWT support "dj_rest_auth.registration", # REST registration support "drf_spectacular", # OpenAPI 3.0 documentation "corsheaders", # CORS headers for API "pghistory", # django-pghistory "pgtrigger", # Required by django-pghistory "django_fsm_log", # FSM transition logging "allauth", "allauth.account", "allauth.socialaccount", "allauth.socialaccount.providers.google", "allauth.socialaccount.providers.discord", "django_cleanup", "django_filters", "django_htmx", "whitenoise", "django_tailwind_cli", "autocomplete", # Django HTMX Autocomplete "health_check", # Health checks "health_check.db", "health_check.cache", "health_check.storage", "health_check.contrib.migrations", "health_check.contrib.redis", "django_celery_beat", # Celery beat scheduler "django_celery_results", # Celery result backend "django_extensions", # Django Extensions for enhanced development tools ] LOCAL_APPS = [ "apps.core", "apps.accounts", "apps.parks", "apps.rides", "api", # Centralized API app (located at backend/api/) "django_forwardemail", # New PyPI package for email service "apps.moderation", ] INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS # ============================================================================= # Middleware Configuration # ============================================================================= MIDDLEWARE = [ "django.middleware.cache.UpdateCacheMiddleware", # Must be first for cache middleware "django.middleware.gzip.GZipMiddleware", # Response compression "corsheaders.middleware.CorsMiddleware", # CORS middleware for API "django.middleware.security.SecurityMiddleware", "apps.core.middleware.security_headers.SecurityHeadersMiddleware", # Custom security headers "apps.core.middleware.rate_limiting.AuthRateLimitMiddleware", # Rate limiting "whitenoise.middleware.WhiteNoiseMiddleware", "apps.core.middleware.performance_middleware.PerformanceMiddleware", # Performance monitoring "apps.core.middleware.performance_middleware.QueryCountMiddleware", # Database query monitoring "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", "apps.core.middleware.analytics.PgHistoryContextMiddleware", # History context tracking "allauth.account.middleware.AccountMiddleware", "django_htmx.middleware.HtmxMiddleware", "django.middleware.cache.FetchFromCacheMiddleware", # Must be last for cache middleware ] # ============================================================================= # URL Configuration # ============================================================================= ROOT_URLCONF = "thrillwiki.urls" WSGI_APPLICATION = "thrillwiki.wsgi.application" # ============================================================================= # Template Configuration # ============================================================================= # Toggle to enable/disable Django template support via env var TEMPLATES_ENABLED = config("TEMPLATES_ENABLED", default=True, cast=bool) if TEMPLATES_ENABLED: TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [BASE_DIR / "templates"], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "apps.moderation.context_processors.moderation_access", "apps.core.context_processors.fsm_context", "apps.core.context_processors.breadcrumbs", "apps.core.context_processors.page_meta", ] }, } ] else: # When templates are disabled, still need APP_DIRS=True for DRF Spectacular TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "APP_DIRS": True, "DIRS": [BASE_DIR / "templates/" / "404"], "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", "apps.moderation.context_processors.moderation_access", "apps.core.context_processors.fsm_context", "apps.core.context_processors.breadcrumbs", "apps.core.context_processors.page_meta", ] }, } ] # ============================================================================= # Custom User Model # ============================================================================= AUTH_USER_MODEL = "accounts.User" # ============================================================================= # Internationalization # ============================================================================= LANGUAGE_CODE = "en-us" TIME_ZONE = "America/New_York" USE_I18N = True USE_TZ = True # ============================================================================= # Default Primary Key # ============================================================================= DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" # ============================================================================= # Test Runner # ============================================================================= TEST_RUNNER = "django.test.runner.DiscoverRunner" # ============================================================================= # Import Modular Settings # ============================================================================= # Import settings from modular configuration files. # These imports add/override settings defined above. # Database configuration (DATABASES, GDAL_LIBRARY_PATH, GEOS_LIBRARY_PATH) from config.settings.database import * # noqa: F401,F403,E402 # Cache configuration (CACHES, SESSION_*, CACHE_MIDDLEWARE_*) from config.settings.cache import * # noqa: F401,F403,E402 # Security configuration (SECURE_*, CSRF_*, SESSION_COOKIE_*, AUTH_PASSWORD_VALIDATORS) from config.settings.security import * # noqa: F401,F403,E402 # Email configuration (EMAIL_*, FORWARD_EMAIL_*) from config.settings.email import * # noqa: F401,F403,E402 # Logging configuration (LOGGING) from config.settings.logging import * # noqa: F401,F403,E402 # REST Framework configuration (REST_FRAMEWORK, CORS_*, SIMPLE_JWT, REST_AUTH, SPECTACULAR_SETTINGS) from config.settings.rest_framework import * # noqa: F401,F403,E402 # Third-party configuration (ACCOUNT_*, SOCIALACCOUNT_*, CLOUDFLARE_IMAGES, etc.) from config.settings.third_party import * # noqa: F401,F403,E402 # Storage configuration (STATIC_*, MEDIA_*, STORAGES, WHITENOISE_*, FILE_UPLOAD_*) from config.settings.storage import * # noqa: F401,F403,E402 # ============================================================================= # Post-Import Overrides # ============================================================================= # Settings that need to reference values from imported modules # Update SimpleJWT to use the SECRET_KEY SIMPLE_JWT["SIGNING_KEY"] = SECRET_KEY # noqa: F405 # ============================================================================= # Startup Validation # ============================================================================= # Run configuration validation after all settings are loaded. # These validations catch configuration errors early during Django startup. from config.settings.secrets import run_startup_validation as validate_secrets # noqa: E402 from config.settings.validation import run_startup_validation as validate_config # noqa: E402 # Run secret validation (fails fast in production, warns in development) validate_secrets() # Run configuration validation (fails fast in production, warns in development) validate_config()