TL; DR: O truque é modificar os.environment
antes de importar settings/base.py
em qualquer settings/<purpose>.py
, isso vai simplificar muito as coisas.
Só de pensar em todos esses arquivos entrelaçados me dá dor de cabeça. Combinando, importando (às vezes condicionalmente), substituindo, corrigindo o que já foi definido, caso a DEBUG
configuração seja alterada posteriormente. Que pesadelo!
Ao longo dos anos, passei por todas as soluções diferentes. Todos eles funcionam um pouco , mas são tão difíceis de gerenciar. WTF! Realmente precisamos de todo esse aborrecimento? Começamos com apenas um settings.py
arquivo. Agora precisamos de uma documentação apenas para combinar corretamente tudo isso em uma ordem correta!
Espero finalmente chegar ao (meu) ponto ideal com a solução abaixo.
Vamos recapitular os objetivos (alguns comuns, outros meus)
Mantenha segredos em segredo - não os guarde em um repositório!
Defina / leia chaves e segredos através das configurações do ambiente, estilo de 12 fatores .
Tenha padrões de fallback sensatos. Idealmente para o desenvolvimento local, você não precisa de mais nada além dos padrões.
… Mas tente manter a produção dos padrões segura. É melhor perder uma substituição de configuração localmente do que lembrar de ajustar as configurações padrão seguras para produção.
Tem a capacidade de ligar DEBUG
/ desligar de uma maneira que possa afetar outras configurações (por exemplo, usando javascript compactado ou não).
A alternância entre as configurações de finalidade, como local / teste / preparação / produção, deve basear-se apenas em DJANGO_SETTINGS_MODULE
nada mais.
… Mas permita parametrização adicional por meio de configurações do ambiente como DATABASE_URL
.
… Também permite que eles usem configurações de finalidade diferentes e as executem localmente lado a lado, por exemplo. configuração de produção na máquina local do desenvolvedor, para acessar o banco de dados de produção ou as folhas de estilo compactadas para teste de fumaça.
Falha se uma variável de ambiente não estiver definida explicitamente (exigindo um valor vazio no mínimo), especialmente na produção, por exemplo. EMAIL_HOST_PASSWORD
.
Responda ao padrão DJANGO_SETTINGS_MODULE
definido em manage.py durante o django-admin startproject
Mantenha as condições mínimas, se a condição for o tipo de ambiente proposto (por exemplo, para o arquivo de log do conjunto de produção e sua rotação), substitua as configurações no arquivo de configurações propostas associado.
Não
Não permita que o django leia a configuração DJANGO_SETTINGS_MODULE de um arquivo.
Ugh! Pense em como isso é meta. Se você precisar de um arquivo (como o docker env), leia-o no ambiente antes de iniciar um processo de django.
Não substitua DJANGO_SETTINGS_MODULE no código do seu projeto / aplicativo, por exemplo. com base no nome do host ou no nome do processo.
Se você tem preguiça de definir variáveis de ambiente (como para setup.py test
), faça-o em ferramentas imediatamente antes de executar o código do projeto.
Evite magia e correções de como o django lê suas configurações, pré-processe as configurações, mas não interfira posteriormente.
Nenhuma lógica complicada baseada em lógica. A configuração deve ser fixa e materializada, não computada em tempo real. Fornecer padrões de fallback é apenas lógica suficiente aqui.
Deseja realmente depurar, por que localmente você tem um conjunto correto de configurações, mas em produção em um servidor remoto, em uma das cem máquinas, algo calculado de maneira diferente? Oh! Testes unitários? Para configurações? Seriamente?
Solução
Minha estratégia consiste no excelente django-environment usado com ini
arquivos de estilo, fornecendo os.environment
padrões para o desenvolvimento local, alguns settings/<purpose>.py
arquivos mínimos e curtos que possuem um
import settings/base.py
APÓS o os.environment
conjunto foi definido a partir de um INI
arquivo. Isso efetivamente nos dá um tipo de injeção de configurações.
O truque aqui é modificar os.environment
antes de importar settings/base.py
.
Para ver o exemplo completo, faça o repo: 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
configurações / .env
Um padrão para o desenvolvimento local. Um arquivo secreto, para definir principalmente as variáveis de ambiente necessárias. Configure-os para valores vazios se não forem necessários no desenvolvimento local. Nós fornecemos os padrões aqui e não settings/base.py
falharemos em nenhuma outra máquina se eles estiverem ausentes no ambiente.
configurações / local.py
O que acontece aqui é carregar o ambiente de settings/.env
e importar configurações comuns de settings/base.py
. Depois disso, podemos substituir alguns para facilitar o desenvolvimento 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 produção, não devemos esperar um arquivo de ambiente, mas é mais fácil ter um se estiver testando algo. De qualquer maneira, para que não forneça alguns padrões em linha, é settings/base.py
possível responder de acordo.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
O principal ponto de interesse aqui são DEBUG
e ASSETS_DEBUG
substitui, eles serão aplicados ao python os.environ
SOMENTE se estiverem ausentes do ambiente e do arquivo.
Esses serão os padrões de produção, não é necessário colocá-los no ambiente ou no arquivo, mas podem ser substituídos, se necessário. Arrumado!
configurações / base.py
Essas são as configurações do seu baunilha, com alguns condicionais e muitas leituras do ambiente. Quase tudo está aqui, mantendo todos os ambientes propostos consistentes e o mais semelhante possível.
As principais diferenças estão abaixo (espero que sejam auto-explicativas):
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',)
O último bit mostra o poder aqui. ASSETS_DEBUG
possui um padrão sensato, que pode ser substituído settings/production.py
e mesmo aquele que pode ser substituído por uma configuração de ambiente! Yay!
Com efeito, temos uma hierarquia mista de importância:
- settings / .py - define padrões com base na finalidade, não armazena segredos
- settings / base.py - é principalmente controlado pelo ambiente
- configurações do ambiente de processo - bebê de 12 fatores!
- settings / .env - padrões locais para inicialização fácil