TL; DR: Der Trick ist , zu ändern , os.environment
bevor Sie importieren settings/base.py
in jedem settings/<purpose>.py
dieser stark die Dinge vereinfachen wird.
Wenn ich nur an all diese ineinander verschlungenen Dateien denke, bekomme ich Kopfschmerzen. Kombinieren, Importieren (manchmal bedingt), Überschreiben, Patchen von dem, was bereits festgelegt wurde, falls sich die DEBUG
Einstellung später ändert. Was ein Alptraum!
Im Laufe der Jahre habe ich alle verschiedenen Lösungen durchlaufen. Sie funktionieren alle etwas , sind aber so schmerzhaft zu handhaben. WTF! Brauchen wir wirklich all diesen Ärger? Wir haben mit nur einer settings.py
Datei begonnen. Jetzt brauchen wir eine Dokumentation, um all dies in der richtigen Reihenfolge richtig zu kombinieren!
Ich hoffe, dass ich mit der folgenden Lösung endlich den (meinen) Sweet Spot erreicht habe.
Lassen Sie uns die Ziele zusammenfassen (einige häufig, andere meine)
Geheimnisse geheim halten - nicht in einem Repo aufbewahren!
Festlegen / Lesen von Schlüsseln und Geheimnissen über Umgebungseinstellungen im 12-Faktor-Stil .
Haben Sie vernünftige Fallback-Standardeinstellungen. Ideal für die lokale Entwicklung benötigen Sie neben den Standardeinstellungen nichts mehr.
… Aber versuchen Sie, die Standardeinstellung der Produktion sicher zu halten. Es ist besser, eine lokale Überschreibung von Einstellungen zu verpassen, als daran zu denken, die Standardeinstellungen sicher für die Produktion anzupassen.
Sie können DEBUG
auf eine Weise ein- und ausschalten, die sich auf andere Einstellungen auswirken kann (z. B. komprimiertes oder nicht komprimiertes Javascript).
Das Umschalten zwischen Zweckeinstellungen wie lokal / Testen / Staging / Produktion sollte nur auf DJANGO_SETTINGS_MODULE
, nicht mehr basieren .
… Aber erlauben Sie eine weitere Parametrierung durch Umgebungseinstellungen wie DATABASE_URL
.
… Erlauben ihnen auch, verschiedene Zweckeinstellungen zu verwenden und sie lokal nebeneinander auszuführen, z. Produktions-Setup auf einem lokalen Entwicklercomputer, um auf die Produktionsdatenbank zuzugreifen oder komprimierte Stylesheets zu rauchen.
Fehler, wenn eine Umgebungsvariable nicht explizit festgelegt ist (mindestens ein leerer Wert erforderlich), insbesondere in der Produktion, z. EMAIL_HOST_PASSWORD
.
Reagieren DJANGO_SETTINGS_MODULE
Sie während des Startprojekts von django-admin auf die in manage.py festgelegte Standardeinstellung
Halten Sie die Bedingungen auf ein Minimum. Wenn es sich bei der Bedingung um den beabsichtigten Umgebungstyp handelt (z. B. für die Produktionssatz-Protokolldatei und deren Drehung), überschreiben Sie die Einstellungen in der zugehörigen beabsichtigten Einstellungsdatei.
Nicht
Lassen Sie django nicht die Einstellung DJANGO_SETTINGS_MODULE aus einer Datei lesen.
Pfui! Überlegen Sie, wie meta das ist. Wenn Sie eine Datei (wie Docker Env) benötigen, lesen Sie diese in die Umgebung, bevor Sie einen Django-Prozess starten.
Überschreiben Sie DJANGO_SETTINGS_MODULE nicht in Ihrem Projekt- / App-Code, z. basierend auf Hostname oder Prozessname.
Wenn Sie faul sind, Umgebungsvariablen (wie für setup.py test
) festzulegen, tun Sie dies in Tools, bevor Sie Ihren Projektcode ausführen.
Vermeiden Sie Magie und Patches, wie Django seine Einstellungen liest, verarbeiten Sie die Einstellungen vor, aber stören Sie sie danach nicht.
Kein komplizierter logischer Unsinn. Die Konfiguration sollte festgelegt und materialisiert werden und nicht im laufenden Betrieb berechnet werden. Das Bereitstellen von Fallback-Standardeinstellungen ist hier gerade genug Logik.
Möchten Sie wirklich debuggen, warum haben Sie lokal die richtigen Einstellungen, aber in der Produktion auf einem Remote-Server, auf einem von hundert Computern, etwas anders berechnet? Oh! Unit Tests? Für Einstellungen? Ernsthaft?
Lösung
Meine Strategie besteht aus einer exzellenten Django-Umgebung, die mit ini
Stildateien verwendet wird und os.environment
Standardeinstellungen für die lokale Entwicklung bietet, sowie einigen minimalen und kurzen settings/<purpose>.py
Dateien, die ein
import settings/base.py
AFTER the haben, os.environment
das aus einer INI
Datei festgelegt wurde. Dies gibt uns effektiv eine Art Einspritzung.
Der Trick hier besteht darin, os.environment
vor dem Import zu ändern settings/base.py
.
Das vollständige Beispiel finden Sie im 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
Einstellungen / .env
Ein Standard für die lokale Entwicklung. Eine geheime Datei, um meistens die erforderlichen Umgebungsvariablen festzulegen. Setzen Sie sie auf leere Werte, wenn sie in der lokalen Entwicklung nicht benötigt werden. Wir geben hier settings/base.py
Standardeinstellungen an und versagen auf keinem anderen Computer, wenn diese in der Umgebung fehlen.
settings / local.py
Was hier passiert, ist das Laden der Umgebung von settings/.env
und das Importieren allgemeiner Einstellungen von settings/base.py
. Danach können wir einige überschreiben, um die lokale Entwicklung zu erleichtern.
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
Für die Produktion sollten wir keine Umgebungsdatei erwarten, aber es ist einfacher, eine zu haben, wenn wir etwas testen. Trotzdem werden nicht wenige Standardeinstellungen inline bereitgestellt, sodass settings/base.py
entsprechend reagiert werden kann.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
Das Hauptinteresse hierbei ist DEBUG
und ASSETS_DEBUG
überschreibt, sie werden os.environ
NUR auf die Python angewendet, wenn sie aus der Umgebung und der Datei fehlen.
Dies sind unsere Produktionsstandards, die nicht in die Umgebung oder Datei gestellt werden müssen, aber bei Bedarf überschrieben werden können. Ordentlich!
settings / base.py
Dies sind Ihre meist Vanille-Django-Einstellungen, mit ein paar Bedingungen und viel Lesen aus der Umgebung. Fast alles ist hier drin, um alle beabsichtigten Umgebungen konsistent und so ähnlich wie möglich zu halten.
Die Hauptunterschiede sind unten (ich hoffe, diese sind selbsterklärend):
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',)
Das letzte Bit zeigt die Kraft hier. ASSETS_DEBUG
hat eine sinnvolle Standardeinstellung, die in überschrieben werden kann settings/production.py
und die sogar durch eine Umgebungseinstellung überschrieben werden kann! Yay!
Tatsächlich haben wir eine gemischte Hierarchie von Bedeutung:
- settings / .py - legt die Standardeinstellungen basierend auf dem Zweck fest und speichert keine Geheimnisse
- settings / base.py - wird hauptsächlich von der Umgebung gesteuert
- Prozessumgebungseinstellungen - 12-Faktor-Baby!
- settings / .env - lokale Standardeinstellungen für einen einfachen Start