2 Commits

Author SHA1 Message Date
bf69fe88d9 feat: Add staging environment for local production testing
- Add docker-compose.staging.yml with PostgreSQL and Django
- Add .env.staging.example with staging-specific environment variables
- Configure staging settings (DEBUG=False, no SSL redirect for localhost)
- Update settings/__init__.py to support staging environment detection
- Update AGENTS.md with staging environment documentation
- Update .gitignore to exclude .env.staging
- Optimize docker-compose.prod.yml configuration

Staging environment simulates production configuration locally:
- PostgreSQL database (port 5433 to avoid conflicts)
- Gunicorn with 4 workers
- DEBUG=False but HTTP allowed for easier testing
- Separate volumes for static files, media, and logs

Usage:
  cp .env.staging.example .env.staging
  docker compose -f docker-compose.staging.yml up -d
2026-05-10 20:32:42 -05:00
8818246870 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
2026-05-10 20:32:30 -05:00
12 changed files with 448 additions and 26 deletions

51
.env.staging.example Normal file
View File

@@ -0,0 +1,51 @@
# Staging Environment Variables
# Testing de producción en localhost sin SSL
# Este ambiente simula producción pero permite HTTP en localhost
# Django Environment
DJANGO_ENV=staging
# Debug mode (False para simular producción)
DEBUG=False
# Django Secret Key
# Para staging local, puedes usar una key fija (NO usar en producción real)
SECRET_KEY=staging-local-key-zh6rinl@8y7g(cf781snisx2j%p^c#d&b2@@9cqe!v@4yv8x=v
# Allowed hosts (localhost para testing)
ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0
# CORS allowed origins (permite acceso desde navegador local)
# Incluye común puertos de desarrollo de frontend y acceso directo
CORS_ALLOWED_ORIGINS=http://localhost:8000,http://localhost:3000,http://localhost:5173,http://localhost:7001,http://127.0.0.1:8000
# CSRF Trusted Origins (localhost HTTP para staging)
CSRF_TRUSTED_ORIGINS=http://localhost:8000,http://127.0.0.1:8000
# PostgreSQL Database Configuration (staging local)
DB_NAME=tienda_ilusion_staging
DB_USER=tienda_ilusion_user
DB_PASSWORD=staging_local_password
DB_HOST=postgres
DB_PORT=5432
# Email Configuration (console backend para staging)
EMAIL_HOST=smtp.gmail.com
EMAIL_PORT=587
EMAIL_USE_TLS=True
EMAIL_HOST_USER=staging@tiendailusion.local
EMAIL_HOST_PASSWORD=staging_password
DEFAULT_FROM_EMAIL=staging@tiendailusion.local
# Admin notifications
ADMIN_EMAIL=admin@tiendailusion.local
# Tryton ERP Configuration (Staging)
# Ajusta estos valores según tu entorno de staging de Tryton
TRYTON_HOST=localhost
TRYTON_DATABASE=tryton_staging
TRYTON_USERNAME=admin
TRYTON_PASSWORD=admin
# Staging specific: Disable SSL redirect for localhost testing
STAGING_DISABLE_SSL=True

1
.gitignore vendored
View File

@@ -363,3 +363,4 @@ pgdata/
# IDE-specific # IDE-specific
.vscode/ .vscode/
.idea/ .idea/
.env.staging

View File

@@ -8,9 +8,11 @@ Backend Django con Django REST Framework
don_confiao_backend/ don_confiao_backend/
├── requirements.txt # Dependencias Python ├── requirements.txt # Dependencias Python
├── docker-compose.dev.yml # Docker Compose para desarrollo ├── docker-compose.dev.yml # Docker Compose para desarrollo
├── docker-compose.staging.yml # Docker Compose para staging (testing producción local)
├── docker-compose.prod.yml # Docker Compose para producción ├── docker-compose.prod.yml # Docker Compose para producción
├── django.Dockerfile # Dockerfile Django ├── django.Dockerfile # Dockerfile Django
├── .env.development # Variables de entorno desarrollo ├── .env.development # Variables de entorno desarrollo
├── .env.staging # Variables de entorno staging
├── .env.production.example # Ejemplo variables de entorno producción ├── .env.production.example # Ejemplo variables de entorno producción
├── .env_example # Ejemplo de variables de entorno ├── .env_example # Ejemplo de variables de entorno
├── README.rst # Documentación básica ├── README.rst # Documentación básica
@@ -42,6 +44,7 @@ don_confiao_backend/
│ ├── __init__.py # Detección automática de ambiente │ ├── __init__.py # Detección automática de ambiente
│ ├── base.py # Configuración compartida │ ├── base.py # Configuración compartida
│ ├── development.py # Configuración desarrollo │ ├── development.py # Configuración desarrollo
│ ├── staging.py # Configuración staging
│ └── production.py # Configuración producción │ └── production.py # Configuración producción
├── urls.py ├── urls.py
├── wsgi.py ├── wsgi.py
@@ -86,20 +89,43 @@ El proyecto se ejecuta con docker-compose. Todos los comandos `manage.py` deben
```bash ```bash
# Ejecutar tests # Ejecutar tests
docker-compose -f docker-compose.dev.yml run --rm django python manage.py test docker compose -f docker-compose.dev.yml run --rm django python manage.py test
# Migraciones # Migraciones
docker-compose -f docker-compose.dev.yml run --rm django python manage.py makemigrations docker compose -f docker-compose.dev.yml run --rm django python manage.py makemigrations
docker-compose -f docker-compose.dev.yml run --rm django python manage.py migrate docker compose -f docker-compose.dev.yml run --rm django python manage.py migrate
# Servidor desarrollo # Servidor desarrollo
docker-compose -f docker-compose.dev.yml up docker compose -f docker-compose.dev.yml up
# Shell Django # Shell Django
docker-compose -f docker-compose.dev.yml run --rm django python manage.py shell docker compose -f docker-compose.dev.yml run --rm django python manage.py shell
# Crear superuser # Crear superuser
docker-compose -f docker-compose.dev.yml run --rm django python manage.py createsuperuser docker compose -f docker-compose.dev.yml run --rm django python manage.py createsuperuser
```
### Staging (Testing Producción Local)
```bash
# Iniciar servicios (PostgreSQL + Django con Gunicorn)
# Ya incluye el archivo .env.staging, listo para usar
docker compose -f docker-compose.staging.yml up -d
# Migraciones
docker compose -f docker-compose.staging.yml run --rm django python manage.py migrate
# Colectar archivos estáticos
docker compose -f docker-compose.staging.yml run --rm django python manage.py collectstatic --noinput
# Crear superuser
docker compose -f docker-compose.staging.yml run --rm django python manage.py createsuperuser
# Ver logs
docker compose -f docker-compose.staging.yml logs -f django
# Detener servicios
docker compose -f docker-compose.staging.yml down
``` ```
### Producción (Production) ### Producción (Production)
@@ -110,22 +136,22 @@ cp .env.production.example .env.production
# Editar .env.production con valores reales # Editar .env.production con valores reales
# Iniciar servicios (PostgreSQL + Django con Gunicorn) # Iniciar servicios (PostgreSQL + Django con Gunicorn)
docker-compose -f docker-compose.prod.yml up -d docker compose -f docker-compose.prod.yml up -d
# Migraciones # Migraciones
docker-compose -f docker-compose.prod.yml run --rm django python manage.py migrate docker compose -f docker-compose.prod.yml run --rm django python manage.py migrate
# Colectar archivos estáticos # Colectar archivos estáticos
docker-compose -f docker-compose.prod.yml run --rm django python manage.py collectstatic --noinput docker compose -f docker-compose.prod.yml run --rm django python manage.py collectstatic --noinput
# Crear superuser # Crear superuser
docker-compose -f docker-compose.prod.yml run --rm django python manage.py createsuperuser docker compose -f docker-compose.prod.yml run --rm django python manage.py createsuperuser
# Ver logs # Ver logs
docker-compose -f docker-compose.prod.yml logs -f django docker compose -f docker-compose.prod.yml logs -f django
# Detener servicios # Detener servicios
docker-compose -f docker-compose.prod.yml down docker compose -f docker-compose.prod.yml down
``` ```
Nota: El volumen monta `tienda_ilusion/` en `/app/`, por lo que el path correcto es `python manage.py` (no `python tienda_ilusion/manage.py`). Nota: El volumen monta `tienda_ilusion/` en `/app/`, por lo que el path correcto es `python manage.py` (no `python tienda_ilusion/manage.py`).
@@ -143,7 +169,7 @@ Nota: El volumen monta `tienda_ilusion/` en `/app/`, por lo que el path correcto
## Configuración de Ambientes ## Configuración de Ambientes
El proyecto soporta dos ambientes diferentes mediante la variable `DJANGO_ENV`: El proyecto soporta tres ambientes diferentes mediante la variable `DJANGO_ENV`:
### Development (desarrollo local) ### Development (desarrollo local)
- **Variable de ambiente**: `DJANGO_ENV=development` - **Variable de ambiente**: `DJANGO_ENV=development`
@@ -153,6 +179,16 @@ El proyecto soporta dos ambientes diferentes mediante la variable `DJANGO_ENV`:
- **CORS**: Permisivo para desarrollo local - **CORS**: Permisivo para desarrollo local
- **Docker Compose**: `docker-compose.dev.yml` - **Docker Compose**: `docker-compose.dev.yml`
### Staging (testing de producción local)
- **Variable de ambiente**: `DJANGO_ENV=staging`
- **Archivo de configuración**: `.env.staging`
- **Base de datos**: PostgreSQL (tienda_ilusion_staging)
- **DEBUG**: False (simula producción)
- **CORS**: Permisivo para localhost (sin SSL redirect)
- **Docker Compose**: `docker-compose.staging.yml`
- **Servidor**: Gunicorn con 4 workers
- **Puerto PostgreSQL**: 5433 (para no conflictuar con producción)
### Production (producción) ### Production (producción)
- **Variable de ambiente**: `DJANGO_ENV=production` - **Variable de ambiente**: `DJANGO_ENV=production`
- **Archivo de configuración**: `.env.production` (crear desde `.env.production.example`) - **Archivo de configuración**: `.env.production` (crear desde `.env.production.example`)
@@ -167,6 +203,7 @@ El proyecto soporta dos ambientes diferentes mediante la variable `DJANGO_ENV`:
La detección de ambiente es automática mediante la variable `DJANGO_ENV`. Docker Compose configura esta variable automáticamente según el archivo usado: La detección de ambiente es automática mediante la variable `DJANGO_ENV`. Docker Compose configura esta variable automáticamente según el archivo usado:
- `docker-compose.dev.yml` → usa `.env.development` → carga `settings/development.py` - `docker-compose.dev.yml` → usa `.env.development` → carga `settings/development.py`
- `docker-compose.staging.yml` → usa `.env.staging` → carga `settings/staging.py`
- `docker-compose.prod.yml` → usa `.env.production` → carga `settings/production.py` - `docker-compose.prod.yml` → usa `.env.production` → carga `settings/production.py`
### Variables de entorno requeridas ### Variables de entorno requeridas
@@ -176,6 +213,15 @@ La detección de ambiente es automática mediante la variable `DJANGO_ENV`. Dock
- `DEBUG=True` - `DEBUG=True`
- `TRYTON_HOST`, `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD` - `TRYTON_HOST`, `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD`
**Staging** (`.env.staging`):
- `DJANGO_ENV=staging`
- `DEBUG=False`
- `SECRET_KEY` (para staging local, puede ser una key fija)
- `ALLOWED_HOSTS=localhost,127.0.0.1,0.0.0.0`
- `CORS_ALLOWED_ORIGINS` (URLs localhost separadas por comas)
- `DB_NAME=tienda_ilusion_staging`, `DB_USER`, `DB_PASSWORD`, `DB_HOST=postgres`, `DB_PORT=5432`
- `TRYTON_HOST`, `TRYTON_DATABASE`, `TRYTON_USERNAME`, `TRYTON_PASSWORD`
**Producción** (`.env.production`): **Producción** (`.env.production`):
- `DJANGO_ENV=production` - `DJANGO_ENV=production`
- `DEBUG=False` - `DEBUG=False`

View File

@@ -4,5 +4,3 @@ WORKDIR /app/
COPY requirements.txt ./ COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
CMD ["python", "manage.py", "runserver", "0.0.0.0:9090"]

View File

@@ -18,7 +18,11 @@ services:
- tienda_network - tienda_network
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-tienda_ilusion_user}"] test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-tienda_ilusion_user} -d ${POSTGRES_DB:-tienda_ilusion_prod}",
]
interval: 10s interval: 10s
timeout: 5s timeout: 5s
retries: 5 retries: 5

View File

@@ -0,0 +1,73 @@
version: "3.8"
services:
postgres:
image: postgres:15-alpine
container_name: tienda_ilusion_postgres_staging
environment:
- POSTGRES_USER=${DB_USER:-tienda_ilusion_user}
- POSTGRES_DB=${DB_NAME:-tienda_ilusion_staging}
- POSTGRES_PASSWORD=${DB_PASSWORD:-staging_local_password}
env_file:
- .env.staging
volumes:
- postgres_staging_data:/var/lib/postgresql/data
ports:
- "5433:5432" # Puerto diferente para no conflictuar con prod
networks:
- tienda_staging_network
restart: unless-stopped
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${DB_USER:-tienda_ilusion_user} -d ${DB_PASSWORD:-tienda_ilusion_staging}",
]
interval: 10s
timeout: 5s
retries: 5
django:
build:
context: ./
dockerfile: django.Dockerfile
container_name: tienda_ilusion_django_staging
env_file:
- .env.staging
environment:
- DJANGO_ENV=staging
- DB_HOST=postgres
- DB_USER=${DB_USER:-tienda_ilusion_user}
- DB_NAME=${DB_NAME:-tienda_ilusion_staging}
- DB_PASSWORD=${DB_PASSWORD:-staging_local_password}
volumes:
- ./tienda_ilusion:/app/
- static_staging_volume:/app/staticfiles
- media_staging_volume:/app/media
- logs_staging_volume:/app/logs
ports:
- "8000:8000"
depends_on:
postgres:
condition: service_healthy
networks:
- tienda_staging_network
restart: unless-stopped
command: >
sh -c "python manage.py migrate &&
python manage.py collectstatic --noinput &&
gunicorn config.wsgi:application --bind 0.0.0.0:8000 --workers 4 --timeout 120 --reload"
volumes:
postgres_staging_data:
driver: local
static_staging_volume:
driver: local
media_staging_volume:
driver: local
logs_staging_volume:
driver: local
networks:
tienda_staging_network:
driver: bridge

View File

@@ -13,5 +13,5 @@ gunicorn # WSGI HTTP Server for production
# Environment variable management # Environment variable management
python-decouple # Manage environment variables and settings python-decouple # Manage environment variables and settings
# Optional: Static files serving in production # Static files serving in production/staging
# whitenoise # Serve static files efficiently whitenoise==6.6.0 # Serve static files efficiently with compression

View File

@@ -3,8 +3,9 @@ Settings module for tienda_ilusion project.
This module automatically loads the appropriate settings based on the DJANGO_ENV This module automatically loads the appropriate settings based on the DJANGO_ENV
environment variable. Valid values are: environment variable. Valid values are:
- 'development' (default): Development settings - 'development' (default): Development settings with SQLite
- 'production': Production settings - 'staging': Staging settings for testing production config locally (PostgreSQL, no SSL)
- 'production': Production settings with full security (PostgreSQL, SSL, etc.)
Usage: Usage:
Set DJANGO_ENV environment variable before running Django: Set DJANGO_ENV environment variable before running Django:
@@ -13,6 +14,10 @@ Usage:
export DJANGO_ENV=development export DJANGO_ENV=development
python manage.py runserver python manage.py runserver
Staging (test production locally):
export DJANGO_ENV=staging
python manage.py runserver
Production: Production:
export DJANGO_ENV=production export DJANGO_ENV=production
gunicorn config.wsgi:application gunicorn config.wsgi:application
@@ -25,6 +30,8 @@ DJANGO_ENV = os.environ.get("DJANGO_ENV", "development")
if DJANGO_ENV == "production": if DJANGO_ENV == "production":
from .production import * from .production import *
elif DJANGO_ENV == "staging":
from .staging import *
elif DJANGO_ENV == "development": elif DJANGO_ENV == "development":
from .development import * from .development import *
else: else:

View File

@@ -58,13 +58,14 @@ INSTALLED_APPS = [
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "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.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.common.CommonMiddleware", "django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware", "django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware", "django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware",
"corsheaders.middleware.CorsMiddleware",
] ]
ROOT_URLCONF = "config.urls" ROOT_URLCONF = "config.urls"

View File

@@ -42,6 +42,29 @@ DATABASES = {
# Email backend for development (prints to console) # Email backend for development (prints to console)
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" 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 # Enhanced logging for development
LOGGING = { LOGGING = {
"version": 1, "version": 1,

View File

@@ -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") DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "noreply@tiendailusion.com")
# Static files configuration for production # Static files configuration for production
# Serve static files using WhiteNoise or CDN
STATIC_ROOT = BASE_DIR / "staticfiles" STATIC_ROOT = BASE_DIR / "staticfiles"
STATIC_URL = "/static/" STATIC_URL = "/static/"
@@ -83,10 +82,23 @@ STATIC_URL = "/static/"
MEDIA_ROOT = BASE_DIR / "media" MEDIA_ROOT = BASE_DIR / "media"
MEDIA_URL = "/media/" MEDIA_URL = "/media/"
# Optional: WhiteNoise configuration for serving static files # WhiteNoise configuration for serving static files in production
# Uncomment if using WhiteNoise # Django 4.2+ uses STORAGES setting instead of STATICFILES_STORAGE
# MIDDLEWARE.insert(1, 'whitenoise.middleware.WhiteNoiseMiddleware') STORAGES = {
# STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' "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 # Production logging - log to file and console
LOGGING = { LOGGING = {

View 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