mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2025-12-24 19:31:09 -05:00
Add secret management guide, client-side performance monitoring, and search accessibility enhancements
- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols. - Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage. - Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
@@ -1,28 +1,34 @@
|
||||
"""
|
||||
Production settings for thrillwiki project.
|
||||
|
||||
This module extends base.py with production-specific configurations:
|
||||
- Debug mode disabled
|
||||
- Strict security settings (HSTS, secure cookies, SSL redirect)
|
||||
- Redis caching (required in production)
|
||||
- Structured JSON logging
|
||||
- Production-optimized static file serving
|
||||
"""
|
||||
|
||||
# Import the module and use its members, e.g., base.BASE_DIR, base***REMOVED***
|
||||
from . import base
|
||||
from decouple import config
|
||||
from .base import * # noqa: F401,F403
|
||||
|
||||
# Import the module and use its members, e.g., database.DATABASES
|
||||
# =============================================================================
|
||||
# Production Core Settings
|
||||
# =============================================================================
|
||||
|
||||
# Import the module and use its members, e.g., email.EMAIL_HOST
|
||||
|
||||
# Import the module and use its members, e.g., security.SECURE_HSTS_SECONDS
|
||||
|
||||
# Import the module and use its members, e.g., email.EMAIL_HOST
|
||||
|
||||
# Import the module and use its members, e.g., security.SECURE_HSTS_SECONDS
|
||||
|
||||
# Production settings
|
||||
DEBUG = False
|
||||
|
||||
# Allowed hosts must be explicitly set in production
|
||||
ALLOWED_HOSTS = base.config("ALLOWED_HOSTS")
|
||||
ALLOWED_HOSTS = config(
|
||||
"ALLOWED_HOSTS",
|
||||
cast=lambda v: [s.strip() for s in v.split(",") if s.strip()]
|
||||
)
|
||||
|
||||
# CSRF trusted origins for production
|
||||
CSRF_TRUSTED_ORIGINS = base.config("CSRF_TRUSTED_ORIGINS")
|
||||
CSRF_TRUSTED_ORIGINS = config(
|
||||
"CSRF_TRUSTED_ORIGINS",
|
||||
cast=lambda v: [s.strip() for s in v.split(",") if s.strip()]
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# Security Settings for Production
|
||||
@@ -50,7 +56,86 @@ SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SECURE_REFERRER_POLICY = "strict-origin-when-cross-origin"
|
||||
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
|
||||
|
||||
# Production logging
|
||||
# Proxy SSL header (for reverse proxies like nginx)
|
||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
# =============================================================================
|
||||
# Production Cache Configuration (Redis Required)
|
||||
# =============================================================================
|
||||
|
||||
redis_url = config("REDIS_URL", default=None)
|
||||
if redis_url:
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": redis_url,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
"PARSER_CLASS": "redis.connection.HiredisParser",
|
||||
"CONNECTION_POOL_CLASS": "redis.BlockingConnectionPool",
|
||||
"CONNECTION_POOL_CLASS_KWARGS": {
|
||||
"max_connections": config(
|
||||
"REDIS_MAX_CONNECTIONS", default=100, cast=int
|
||||
),
|
||||
"timeout": 20,
|
||||
"socket_keepalive": True,
|
||||
"retry_on_timeout": True,
|
||||
},
|
||||
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
|
||||
"IGNORE_EXCEPTIONS": False, # Fail loudly in production
|
||||
},
|
||||
"KEY_PREFIX": "thrillwiki",
|
||||
},
|
||||
"sessions": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": config("REDIS_SESSIONS_URL", default=redis_url),
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
"PARSER_CLASS": "redis.connection.HiredisParser",
|
||||
},
|
||||
"KEY_PREFIX": "sessions",
|
||||
},
|
||||
"api": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": config("REDIS_API_URL", default=redis_url),
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
"PARSER_CLASS": "redis.connection.HiredisParser",
|
||||
"COMPRESSOR": "django_redis.compressors.zlib.ZlibCompressor",
|
||||
},
|
||||
"KEY_PREFIX": "api",
|
||||
},
|
||||
}
|
||||
|
||||
# Use Redis for sessions in production
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "sessions"
|
||||
|
||||
# =============================================================================
|
||||
# Production Static Files Configuration
|
||||
# =============================================================================
|
||||
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
|
||||
# Update STORAGES for Django 4.2+
|
||||
STORAGES["staticfiles"]["BACKEND"] = ( # noqa: F405
|
||||
"whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
)
|
||||
|
||||
# =============================================================================
|
||||
# Production REST Framework Settings
|
||||
# =============================================================================
|
||||
# Only JSON renderer in production (no browsable API)
|
||||
|
||||
REST_FRAMEWORK["DEFAULT_RENDERER_CLASSES"] = [ # noqa: F405
|
||||
"rest_framework.renderers.JSONRenderer",
|
||||
]
|
||||
|
||||
# =============================================================================
|
||||
# Production Logging Configuration
|
||||
# =============================================================================
|
||||
# Structured JSON logging for log aggregation services
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
@@ -59,69 +144,121 @@ LOGGING = {
|
||||
"format": "{levelname} {asctime} {module} {process:d} {thread:d} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
"json": {
|
||||
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
|
||||
"format": (
|
||||
"%(levelname)s %(asctime)s %(module)s %(process)d "
|
||||
"%(thread)d %(message)s %(pathname)s %(lineno)d"
|
||||
),
|
||||
},
|
||||
"simple": {
|
||||
"format": "{levelname} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
},
|
||||
"filters": {
|
||||
"require_debug_false": {
|
||||
"()": "django.utils.log.RequireDebugFalse",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "json", # JSON for container environments
|
||||
},
|
||||
"file": {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": base.BASE_DIR / "logs" / "django.log",
|
||||
"filename": BASE_DIR / "logs" / "django.log", # noqa: F405
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 10,
|
||||
"formatter": "verbose",
|
||||
"formatter": "json",
|
||||
},
|
||||
"error_file": {
|
||||
"level": "ERROR",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": base.BASE_DIR / "logs" / "django_error.log",
|
||||
"filename": BASE_DIR / "logs" / "django_error.log", # noqa: F405
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 10,
|
||||
"formatter": "verbose",
|
||||
"formatter": "json",
|
||||
},
|
||||
"security_file": {
|
||||
"level": "INFO",
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": BASE_DIR / "logs" / "security.log", # noqa: F405
|
||||
"maxBytes": 1024 * 1024 * 10, # 10MB
|
||||
"backupCount": 10,
|
||||
"formatter": "json",
|
||||
},
|
||||
"mail_admins": {
|
||||
"level": "ERROR",
|
||||
"filters": ["require_debug_false"],
|
||||
"class": "django.utils.log.AdminEmailHandler",
|
||||
"include_html": True,
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["file"],
|
||||
"handlers": ["console", "file"],
|
||||
"level": "INFO",
|
||||
},
|
||||
"loggers": {
|
||||
"django": {
|
||||
"handlers": ["file", "error_file"],
|
||||
"handlers": ["console", "file", "error_file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"django.request": {
|
||||
"handlers": ["console", "error_file", "mail_admins"],
|
||||
"level": "ERROR",
|
||||
"propagate": False,
|
||||
},
|
||||
"django.security": {
|
||||
"handlers": ["console", "security_file"],
|
||||
"level": "WARNING",
|
||||
"propagate": False,
|
||||
},
|
||||
"thrillwiki": {
|
||||
"handlers": ["file", "error_file"],
|
||||
"handlers": ["console", "file", "error_file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"security": {
|
||||
"handlers": ["console", "security_file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"celery": {
|
||||
"handlers": ["console", "file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Static files collection for production
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
# =============================================================================
|
||||
# Sentry Integration (Optional)
|
||||
# =============================================================================
|
||||
# Configure Sentry for error tracking in production
|
||||
|
||||
# Cache settings for production (Redis recommended)
|
||||
redis_url = base.config("REDIS_URL", default=None)
|
||||
if redis_url:
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": redis_url,
|
||||
"OPTIONS": {
|
||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
||||
},
|
||||
}
|
||||
}
|
||||
SENTRY_DSN = config("SENTRY_DSN", default="")
|
||||
|
||||
# Use Redis for sessions in production
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
|
||||
SESSION_CACHE_ALIAS = "default"
|
||||
if SENTRY_DSN:
|
||||
import sentry_sdk
|
||||
from sentry_sdk.integrations.django import DjangoIntegration
|
||||
from sentry_sdk.integrations.celery import CeleryIntegration
|
||||
from sentry_sdk.integrations.redis import RedisIntegration
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_RENDERER_CLASSES": [
|
||||
"rest_framework.renderers.JSONRenderer",
|
||||
],
|
||||
}
|
||||
sentry_sdk.init(
|
||||
dsn=SENTRY_DSN,
|
||||
integrations=[
|
||||
DjangoIntegration(),
|
||||
CeleryIntegration(),
|
||||
RedisIntegration(),
|
||||
],
|
||||
environment=config("SENTRY_ENVIRONMENT", default="production"),
|
||||
traces_sample_rate=config(
|
||||
"SENTRY_TRACES_SAMPLE_RATE", default=0.1, cast=float
|
||||
),
|
||||
send_default_pii=False, # Don't send PII to Sentry
|
||||
attach_stacktrace=True,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user