""" Base Django settings shared across all environments. Environment-specific overrides live in `dev.py` and `prod.py`. Select one with the DJANGO_SETTINGS_MODULE environment variable. """ import os from datetime import timedelta from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent.parent def get_env_list(var_name: str, default_list: list[str]) -> list[str]: val = os.environ.get(var_name) if val: return [item.strip() for item in val.split(",")] return default_list DOMAIN = os.environ.get("DOMAIN", "localhost") ALLOWED_HOSTS = get_env_list( "ALLOWED_HOSTS", ["localhost", "127.0.0.1", "backend", "worker", DOMAIN], ) INSTALLED_APPS = [ "whitenoise.runserver_nostatic", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", # Third party "rest_framework", "rest_framework.authtoken", "rest_framework_simplejwt", "rest_framework_simplejwt.token_blacklist", "corsheaders", "django_filters", "drf_spectacular", "django_ratelimit", "django_celery_beat", # Local "api", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "whitenoise.middleware.WhiteNoiseMiddleware", "compression_middleware.middleware.CompressionMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "holy_backend.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 = "holy_backend.wsgi.application" DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": BASE_DIR / "db.sqlite3", } } DJANGO_CACHE_URL = os.environ.get("DJANGO_CACHE_URL", "redis://redis:6379/1") CACHES = { "default": { "BACKEND": "django.core.cache.backends.redis.RedisCache", "LOCATION": DJANGO_CACHE_URL, } } SILENCED_SYSTEM_CHECKS = ["django_ratelimit.W001", "django_ratelimit.E003"] 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"}, ] LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_TZ = True STATIC_URL = "static/" STATIC_ROOT = BASE_DIR / "staticfiles" STATICFILES_STORAGE = "whitenoise.storage.CompressedStaticFilesStorage" WHITENOISE_INDEX_PAGE = True MEDIA_URL = "media/" MEDIA_ROOT = BASE_DIR / "media" # Reverse proxy USE_X_FORWARDED_HOST = True USE_X_FORWARDED_PORT = True SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") CSRF_TRUSTED_ORIGINS = get_env_list( "CSRF_TRUSTED_ORIGINS", [f"https://{DOMAIN}", "http://localhost:5173"], ) AUTH_USER_MODEL = "api.User" REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES": [ "api.utils.cookie_auth.CookieJWTAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "rest_framework.permissions.IsAuthenticatedOrReadOnly", ], "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend"], "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination", "PAGE_SIZE": 50, "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema", } SIMPLE_JWT = { "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60), "REFRESH_TOKEN_LIFETIME": timedelta(days=14), "ROTATE_REFRESH_TOKENS": True, "BLACKLIST_AFTER_ROTATION": True, "AUTH_HEADER_TYPES": ("Bearer",), } # httpOnly-cookie JWT config. The Authorization header path is kept as a # fallback during the rollout — once all clients use cookies it can be removed. JWT_AUTH_COOKIE_ACCESS = "access_token" JWT_AUTH_COOKIE_REFRESH = "refresh_token" JWT_AUTH_COOKIE_SECURE = False # prod.py overrides to True JWT_AUTH_COOKIE_SAMESITE = "Lax" JWT_AUTH_COOKIE_PATH = "/" CORS_ALLOWED_ORIGINS = get_env_list( "CORS_ALLOWED_ORIGINS", [f"https://{DOMAIN}", "http://localhost:5173", "http://127.0.0.1:5173"], ) CORS_ALLOW_CREDENTIALS = True FRONTEND_URL = os.environ.get("FRONTEND_URL", f"https://{DOMAIN}").rstrip("/") # Email (SMTP) EMAIL_HOST = os.environ.get("EMAIL_HOST") EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 587)) EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "false").lower() == "true" EMAIL_USE_SSL = os.environ.get("EMAIL_USE_SSL", "false").lower() == "true" EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER") EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD") EMAIL_SKIP_CERT_VERIFICATION = ( os.environ.get("EMAIL_SKIP_CERT_VERIFICATION", "false").lower() == "true" ) DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", EMAIL_HOST_USER) SERVER_EMAIL = DEFAULT_FROM_EMAIL if EMAIL_HOST: EMAIL_BACKEND = "api.email_backend.InsecureEmailBackend" else: EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" SPECTACULAR_SETTINGS = { "TITLE": "Holy Flavors API", "DESCRIPTION": "API for rating flavors", "VERSION": "1.0.0", "SERVE_INCLUDE_SCHEMA": False, } # Celery CELERY_BROKER_URL = os.environ.get("CELERY_BROKER_URL", "redis://redis:6379/0") CELERY_RESULT_BACKEND = os.environ.get("CELERY_RESULT_BACKEND", "redis://redis:6379/0") CELERY_TIMEZONE = TIME_ZONE CELERY_TASK_TRACK_STARTED = True CELERY_TASK_TIME_LIMIT = 30 * 60 CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" CELERY_TASK_ALWAYS_EAGER = os.environ.get("CELERY_TASK_ALWAYS_EAGER", "false").lower() == "true" CELERY_TASK_ACKS_LATE = True CELERY_TASK_REJECT_ON_WORKER_LOST = True CELERY_WORKER_PREFETCH_MULTIPLIER = 1 CELERY_RESULT_EXPIRES = 3600