-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsettings.py
More file actions
276 lines (224 loc) · 8.83 KB
/
Copy pathsettings.py
File metadata and controls
276 lines (224 loc) · 8.83 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
"""
Django settings for the textsummerizer project.
Configuration is read from environment variables (see ``.env.example``).
The defaults are chosen so that ``python manage.py runserver`` works on a
fresh clone with no database server and no ``.env`` file, while production
deployments fail loudly rather than silently falling back to insecure values.
Note on naming: the project package ``textsummerizer`` and the app package
``textApp`` are misspelled / non-PEP8, but both names are baked into the
existing migration history (``('textApp', '0001_initial')``) and into
``DJANGO_SETTINGS_MODULE``. Renaming them would break every deployed
database, so the names are kept deliberately and the lint warnings are
suppressed in ``.pylintrc`` instead.
"""
import os
from pathlib import Path
from django.core.exceptions import ImproperlyConfigured
from dotenv import load_dotenv
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
load_dotenv(BASE_DIR / ".env")
def _env_bool(name, default=False):
"""Read a boolean from the environment, accepting the usual spellings."""
raw = os.environ.get(name)
if raw is None:
return default
return raw.strip().lower() in {"1", "true", "yes", "on"}
def _env_list(name, default=()):
"""Read a comma-separated list from the environment."""
raw = os.environ.get(name)
if not raw:
return list(default)
return [item.strip() for item in raw.split(",") if item.strip()]
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = _env_bool("DJANGO_DEBUG", default=False)
# SECURITY WARNING: keep the secret key used in production secret!
# A throwaway development key is only acceptable while DEBUG is on; a
# production process with no DJANGO_SECRET_KEY must refuse to start.
SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY")
if not SECRET_KEY:
if not DEBUG:
raise ImproperlyConfigured(
"DJANGO_SECRET_KEY is not set. Generate one with:\n"
" python -c \"from django.core.management.utils import "
'get_random_secret_key as k; print(k())"'
)
SECRET_KEY = "django-insecure-development-key-do-not-use-in-production"
# Salt for hashing client IP addresses before they are stored. Falls back to
# SECRET_KEY so there is always a value; set it explicitly if you rotate
# SECRET_KEY and want existing hashes to stay comparable.
IP_HASH_SALT = os.environ.get("IP_HASH_SALT") or SECRET_KEY
ALLOWED_HOSTS = _env_list("DJANGO_ALLOWED_HOSTS")
if not ALLOWED_HOSTS:
if not DEBUG:
raise ImproperlyConfigured(
"DJANGO_ALLOWED_HOSTS must list the hostnames this site serves "
"when DJANGO_DEBUG is off (comma-separated, e.g. "
"'example.com,www.example.com')."
)
ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]"]
# Groq API credentials. Read here so that a missing key is reported by
# `manage.py check` in production rather than as a confusing 500 at runtime.
GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
GROQ_MODEL = os.environ.get("GROQ_MODEL", "openai/gpt-oss-20b")
if not GROQ_API_KEY and not DEBUG:
raise ImproperlyConfigured(
"GROQ_API_KEY is not set. Summarization cannot work without it. "
"Get a key at https://console.groq.com/keys"
)
# reCAPTCHA. When the secret is unset the contact form rejects submissions
# rather than accepting unverified ones.
RECAPTCHA_VERIFY_URL = os.environ.get(
"RECAPTCHA_VERIFY_URL", "https://www.google.com/recaptcha/api/siteverify"
)
RECAPTCHA_SECRET_KEY = os.environ.get("RECAPTCHA_SECRET_KEY", "")
# Cross-origin access for the Vite dev server / deployed frontend.
CORS_ALLOWED_ORIGINS = _env_list(
"CORS_ALLOWED_ORIGINS", default=["http://localhost:5173"]
)
CORS_ALLOW_CREDENTIALS = True
CSRF_TRUSTED_ORIGINS = _env_list("CSRF_TRUSTED_ORIGINS", default=CORS_ALLOWED_ORIGINS)
# Maximum size of an uploaded document, in bytes.
MAX_UPLOAD_SIZE = int(os.environ.get("MAX_UPLOAD_SIZE", 5 * 1024 * 1024))
# Whether to persist a log row (hashed IP, submitted text, sentiment) for
# each summarization request. See the privacy section of the README.
ENABLE_REQUEST_LOGGING = _env_bool("ENABLE_REQUEST_LOGGING", default=True)
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"corsheaders",
"rest_framework",
"textApp",
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.security.SecurityMiddleware",
"whitenoise.middleware.WhiteNoiseMiddleware",
"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 = "textsummerizer.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 = "textsummerizer.wsgi.application"
# Database
# Defaults to a file-backed SQLite database so a fresh clone runs with no
# database server. Set DB_ENGINE (plus the DB_* vars) to use PostgreSQL or
# MySQL instead.
DB_ENGINE = os.environ.get("DB_ENGINE", "django.db.backends.sqlite3")
if DB_ENGINE.endswith("sqlite3"):
DATABASES = {
"default": {
"ENGINE": DB_ENGINE,
"NAME": os.environ.get("DB_NAME") or str(BASE_DIR / "db.sqlite3"),
}
}
else:
_missing = [
var
for var in ("DB_NAME", "DB_USER", "DB_HOST")
if not os.environ.get(var)
]
if _missing:
raise ImproperlyConfigured(
f"DB_ENGINE is set to {DB_ENGINE!r} but these required variables "
f"are missing: {', '.join(_missing)}."
)
DATABASES = {
"default": {
"ENGINE": DB_ENGINE,
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
"HOST": os.environ["DB_HOST"],
"PORT": os.environ.get("DB_PORT", ""),
}
}
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": ["rest_framework.throttling.AnonRateThrottle"],
"DEFAULT_THROTTLE_RATES": {
"anon": os.environ.get("API_THROTTLE_RATE", "30/minute"),
},
}
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
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",
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
STATIC_URL = "/static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {
"BACKEND": "django.core.files.storage.FileSystemStorage",
},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# Security hardening. Only enabled outside DEBUG so local HTTP development
# keeps working.
if not DEBUG:
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
SECURE_SSL_REDIRECT = _env_bool("SECURE_SSL_REDIRECT", default=True)
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = int(os.environ.get("SECURE_HSTS_SECONDS", 31536000))
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = "DENY"
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("LOG_LEVEL", "INFO")},
}
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"