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

1""" 

2Django settings for blogModel project. 

3 

4Generated by 'django-admin startproject' using Django 5.0.3. 

5 

6For more information on this file, see 

7https://docs.djangoproject.com/en/5.0/topics/settings/ 

8 

9For the full list of settings and their values, see 

10https://docs.djangoproject.com/en/5.0/ref/settings/ 

11""" 

12 

13import os 

14import sys 

15from pathlib import Path 

16 

17# Build paths inside the project like this: BASE_DIR / 'subdir'. 

18BASE_DIR = Path(__file__).resolve().parent.parent.parent 

19 

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" 

24 

25# Quick-start development settings - unsuitable for production 

26# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ 

27 

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 ) 

37 

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 ) 

49 

50_raw_hosts = os.environ.get("DJANGO_ALLOWED_HOSTS", "example.com") 

51ALLOWED_HOSTS = [h.strip() for h in _raw_hosts.split(",") if h.strip()] 

52 

53# Application definition 

54 

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] 

67 

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] 

79 

80MESSAGE_STORAGE = "django.contrib.messages.storage.cookie.CookieStorage" 

81 

82ROOT_URLCONF = "blogModel.urls" 

83 

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] 

99 

100WSGI_APPLICATION = "blogModel.wsgi.application" 

101 

102 

103# Database 

104# https://docs.djangoproject.com/en/5.0/ref/settings/#databases 

105 

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 } 

120 

121 

122# Password validation 

123# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators 

124 

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] 

142 

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) 

150 

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] 

159 

160AUTH_USER_MODEL = "website.User" 

161 

162PHONENUMBER_DEFAULT_REGION = "BR" 

163 

164 

165# Internationalization 

166# https://docs.djangoproject.com/en/5.0/topics/i18n/ 

167 

168LANGUAGE_CODE = "pt-br" 

169 

170TIME_ZONE = "America/Sao_Paulo" 

171 

172USE_I18N = True 

173 

174USE_TZ = True 

175 

176 

177# Static files (CSS, JavaScript, Images) 

178# https://docs.djangoproject.com/en/5.0/howto/static-files/ 

179 

180STATIC_URL = "website/static/" 

181STATIC_ROOT = os.path.join(BASE_DIR, "static") 

182STATICFILES_DIRS = (os.path.join(BASE_DIR, "website/static"),) 

183 

184MEDIA_URL = "/media/" 

185MEDIA_ROOT = os.path.join(BASE_DIR, "media") 

186 

187# Default primary key field type 

188# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field 

189 

190DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 

191 

192 

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} 

216 

217# Include nonces for script-src so templates can use {% csp_nonce %} 

218CSP_INCLUDE_NONCE_IN = ("script-src",) 

219 

220if not DEBUG: 

221 # Cookies 

222 SESSION_COOKIE_SECURE = True 

223 CSRF_COOKIE_SECURE = True 

224 SESSION_COOKIE_HTTPONLY = True 

225 

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 

230 

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") 

236 

237 # XSS / clickjacking 

238 SECURE_BROWSER_XSS_FILTER = True 

239 X_FRAME_OPTIONS = "DENY" 

240 

241 # Referrer policy 

242 SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"