Módulo 4 · Lección 40 de 45
Configuración por entornos
La clave secreta que está en el repositorio de medio internet, partir los ajustes en tres ficheros, y el check --deploy que dice ocho cosas.
Hay un fichero de este proyecto que lleva desde la lección 4 con un problema a la vista, y con un comentario de Django avisando:
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-#y$m&pphoplwy&m+6x)i$wkiq-v&73))1658kb)j==c69666@5'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []Tres ajustes, y los tres tienen que valer una cosa en tu portátil y otra en el servidor. Mientras hay un solo settings.py, eso solo se puede resolver cambiándolo a mano antes de desplegar, que es exactamente como se despliega con DEBUG = True puesto.
check --deploy#
Antes de arreglar nada, conviene ver el diagnóstico. Django lo trae hecho:
python manage.py check --deployERRORS:
?: (mail.E001) Your MAILERS setting uses a development-only email backend
in the 'default' entry (django.core.mail.backends.console.EmailBackend).
WARNINGS:
?: (security.W004) You have not set a value for the SECURE_HSTS_SECONDS setting...
?: (security.W008) Your SECURE_SSL_REDIRECT setting is not set to True...
?: (security.W009) Your SECRET_KEY has less than 50 characters, less than 5
unique characters, or it's prefixed with 'django-insecure-'...
?: (security.W012) SESSION_COOKIE_SECURE is not set to True...
?: (security.W016) ... you have not set CSRF_COOKIE_SECURE to True...
?: (security.W018) You should not have DEBUG set to True in deployment.
?: (security.W020) ALLOWED_HOSTS must not be empty in deployment.
System check identified 8 issues (0 silenced).Ocho avisos sobre un proyecto recién creado, y cada uno señala algo real. Es la lista de tareas de esta lección y de la 42, y la orden es sencilla: antes de desplegar, esto tiene que salir limpio.
Fíjate en que el primero es un error, no un aviso, y es nuevo de Django 6.1: como ahora startproject deja el correo de consola configurado, la comprobación se asegura de que nadie lo deje así en producción, donde significaría que los correos no salen. Antes eso no se podía detectar.
Lo que hace --deploy es activar comprobaciones que están apagadas en desarrollo, porque quejarse de DEBUG = True mientras desarrollas no tendría sentido.
Las variables de entorno#
La regla es la de siempre: lo que cambia según la máquina, y lo que es secreto, no va en el código. Va en el entorno, y el código lo lee.
import os
SECRET_KEY = os.environ.get('CLAVE_SECRETA', 'insegura-solo-para-desarrollo')Esto resuelve el problema de raíz. La clave de producción nunca existe en el repositorio: vive en las variables de entorno del servidor, y el código solo sabe cómo se llama.
Para generar una, Django trae la misma función que usa startproject:
from django.core.management.utils import get_random_secret_key8*q+e97=)^3xlb%z$^q%yd%lktm*(abdy%^=-v9)_54!9&89@a
longitud: 50Y una advertencia sobre esa clave, porque no es solo "un secreto más": de ella salen las firmas de las sesiones, los testigos de recuperación de contraseña de la lección 38 y los testigos CSRF. Quien la tenga puede entrar como cualquier usuario. Por eso --deploy se queja hasta de que sea corta.
Partir los ajustes#
Un fichero con tres if dentro se vuelve ilegible enseguida. Lo que se hace es un paquete:
cfg/
ajustes/
__init__.py
base.py lo común
desarrollo.py lo de tu portátil
produccion.py lo del servidorbase.py es el settings.py de siempre, con una corrección importante: como ha bajado un nivel, BASE_DIR tiene que subir uno más.
BASE_DIR = Path(__file__).resolve().parent.parent.parentOlvidarse de eso es el fallo número uno de esta reorganización, y no da error: deja la base de datos, los estáticos y los ficheros subidos una carpeta más adentro de donde deberían, y te enteras cuando algo no aparece.
from .base import * # noqa: F403
DEBUG = True
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
INSTALLED_APPS += ['django.contrib.humanize']import os
from .base import * # noqa: F403
DEBUG = False
SECRET_KEY = os.environ['CLAVE_SECRETA']
ALLOWED_HOSTS = [h for h in os.environ.get('HOSTS_PERMITIDOS', '').split(',') if h]
MAILERS = {
'default': {
'BACKEND': 'django.core.mail.backends.smtp.EmailBackend',
'OPTIONS': {'host': os.environ['SMTP_HOST'], 'timeout': 10},
},
}
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
STATIC_ROOT = BASE_DIR / 'estaticos_recogidos'desarrollo: System check identified no issues.
producción, sin CLAVE_SECRETA: KeyError: 'CLAVE_SECRETA'
producción, con todo puesto: System check identified no issues (0 silenced).Los ocho avisos de --deploy, a cero. Y el KeyError del medio es un acierto, no un fallo: el proyecto se niega a arrancar sin el secreto, en lugar de arrancar mal.
Ese import *, que en cualquier otro sitio sería mala idea, aquí es la convención y tiene sentido, porque un módulo de ajustes no es código normal: es una lista de nombres en mayúsculas que Django va a leer. El único detalle a recordar es que import * no trae los nombres que empiezan por guion bajo, así que los auxiliares que no quieras propagar se nombran así.
Elegir el módulo#
Hay dos sitios donde se decide, y conviene entender la diferencia.
En manage.py, que es lo que usas a diario:
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'cfg.ajustes.desarrollo')Ese setdefault es la clave: solo pone el valor si no estaba. Así, en el servidor basta con exportar la variable y manage.py la respeta.
DJANGO_SETTINGS_MODULE=cfg.ajustes.produccion python manage.py migrate
python manage.py check --settings=cfg.ajustes.produccionY en wsgi.py y asgi.py, que son los que usa el servidor de verdad. Es la parte que se olvida: se cambia manage.py, se prueba, y en producción el servidor sigue cargando los ajustes de desarrollo porque nadie tocó wsgi.py. Es la lección 43.