Coverage for blogModel/settings/settings.py: 72%
53 statements
« prev ^ index » next coverage.py v7.5.0, created at 2025-09-13 15:29 -0300
« prev ^ index » next coverage.py v7.5.0, created at 2025-09-13 15:29 -0300
1"""
2Django settings for blogModel project.
4Generated by 'django-admin startproject' using Django 5.0.3.
6For more information on this file, see
7https://docs.djangoproject.com/en/5.0/topics/settings/
9For the full list of settings and their values, see
10https://docs.djangoproject.com/en/5.0/ref/settings/
11"""
13import os
14import sys
15from pathlib import Path
17# Build paths inside the project like this: BASE_DIR / 'subdir'.
18BASE_DIR = Path(__file__).resolve().parent.parent.parent
20# SECURITY WARNING: don't run with debug turned on in production!
21# Use environment variable for DEBUG in production
22# In development, default to True
23DEBUG = os.environ.get("DJANGO_DEBUG", "False") == "True"
25# Quick-start development settings - unsuitable for production
26# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
28# SECURITY WARNING: keep the secret key used in production secret!
29# Use environment variable for SECRET_KEY in production
30# In development, use this fallback key
31SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", None)
32if not SECRET_KEY and DEBUG is not True:
33 print(SECRET_KEY, DEBUG)
34 raise RuntimeError(
35 "DJANGO_SECRET_KEY environment variable must be set in production"
36 )
38# Warn / fail if SECRET_KEY is weak in production
39if SECRET_KEY:
40 _weak_key = (
41 len(SECRET_KEY) < 50
42 or SECRET_KEY.startswith("django-insecure-")
43 or len(set(SECRET_KEY)) < 5
44 )
45 if _weak_key and DEBUG is not True:
46 raise RuntimeError(
47 "DJANGO_SECRET_KEY is too weak for production. Generate a secure, random value (>=50 chars)."
48 )
50_raw_hosts = os.environ.get("DJANGO_ALLOWED_HOSTS", "example.com")
51ALLOWED_HOSTS = [h.strip() for h in _raw_hosts.split(",") if h.strip()]
53# Application definition
55INSTALLED_APPS = [
56 "django.contrib.admin",
57 "django.contrib.auth",
58 "django.contrib.contenttypes",
59 "django.contrib.sessions",
60 "django.contrib.messages",
61 "django.contrib.staticfiles",
62 "website",
63 "bootstrap",
64 "phonenumber_field",
65 "csp",
66]
68MIDDLEWARE = [
69 "django.middleware.security.SecurityMiddleware",
70 "django.contrib.sessions.middleware.SessionMiddleware",
71 "django.middleware.common.CommonMiddleware",
72 "django.middleware.csrf.CsrfViewMiddleware",
73 "django.contrib.auth.middleware.AuthenticationMiddleware",
74 "django.contrib.messages.middleware.MessageMiddleware",
75 "csp.middleware.CSPMiddleware",
76 "django.middleware.clickjacking.XFrameOptionsMiddleware",
77 "website.middleware.Custom404Middleware",
78]
80MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage"
82ROOT_URLCONF = "blogModel.urls"
84TEMPLATES = [
85 {
86 "BACKEND": "django.template.backends.django.DjangoTemplates",
87 "DIRS": [BASE_DIR / "website/templates"],
88 "APP_DIRS": True,
89 "OPTIONS": {
90 "context_processors": [
91 "django.template.context_processors.debug",
92 "django.template.context_processors.request",
93 "django.contrib.auth.context_processors.auth",
94 "django.contrib.messages.context_processors.messages",
95 ],
96 },
97 },
98]
100WSGI_APPLICATION = "blogModel.wsgi.application"
103# Database
104# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
106if "test" in sys.argv:
107 DATABASES = {
108 "default": {
109 "ENGINE": "django.db.backends.sqlite3",
110 "NAME": BASE_DIR / "blogModel/settings/db.sqlite3",
111 }
112 }
113else:
114 DATABASES = {
115 "default": {
116 "ENGINE": "django.db.backends.sqlite3",
117 "NAME": BASE_DIR / "db.sqlite3",
118 }
119 }
122# Password validation
123# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
125AUTH_PASSWORD_VALIDATORS = [
126 {
127 "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
128 },
129 {
130 "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
131 "OPTIONS": {
132 "min_length": 10,
133 },
134 },
135 {
136 "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
137 },
138 {
139 "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
140 },
141]
143# Keep the project's legacy password checks available via Django's validator
144# framework so we can call `validate_password()` from views/forms while
145# preserving the original learning-oriented rules. The custom validator is
146# implemented in `website.validators.LegacyPasswordValidator`.
147AUTH_PASSWORD_VALIDATORS.insert(
148 1, {"NAME": "website.validators.LegacyPasswordValidator"}
149)
151# Prefer Argon2 for password hashing when available. Requires argon2-cffi in
152# the environment (already added to requirements.txt).
153PASSWORD_HASHERS = [
154 "django.contrib.auth.hashers.Argon2PasswordHasher",
155 "django.contrib.auth.hashers.PBKDF2PasswordHasher",
156 "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
157 "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
158]
160AUTH_USER_MODEL = "website.User"
162PHONENUMBER_DEFAULT_REGION = "BR"
165# Internationalization
166# https://docs.djangoproject.com/en/5.0/topics/i18n/
168LANGUAGE_CODE = "pt-br"
170TIME_ZONE = "America/Sao_Paulo"
172USE_I18N = True
174USE_TZ = True
177# Static files (CSS, JavaScript, Images)
178# https://docs.djangoproject.com/en/5.0/howto/static-files/
180STATIC_URL = "website/static/"
181STATIC_ROOT = os.path.join(BASE_DIR, "static")
182STATICFILES_DIRS = (os.path.join(BASE_DIR, "website/static"),)
184MEDIA_URL = "/media/"
185MEDIA_ROOT = os.path.join(BASE_DIR, "media")
187# Default primary key field type
188# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
190DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
193# Django-CSP >=4.0 configuration (new format)
194CONTENT_SECURITY_POLICY = {
195 "DIRECTIVES": {
196 "default-src": ("'self'",),
197 "style-src": ("'self'", "https://fonts.googleapis.com"),
198 "font-src": ("'self'", "https://fonts.gstatic.com"),
199 "script-src": (("'self'",)),
200 "img-src": (
201 "'self'",
202 "data:",
203 "https://i.ytimg.com",
204 "https://img.youtube.com",
205 "https://ytimg.googleusercontent.com",
206 ),
207 "frame-src": (
208 "'self'",
209 "https://www.youtube.com",
210 "https://youtube.com",
211 "https://www.youtube-nocookie.com",
212 "https://player.vimeo.com",
213 ),
214 }
215}
217# Include nonces for script-src so templates can use {% csp_nonce %}
218CSP_INCLUDE_NONCE_IN = ("script-src",)
220if not DEBUG:
221 # Cookies
222 SESSION_COOKIE_SECURE = True
223 CSRF_COOKIE_SECURE = True
224 SESSION_COOKIE_HTTPONLY = True
226 # HSTS
227 SECURE_HSTS_SECONDS = int(os.environ.get("SECURE_HSTS_SECONDS", 31536000))
228 SECURE_HSTS_INCLUDE_SUBDOMAINS = True
229 SECURE_HSTS_PRELOAD = True
231 # SSL redirect (only if you terminate SSL at Django or trust proxy)
232 SECURE_SSL_REDIRECT = (
233 False if DEBUG else True
234 ) # os.environ.get("SECURE_SSL_REDIRECT", "True") == "True"
235 SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
237 # XSS / clickjacking
238 SECURE_BROWSER_XSS_FILTER = True
239 X_FRAME_OPTIONS = "DENY"
241 # Referrer policy
242 SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"