TL; DR: El truco es modificar os.environment
antes de importar settings/base.py
en cualquiera settings/<purpose>.py
, esto simplificará enormemente las cosas.
Solo pensar en todos estos archivos entrelazados me da dolor de cabeza. Combinar, importar (a veces condicionalmente), anular, parchear lo que ya estaba configurado en caso de que la DEBUG
configuración cambie más adelante. ¡Qué pesadilla!
A través de los años pasé por diferentes soluciones. Todos funcionan de alguna manera , pero son muy dolorosos de manejar. WTF! ¿Realmente necesitamos toda esa molestia? Comenzamos con solo un settings.py
archivo. ¡Ahora necesitamos una documentación solo para combinar correctamente todo esto en un orden correcto!
Espero finalmente alcanzar el (mi) punto dulce con la solución a continuación.
Recapitulemos los objetivos (algunos comunes, algunos míos)
Mantenga los secretos en secreto: ¡no los guarde en un repositorio!
Establecer / leer claves y secretos a través de la configuración del entorno, estilo de 12 factores .
Tener valores predeterminados razonables. Idealmente para el desarrollo local, no necesita nada más aparte de los valores predeterminados.
… Pero trate de mantener segura la producción predeterminada. Es mejor omitir una anulación de configuración localmente, que tener que recordar ajustar la configuración predeterminada de forma segura para la producción.
Tener la capacidad de encender DEBUG
/ apagar de una manera que pueda tener un efecto en otras configuraciones (por ejemplo, usando JavaScript comprimido o no).
El cambio entre configuraciones de propósito, como local / testing / puesta en escena / producción, debe basarse solo en DJANGO_SETTINGS_MODULE
nada más.
... pero permitir necesidad de parametrización a través de la configuración del entorno como DATABASE_URL
.
… También les permite usar configuraciones de propósito diferente y ejecutarlas localmente lado a lado, por ejemplo. configuración de producción en la máquina del desarrollador local, para acceder a la base de datos de producción o las hojas de estilo comprimidas de prueba de humo.
Falla si una variable de entorno no se establece explícitamente (lo que requiere un valor vacío como mínimo), especialmente en producción, por ejemplo. EMAIL_HOST_PASSWORD
.
Responda al DJANGO_SETTINGS_MODULE
conjunto predeterminado en manage.py durante el proyecto de inicio django-admin
Mantenga los condicionales al mínimo, si la condición es el tipo de entorno intencionado (por ejemplo, para el archivo de registro del conjunto de producción y su rotación), anule la configuración en el archivo de configuración intencionado asociado.
No
No permita que django lea la configuración DJANGO_SETTINGS_MODULE de un archivo.
Ugh! Piensa en lo meta que es esto. Si necesita tener un archivo (como docker env), léalo en el entorno antes de iniciar un proceso de django.
No anule DJANGO_SETTINGS_MODULE en su proyecto / código de aplicación, por ejemplo. basado en el nombre de host o el nombre del proceso.
Si tiene pereza para establecer una variable de entorno (como for setup.py test
), hágalo en herramientas justo antes de ejecutar el código del proyecto.
Evite la magia y los parches de cómo django lee su configuración, preprocese la configuración pero no interfiera después.
Sin complicaciones lógicas basadas en tonterías. La configuración debe ser fija y materializada, no calculada sobre la marcha. Proporcionar valores predeterminados alternativos es suficiente lógica aquí.
¿Realmente desea depurar? ¿Por qué localmente tiene la configuración correcta pero en producción en un servidor remoto, en una de cientos de máquinas, algo calculado de manera diferente? Oh! Pruebas unitarias? Para la configuración? ¿Seriamente?
Solución
Mi estrategia consiste en excelente django-Environ se utiliza con ini
archivos de estilo, que proporciona os.environment
valores por defecto para el desarrollo local, algunos mínimos y cortas settings/<purpose>.py
archivos que tienen una
import settings/base.py
DESPUÉS el os.environment
se estableció a partir de un INI
archivo. Esto efectivamente nos da un tipo de ajustes de inyección.
El truco aquí es modificar os.environment
antes de importar settings/base.py
.
Para ver el ejemplo completo, vaya al repositorio: https://github.com/wooyek/django-settings-strategy
.
│ manage.py
├───data
└───website
├───settings
│ │ __init__.py <-- imports local for compatibility
│ │ base.py <-- almost all the settings, reads from proces environment
│ │ local.py <-- a few modifications for local development
│ │ production.py <-- ideally is empty and everything is in base
│ │ testing.py <-- mimics production with a reasonable exeptions
│ │ .env <-- for local use, not kept in repo
│ __init__.py
│ urls.py
│ wsgi.py
settings / .env
A por defecto para el desarrollo local. Un archivo secreto, para establecer principalmente las variables de entorno requeridas. Póngalos en valores vacíos si no son necesarios en el desarrollo local. Proporcionamos valores predeterminados aquí y no settings/base.py
fallar en ninguna otra máquina si faltan en el entorno.
settings / local.py
Lo que sucede aquí es cargar el entorno desde settings/.env
y luego importar configuraciones comunes desde settings/base.py
. Después de eso, podemos anular algunos para facilitar el desarrollo local.
import logging
import environ
logging.debug("Settings loading: %s" % __file__)
# This will read missing environment variables from a file
# We wan to do this before loading a base settings as they may depend on environment
environ.Env.read_env(DEBUG='True')
from .base import *
ALLOWED_HOSTS += [
'127.0.0.1',
'localhost',
'.example.com',
'vagrant',
]
# https://docs.djangoproject.com/en/1.6/topics/email/#console-backend
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
LOGGING['handlers']['mail_admins']['email_backend'] = 'django.core.mail.backends.dummy.EmailBackend'
# Sync task testing
# http://docs.celeryproject.org/en/2.5/configuration.html?highlight=celery_always_eager#celery-always-eager
CELERY_ALWAYS_EAGER = True
CELERY_EAGER_PROPAGATES_EXCEPTIONS = True
settings / production.py
Para la producción no deberíamos esperar un archivo de entorno, pero es más fácil tener uno si estamos probando algo. Pero de todos modos, no proporcionamos algunos valores predeterminados en línea, por lo que settings/base.py
podemos responder en consecuencia.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
El principal punto de interés aquí son DEBUG
y ASSETS_DEBUG
reemplaza, se aplicarán a la pitón os.environ
SOLAMENTE si faltan del entorno y el archivo.
Estos serán nuestros valores predeterminados de producción, no es necesario ponerlos en el entorno o archivo, pero pueden anularse si es necesario. ¡Ordenado!
settings / base.py
Estas son sus configuraciones de django en su mayoría de vainilla, con algunos condicionales y muchas lecturas del entorno. Casi todo está aquí, manteniendo todos los entornos propuestos consistentes y lo más similares posible.
Las principales diferencias están a continuación (espero que se expliquen por sí mismas):
import environ
# https://github.com/joke2k/django-environ
env = environ.Env()
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
# Where BASE_DIR is a django source root, ROOT_DIR is a whole project root
# It may differ BASE_DIR for eg. when your django project code is in `src` folder
# This may help to separate python modules and *django apps* from other stuff
# like documentation, fixtures, docker settings
ROOT_DIR = BASE_DIR
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = env('DEBUG', default=False)
INTERNAL_IPS = [
'127.0.0.1',
]
ALLOWED_HOSTS = []
if 'ALLOWED_HOSTS' in os.environ:
hosts = os.environ['ALLOWED_HOSTS'].split(" ")
BASE_URL = "https://" + hosts[0]
for host in hosts:
host = host.strip()
if host:
ALLOWED_HOSTS.append(host)
SECURE_SSL_REDIRECT = env.bool('SECURE_SSL_REDIRECT', default=False)
# Database
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
if "DATABASE_URL" in os.environ: # pragma: no cover
# Enable database config through environment
DATABASES = {
# Raises ImproperlyConfigured exception if DATABASE_URL not in os.environ
'default': env.db(),
}
# Make sure we use have all settings we need
# DATABASES['default']['ENGINE'] = 'django.contrib.gis.db.backends.postgis'
DATABASES['default']['TEST'] = {'NAME': os.environ.get("DATABASE_TEST_NAME", None)}
DATABASES['default']['OPTIONS'] = {
'options': '-c search_path=gis,public,pg_catalog',
'sslmode': 'require',
}
else:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
# 'ENGINE': 'django.contrib.gis.db.backends.spatialite',
'NAME': os.path.join(ROOT_DIR, 'data', 'db.dev.sqlite3'),
'TEST': {
'NAME': os.path.join(ROOT_DIR, 'data', 'db.test.sqlite3'),
}
}
}
STATIC_ROOT = os.path.join(ROOT_DIR, 'static')
# django-assets
# http://django-assets.readthedocs.org/en/latest/settings.html
ASSETS_LOAD_PATH = STATIC_ROOT
ASSETS_ROOT = os.path.join(ROOT_DIR, 'assets', "compressed")
ASSETS_DEBUG = env('ASSETS_DEBUG', default=DEBUG) # Disable when testing compressed file in DEBUG mode
if ASSETS_DEBUG:
ASSETS_URL = STATIC_URL
ASSETS_MANIFEST = "json:{}".format(os.path.join(ASSETS_ROOT, "manifest.json"))
else:
ASSETS_URL = STATIC_URL + "assets/compressed/"
ASSETS_MANIFEST = "json:{}".format(os.path.join(STATIC_ROOT, 'assets', "compressed", "manifest.json"))
ASSETS_AUTO_BUILD = ASSETS_DEBUG
ASSETS_MODULES = ('website.assets',)
El último bit muestra el poder aquí. ASSETS_DEBUG
tiene un valor predeterminado sensible, que puede ser anulado settings/production.py
e incluso eso puede ser anulado por una configuración de entorno. ¡Hurra!
En efecto, tenemos una jerarquía mixta de importancia:
- settings / .py: establece los valores predeterminados según el propósito, no almacena secretos
- settings / base.py: está controlado principalmente por el entorno
- ajustes del entorno del proceso - 12 factor baby!
- settings / .env: valores predeterminados locales para un inicio fácil