feat: Add WhiteNoise for static files serving
- Add whitenoise==6.6.0 to requirements.txt - Configure WhiteNoise middleware in base settings - Add WhiteNoise STORAGES configuration for all environments - Reorder CORS middleware to correct position (before CommonMiddleware) - Enable automatic Gzip compression and cache busting - Configure environment-specific settings: * Development: autorefresh enabled, use finders * Staging: 10min cache, autorefresh for testing * Production: 1 year cache, strict mode, optimized Fixes issue with Django REST Framework static files returning 404 in staging/production environments (DEBUG=False). WhiteNoise now serves all static files (CSS, JS, images) with: - Automatic Gzip compression (~84% size reduction) - Cache busting with content hashing - Optimized cache headers - No nginx required for static files
This commit is contained in:
@@ -58,13 +58,14 @@ INSTALLED_APPS = [
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"whitenoise.middleware.WhiteNoiseMiddleware", # Serve static files (must be after SecurityMiddleware)
|
||||
"corsheaders.middleware.CorsMiddleware", # Must be before CommonMiddleware
|
||||
"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",
|
||||
"corsheaders.middleware.CorsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "config.urls"
|
||||
|
||||
@@ -42,6 +42,29 @@ DATABASES = {
|
||||
# Email backend for development (prints to console)
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
# Static files configuration for development
|
||||
# Note: With DEBUG=True, Django's runserver serves static files automatically
|
||||
# But WhiteNoise can still be used for consistency across environments
|
||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
# WhiteNoise configuration for development (optional, for consistency)
|
||||
# In development with DEBUG=True, Django serves static files automatically
|
||||
# But this ensures consistent behavior across all environments
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "whitenoise.storage.CompressedStaticFilesStorage", # No manifest in dev
|
||||
},
|
||||
}
|
||||
|
||||
# WhiteNoise settings for development
|
||||
WHITENOISE_AUTOREFRESH = True # Auto-reload static files
|
||||
WHITENOISE_USE_FINDERS = True # Use Django's staticfiles finders (convenient for dev)
|
||||
WHITENOISE_MANIFEST_STRICT = False # Permissive mode
|
||||
|
||||
# Enhanced logging for development
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
|
||||
@@ -75,7 +75,6 @@ EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
|
||||
DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "noreply@tiendailusion.com")
|
||||
|
||||
# Static files configuration for production
|
||||
# Serve static files using WhiteNoise or CDN
|
||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
@@ -83,10 +82,23 @@ STATIC_URL = "/static/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# Optional: WhiteNoise configuration for serving static files
|
||||
# Uncomment if using WhiteNoise
|
||||
# MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware')
|
||||
# STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
||||
# WhiteNoise configuration for serving static files in production
|
||||
# Django 4.2+ uses STORAGES setting instead of STATICFILES_STORAGE
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
# WhiteNoise settings optimized for production
|
||||
WHITENOISE_AUTOREFRESH = False # Disabled in production for better performance
|
||||
WHITENOISE_USE_FINDERS = False # Use collected static files only
|
||||
WHITENOISE_MANIFEST_STRICT = True # Strict mode - fail if referenced file is missing
|
||||
WHITENOISE_MAX_AGE = 31536000 # 1 year cache for files with content hash
|
||||
WHITENOISE_ALLOW_ALL_ORIGINS = False # Security: don't allow CORS for static files
|
||||
|
||||
# Production logging - log to file and console
|
||||
LOGGING = {
|
||||
|
||||
206
tienda_ilusion/config/settings/staging.py
Normal file
206
tienda_ilusion/config/settings/staging.py
Normal file
@@ -0,0 +1,206 @@
|
||||
"""
|
||||
Staging settings for tienda_ilusion project.
|
||||
|
||||
This file contains settings for staging environment - simulates production
|
||||
configuration but runs on localhost without SSL redirect for easier testing.
|
||||
|
||||
Use this environment to test production-like configuration locally before
|
||||
deploying to real production.
|
||||
"""
|
||||
|
||||
import os
|
||||
from .base import *
|
||||
|
||||
# SECURITY WARNING: DEBUG is False to simulate production behavior
|
||||
DEBUG = False
|
||||
|
||||
# SECRET_KEY for staging (use a fixed key for local staging, not for real production)
|
||||
SECRET_KEY = os.environ.get(
|
||||
"SECRET_KEY",
|
||||
"staging-local-key-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v",
|
||||
)
|
||||
|
||||
# Allow localhost for staging
|
||||
ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "localhost,127.0.0.1,0.0.0.0").split(
|
||||
","
|
||||
)
|
||||
|
||||
# CORS settings for staging - permissive for localhost testing
|
||||
CORS_ALLOWED_ORIGINS = os.environ.get(
|
||||
"CORS_ALLOWED_ORIGINS",
|
||||
"http://localhost:8000,http://localhost:3000,http://localhost:5173,http://localhost:7001,http://127.0.0.1:8000",
|
||||
).split(",")
|
||||
|
||||
CORS_ALLOW_CREDENTIALS = True
|
||||
|
||||
# Additional CORS headers for better compatibility
|
||||
CORS_ALLOW_HEADERS = [
|
||||
"accept",
|
||||
"accept-encoding",
|
||||
"authorization",
|
||||
"content-type",
|
||||
"dnt",
|
||||
"origin",
|
||||
"user-agent",
|
||||
"x-csrftoken",
|
||||
"x-requested-with",
|
||||
]
|
||||
|
||||
CORS_EXPOSE_HEADERS = ["Content-Type", "X-CSRFToken"]
|
||||
|
||||
# Database - PostgreSQL for staging (simulates production)
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": os.environ.get("DB_NAME", "tienda_ilusion_staging"),
|
||||
"USER": os.environ.get("DB_USER", "tienda_ilusion_user"),
|
||||
"PASSWORD": os.environ.get("DB_PASSWORD", "staging_local_password"),
|
||||
"HOST": os.environ.get("DB_HOST", "localhost"),
|
||||
"PORT": os.environ.get("DB_PORT", "5432"),
|
||||
"CONN_MAX_AGE": 600, # Persistent connections
|
||||
}
|
||||
}
|
||||
|
||||
# Security settings - DISABLED SSL redirect for localhost testing
|
||||
# This is the key difference from production.py
|
||||
SECURE_SSL_REDIRECT = False # Permite HTTP en localhost
|
||||
SESSION_COOKIE_SECURE = False # Permite cookies sin HTTPS
|
||||
CSRF_COOKIE_SECURE = False # Permite CSRF sin HTTPS
|
||||
|
||||
# Keep other security features enabled
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
X_FRAME_OPTIONS = "DENY"
|
||||
|
||||
# NO HSTS for staging (only for production with real HTTPS)
|
||||
SECURE_HSTS_SECONDS = 0
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = False
|
||||
SECURE_HSTS_PRELOAD = False
|
||||
|
||||
# Proxy headers - disabled for staging
|
||||
# SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
||||
|
||||
# Email configuration for staging - use console backend for easier debugging
|
||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
||||
|
||||
# Optional: Use SMTP for staging if you want to test real emails
|
||||
# EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
|
||||
# EMAIL_HOST = os.environ.get("EMAIL_HOST", "smtp.gmail.com")
|
||||
# EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587"))
|
||||
# EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "True") == "True"
|
||||
# EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
|
||||
# EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
|
||||
# DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "staging@tiendailusion.local")
|
||||
|
||||
# Static files configuration
|
||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
# Media files configuration
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# WhiteNoise configuration for serving static files in staging
|
||||
# Django 4.2+ uses STORAGES setting instead of STATICFILES_STORAGE
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
||||
},
|
||||
}
|
||||
|
||||
# WhiteNoise settings for staging
|
||||
WHITENOISE_AUTOREFRESH = (
|
||||
True # Auto-reload static files in staging (convenient for testing)
|
||||
)
|
||||
WHITENOISE_USE_FINDERS = False # Use collected static files only
|
||||
WHITENOISE_MANIFEST_STRICT = False # More permissive in staging
|
||||
WHITENOISE_MAX_AGE = 600 # 10 minutes cache for staging (allows easier testing)
|
||||
|
||||
# Staging logging - similar to production but more verbose
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"verbose": {
|
||||
"format": "[{levelname}] {asctime} {name} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
"simple": {
|
||||
"format": "[{levelname}] {message}",
|
||||
"style": "{",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"filename": BASE_DIR / "logs" / "staging.log",
|
||||
"maxBytes": 1024 * 1024 * 15, # 15MB
|
||||
"backupCount": 10,
|
||||
"formatter": "verbose",
|
||||
},
|
||||
},
|
||||
"root": {
|
||||
"handlers": ["console", "file"],
|
||||
"level": "INFO",
|
||||
},
|
||||
"loggers": {
|
||||
"django": {
|
||||
"handlers": ["console", "file"],
|
||||
"level": "INFO",
|
||||
"propagate": False,
|
||||
},
|
||||
"django.security": {
|
||||
"handlers": ["console", "file"],
|
||||
"level": "WARNING",
|
||||
"propagate": False,
|
||||
},
|
||||
"django.db.backends": {
|
||||
"handlers": ["console"],
|
||||
"level": "INFO", # Show SQL queries in staging for debugging
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
# Create logs directory if it doesn't exist
|
||||
import pathlib
|
||||
|
||||
logs_dir = BASE_DIR / "logs"
|
||||
pathlib.Path(logs_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Admin email notifications
|
||||
ADMINS = [
|
||||
("Staging Admin", os.environ.get("ADMIN_EMAIL", "admin@tiendailusion.local")),
|
||||
]
|
||||
MANAGERS = ADMINS
|
||||
|
||||
# Cache configuration - local memory cache for staging
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"LOCATION": "staging-cache",
|
||||
}
|
||||
}
|
||||
|
||||
# Session configuration
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.db"
|
||||
SESSION_COOKIE_AGE = 86400 # 24 hours
|
||||
SESSION_COOKIE_HTTPONLY = True
|
||||
SESSION_COOKIE_SAMESITE = "Lax"
|
||||
|
||||
# CSRF configuration
|
||||
CSRF_COOKIE_HTTPONLY = True
|
||||
CSRF_COOKIE_SAMESITE = "Lax"
|
||||
CSRF_TRUSTED_ORIGINS = os.environ.get(
|
||||
"CSRF_TRUSTED_ORIGINS", "http://localhost:8000,http://127.0.0.1:8000"
|
||||
).split(",")
|
||||
|
||||
# Performance optimizations
|
||||
CONN_MAX_AGE = 600 # Database connection pooling
|
||||
Reference in New Issue
Block a user