TL; DR: Il trucco è modificare os.environment
prima di importare settings/base.py
in qualsiasi settings/<purpose>.py
, questo semplificherà notevolmente le cose.
Il solo pensiero di tutti questi file intrecciati mi fa venire il mal di testa. Combinare, importare (a volte in modo condizionale), sovrascrivere, applicare patch a ciò che era già stato impostato nel caso in cui le DEBUG
impostazioni fossero modificate in seguito. Che incubo!
Nel corso degli anni ho attraversato tutte le diverse soluzioni. Funzionano tutti in qualche modo , ma sono così dolorosi da gestire. WTF! Abbiamo davvero bisogno di tutta quella seccatura? Abbiamo iniziato con un solo settings.py
file. Ora abbiamo bisogno di una documentazione solo per combinare correttamente tutte queste in un ordine corretto!
Spero di aver finalmente raggiunto il (mio) punto debole con la soluzione di seguito.
Ricapitoliamo gli obiettivi (alcuni comuni, altri miei)
Mantenere segreti i segreti - non archiviarli in un repository!
Imposta / leggi chiavi e segreti attraverso le impostazioni dell'ambiente, stile 12 fattori .
Possibili impostazioni predefinite di fallback. Idealmente per lo sviluppo locale non è necessario altro oltre alle impostazioni predefinite.
... ma cerca di mantenere sicura la produzione di default. È meglio perdere un'impostazione locale, piuttosto che dover ricordare di regolare le impostazioni predefinite in modo sicuro per la produzione.
Avere la possibilità di accendere DEBUG
/ spegnere in un modo che può avere un effetto su altre impostazioni (ad es. Utilizzando JavaScript compresso o meno).
Il passaggio tra le impostazioni degli scopi, come local / testing / staging / production, dovrebbe basarsi solo su DJANGO_SETTINGS_MODULE
, niente di più.
... ma consenti un'ulteriore parametrizzazione attraverso impostazioni dell'ambiente come DATABASE_URL
.
... consentono inoltre di utilizzare impostazioni di scopi diversi ed eseguirle localmente fianco a fianco, ad es. installazione di produzione su macchina sviluppatore locale, per accedere al database di produzione o testare i fogli di stile compressi del fumo.
Fallire se una variabile d'ambiente non è impostata esplicitamente (richiede un valore vuoto al minimo), specialmente nella produzione, ad es. EMAIL_HOST_PASSWORD
.
Rispondere al DJANGO_SETTINGS_MODULE
set predefinito in manage.py durante l'avvio del progetto django-admin
Mantenere i condizionali al minimo, se la condizione è il tipo di ambiente designato (ad es. Per il file di registro del set di produzione e la sua rotazione), sovrascrivere le impostazioni nel file delle impostazioni finalizzato associato.
Non è
Non lasciare che django legga le impostazioni di DJANGO_SETTINGS_MODULE da un file.
Ugh! Pensa a quanto è meta. Se hai bisogno di avere un file (come docker env) leggilo nell'ambiente prima di avviare un processo di django.
Non sovrascrivere DJANGO_SETTINGS_MODULE nel codice progetto / app, ad es. in base al nome host o al nome del processo.
Se sei pigro per impostare la variabile di ambiente (come per setup.py test
), fallo negli strumenti appena prima di eseguire il codice del progetto.
Evita la magia e le patch di come django legge le sue impostazioni, preelabora le impostazioni ma non interferire in seguito.
Nessuna assurdità basata sulla logica complicata. La configurazione deve essere fissa e materializzata non calcolata al volo. Fornire i valori predefiniti di fallback è una logica sufficiente qui.
Vuoi davvero eseguire il debug, perché localmente hai un set di impostazioni corretto ma in produzione su un server remoto, su una delle centinaia di macchine, qualcosa calcolato in modo diverso? Oh! Test unitari? Per le impostazioni? Sul serio?
Soluzione
La mia strategia è costituito da eccellente django-environ utilizzato con ini
file di stile, fornendo os.environment
, alcuni minimi e brevi valori di default per lo sviluppo locale settings/<purpose>.py
dei file che hanno una
import settings/base.py
dopo l' os.environment
stato fissato da un INI
file. Questo ci dà effettivamente una sorta di iniezione di impostazioni.
Il trucco qui è modificare os.environment
prima di importare settings/base.py
.
Per vedere l'esempio completo vai al repository: 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
impostazioni / .env
Impostazioni predefinite per lo sviluppo locale. Un file segreto, per impostare principalmente le variabili di ambiente richieste. Impostali su valori vuoti se non sono richiesti nello sviluppo locale. Forniamo impostazioni predefinite qui e non in caso settings/base.py
di errore su qualsiasi altra macchina se mancano dall'ambiente.
impostazioni / local.py
Quello che succede qui è caricare l'ambiente da settings/.env
, quindi importare le impostazioni comuni da settings/base.py
. Successivamente, possiamo ignorarne alcuni per facilitare lo sviluppo locale.
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
impostazioni / production.py
Per la produzione non dovremmo aspettarci un file di ambiente, ma è più facile averne uno se stiamo testando qualcosa. Comunque, per non fornire poche impostazioni predefinite in linea, quindi settings/base.py
può rispondere di conseguenza.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
I principali punti di interesse qui sono DEBUG
e ASSETS_DEBUG
sovrascrivono, saranno applicati al pitone os.environ
SOLO se MANCANO dall'ambiente e dal file.
Questi saranno i nostri valori predefiniti di produzione, non è necessario inserirli nell'ambiente o nel file, ma possono essere sostituiti se necessario. ! Neat
impostazioni / base.py
Queste sono le tue impostazioni per lo più django alla vaniglia, con alcuni condizionali e molta lettura da parte dell'ambiente. Quasi tutto è qui, mantenendo tutti gli ambienti proposti coerenti e il più simile possibile.
Le differenze principali sono di seguito (spero che siano autoesplicative):
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',)
L'ultimo bit mostra la potenza qui. ASSETS_DEBUG
ha un valore predefinito ragionevole, che può essere ignorato settings/production.py
e persino che può essere ignorato da un'impostazione ambientale! Sìì!
In effetti abbiamo una gerarchia mista di importanza:
- settings / .py - imposta i valori predefiniti in base allo scopo, non memorizza i segreti
- settings / base.py - è principalmente controllato dall'ambiente
- impostazioni dell'ambiente di processo - 12 fattori baby!
- settings / .env - impostazioni predefinite locali per un facile avvio