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