diff --git a/requirements.txt b/requirements.txt index 7620b29..013f4b1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,5 +13,5 @@ gunicorn # WSGI HTTP Server for production # Environment variable management python-decouple # Manage environment variables and settings -# Optional: Static files serving in production -# whitenoise # Serve static files efficiently +# Static files serving in production/staging +whitenoise==6.6.0 # Serve static files efficiently with compression diff --git a/tienda_ilusion/config/settings/base.py b/tienda_ilusion/config/settings/base.py index ae7ea4e..19b1541 100644 --- a/tienda_ilusion/config/settings/base.py +++ b/tienda_ilusion/config/settings/base.py @@ -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" diff --git a/tienda_ilusion/config/settings/development.py b/tienda_ilusion/config/settings/development.py index ef0705c..f271d51 100644 --- a/tienda_ilusion/config/settings/development.py +++ b/tienda_ilusion/config/settings/development.py @@ -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, diff --git a/tienda_ilusion/config/settings/production.py b/tienda_ilusion/config/settings/production.py index 66c965e..668b1c0 100644 --- a/tienda_ilusion/config/settings/production.py +++ b/tienda_ilusion/config/settings/production.py @@ -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 = { diff --git a/tienda_ilusion/config/settings/staging.py b/tienda_ilusion/config/settings/staging.py new file mode 100644 index 0000000..651efb1 --- /dev/null +++ b/tienda_ilusion/config/settings/staging.py @@ -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