Coverage for cookie / settings.py: 89%
44 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 19:13 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-14 19:13 +0000
1"""
2Django settings for cookie project.
3Single settings file for simplicity.
4"""
6import os
7from pathlib import Path
9import dj_database_url
11BASE_DIR = Path(__file__).resolve().parent.parent
13# ===========================================
14# Environment-based Configuration
15# ===========================================
17DEBUG = os.environ.get("DEBUG", "True").lower() == "true"
20def get_secret_key():
21 """Get secret key from environment or generate one."""
22 env_key = os.environ.get("SECRET_KEY")
23 if env_key:
24 return env_key
25 if DEBUG:
26 return "django-insecure-dev-key-change-in-production"
27 from django.core.management.utils import get_random_secret_key
29 return get_random_secret_key()
32SECRET_KEY = get_secret_key()
34ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "*").split(",")
36# Use X-Forwarded-Host header (preserves port when behind nginx proxy)
37USE_X_FORWARDED_HOST = True
39# CSRF trusted origins (for reverse proxies)
40csrf_origins = os.environ.get("CSRF_TRUSTED_ORIGINS", "")
41CSRF_TRUSTED_ORIGINS = [o.strip() for o in csrf_origins.split(",") if o.strip()]
43INSTALLED_APPS = [
44 "django.contrib.contenttypes",
45 "django.contrib.sessions",
46 "django.contrib.staticfiles",
47 "apps.core",
48 "apps.profiles",
49 "apps.recipes",
50 "apps.ai",
51 "apps.legacy",
52]
54MIDDLEWARE = [
55 "django.middleware.security.SecurityMiddleware",
56 "whitenoise.middleware.WhiteNoiseMiddleware",
57 "django.contrib.sessions.middleware.SessionMiddleware",
58 "django.middleware.common.CommonMiddleware",
59 "apps.core.middleware.DeviceDetectionMiddleware",
60]
62ROOT_URLCONF = "cookie.urls"
64TEMPLATES = [
65 {
66 "BACKEND": "django.template.backends.django.DjangoTemplates",
67 "DIRS": [],
68 "APP_DIRS": True,
69 "OPTIONS": {
70 "context_processors": [
71 "django.template.context_processors.debug",
72 "django.template.context_processors.request",
73 ],
74 },
75 },
76]
78WSGI_APPLICATION = "cookie.wsgi.application"
80# Database configuration
81# Priority: DATABASE_URL > DATABASE_PATH > default SQLite
82DATABASE_URL = os.environ.get("DATABASE_URL")
84if DATABASE_URL:
85 DATABASES = {
86 "default": dj_database_url.parse(
87 DATABASE_URL,
88 conn_max_age=60,
89 conn_health_checks=True,
90 )
91 }
92else:
93 # SQLite fallback (existing behavior)
94 DATABASE_PATH = os.environ.get("DATABASE_PATH", str(BASE_DIR / "db.sqlite3"))
95 DATABASES = {
96 "default": {
97 "ENGINE": "django.db.backends.sqlite3",
98 "NAME": DATABASE_PATH,
99 "OPTIONS": {
100 # Increase lock wait timeout from default 5s to 20s
101 "timeout": 20,
102 # Acquire write lock at transaction START (not mid-transaction)
103 # This prevents "database is locked" errors during concurrent writes
104 # by allowing failed lock acquisitions to be retried
105 "transaction_mode": "IMMEDIATE",
106 # PRAGMA settings applied on each new connection:
107 # - journal_mode=WAL: Allow concurrent reads during writes
108 # - synchronous=NORMAL: Safe for WAL mode, better performance
109 # - busy_timeout=5000: Wait up to 5s for locks at SQLite level
110 "init_command": ("PRAGMA journal_mode=WAL;PRAGMA synchronous=NORMAL;PRAGMA busy_timeout=5000;"),
111 },
112 }
113 }
115LANGUAGE_CODE = "en-us"
116TIME_ZONE = "UTC"
117USE_I18N = True
118USE_TZ = True
120STATIC_URL = "static/"
121STATIC_ROOT = BASE_DIR / "staticfiles"
123# Include built frontend assets in static files (only if directory exists)
124_frontend_dist = BASE_DIR / "frontend" / "dist"
125STATICFILES_DIRS = [_frontend_dist] if _frontend_dist.exists() else []
127# WhiteNoise configuration for efficient static file serving
128STORAGES = {
129 "default": {
130 "BACKEND": "django.core.files.storage.FileSystemStorage",
131 },
132 "staticfiles": {
133 "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
134 },
135}
137MEDIA_URL = "/media/"
138data_dir = os.environ.get("DATA_DIR", str(BASE_DIR))
139MEDIA_ROOT = Path(data_dir) / "data" / "media" if not DEBUG else BASE_DIR / "media"
141DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
143# Session settings
144SESSION_ENGINE = "django.contrib.sessions.backends.db"
145SESSION_COOKIE_AGE = 43200 # 12 hours
147# Logging configuration
148LOGGING = {
149 "version": 1,
150 "disable_existing_loggers": False,
151 "formatters": {
152 "verbose": {
153 "format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
154 "style": "{",
155 },
156 "simple": {
157 "format": "{levelname} {message}",
158 "style": "{",
159 },
160 },
161 "handlers": {
162 "console": {
163 "class": "logging.StreamHandler",
164 "formatter": "verbose",
165 },
166 },
167 "loggers": {
168 "apps.recipes": {
169 "handlers": ["console"],
170 "level": "INFO",
171 "propagate": False,
172 },
173 "apps.recipes.services": {
174 "handlers": ["console"],
175 "level": "INFO",
176 "propagate": False,
177 },
178 "apps.ai": {
179 "handlers": ["console"],
180 "level": "INFO",
181 "propagate": False,
182 },
183 },
184 "root": {
185 "handlers": ["console"],
186 "level": "WARNING",
187 },
188}