TL;DR: Der Trick besteht darin, die os.environment
bevor Sie importieren settings/base.py
in jedem settings/<purpose>.py
Dies wird die Arbeit erheblich vereinfachen.
Allein der Gedanke an all diese ineinandergreifenden Akten bereitet mir Kopfschmerzen. Kombinieren, importieren (manchmal bedingt), überschreiben, Parcheando von dem, was bereits für den Fall gesetzt wurde DEBUG
Einstellung später geändert werden. Was für ein Alptraum!
Im Laufe der Jahre habe ich alle möglichen Lösungen ausprobiert. Sie alle etwas funktionieren, aber so mühsam zu verwalten sind. WTF! Brauchen wir wirklich diesen ganzen Ärger? Wir haben mit nur einer settings.py
Datei. Jetzt brauchen wir eine Dokumentation, um all diese Dateien in der richtigen Reihenfolge zu kombinieren!
Ich hoffe, dass ich mit der nachstehenden Lösung endlich den (meinen) Nerv getroffen habe.
Rekapitulieren wir die Ziele (einige gemeinsame, einige meine)
-
Behalten Sie Geheimnisse für sich - bewahren Sie sie nicht in einem Repo auf!
-
Setzen/Lesen von Schlüsseln und Geheimnissen durch Umgebungseinstellungen, 12-Faktor-Stil .
-
Vernünftige Standardwerte für den Fall der Fälle haben. Im Idealfall brauchen Sie für die lokale Entwicklung außer den Standardeinstellungen nichts weiter.
-
aber versuchen Sie, die Standardeinstellungen produktionssicher zu halten. Es ist besser, eine Einstellung zu verpassen, die lokal überschrieben wird, als daran denken zu müssen, die Standardeinstellungen für die Produktion anzupassen.
-
die Möglichkeit haben, zu wechseln DEBUG
ein/aus in einer Weise, die sich auf andere Einstellungen auswirken kann (z. B. Verwendung von komprimiertem oder nicht komprimiertem Javascript).
-
Der Wechsel zwischen den Zweckeinstellungen, wie z. B. lokal/Testing/Taging/Production, sollte nur auf der Grundlage DJANGO_SETTINGS_MODULE
, nichts weiter.
-
erlauben aber eine weitere Parametrisierung durch Umgebungseinstellungen wie DATABASE_URL
.
-
erlauben es ihnen auch, verschiedene Einstellungen zu verwenden und sie lokal nebeneinander laufen zu lassen, z.B. Produktions-Setup auf lokalem Entwickler-Rechner, um auf die Produktionsdatenbank zuzugreifen oder komprimierte Stylesheets zu testen.
-
Schlägt fehl, wenn eine Umgebungsvariable nicht explizit gesetzt ist (erfordert mindestens einen leeren Wert), insbesondere in der Produktion, z. B. EMAIL_HOST_PASSWORD
.
-
Reagieren Sie auf den Standard DJANGO_SETTINGS_MODULE
die in manage.py während django-admin startproject
-
Beschränken Sie Konditionale auf ein Minimum, wenn die Bedingung die (z. B. für die Produktion eingestellte Protokolldatei und ihre Rotation), die Einstellungen in der zugehörigen Einstellungsdatei außer Kraft setzen.
Do not's
-
Lassen Sie django keine DJANGO_SETTINGS_MODULE-Einstellungen aus einer Datei lesen.
Igitt! Denken Sie daran, wie meta das ist. Wenn Sie eine Datei benötigen (wie Docker env), lesen Sie diese in die Umgebung ein, bevor Sie einen Django-Prozess starten.
-
Überschreiben Sie DJANGO_SETTINGS_MODULE nicht in Ihrem Projekt/Anwendungscode, z.B. basierend auf dem Hostnamen oder Prozessnamen.
Wenn Sie zu faul sind, eine Umgebungsvariable zu setzen (wie bei setup.py test
) tun Sie dies im Tooling, kurz bevor Sie Ihren Projektcode ausführen.
-
Vermeiden Sie Magie und Parcheando, wie django seine Einstellungen liest, verarbeiten Sie die Einstellungen vor, aber mischen Sie sich danach nicht ein.
-
Kein komplizierter, auf Logik basierender Unsinn. Die Konfiguration sollte fixiert und materialisiert sein und nicht on the fly berechnet werden. Die Bereitstellung einer Fallback-Standardwerte ist gerade genug Logik hier.
Wollen Sie wirklich debuggen, warum Sie lokal die richtigen Einstellungen haben, aber in der Produktion auf einem entfernten Server, auf einem von hundert Rechnern, etwas anders berechnet wird? Oh! Unit-Tests? Für Einstellungen? Ernsthaft?
Lösung
Meine Strategie besteht aus hervorragenden django-umgebung verwendet mit ini
Stil-Dateien, Bereitstellung von os.environment
Vorgaben für die lokale Entwicklung, einige minimale und kurze settings/<purpose>.py
Dateien, die ein import settings/base.py
NACH les os.environment
wurde von einer INI
Datei. Dadurch erhalten wir eine Art von Einstellungsinjektion.
Der Trick dabei ist, dass man die os.environment
bevor Sie importieren settings/base.py
.
Um das vollständige Beispiel zu sehen, besuchen Sie das 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
Eine Vorgabe für die lokale Entwicklung. Eine geheime Datei, um meist benötigte Umgebungsvariablen zu setzen. Setzen Sie sie auf leere Werte, wenn sie für die lokale Entwicklung nicht benötigt werden. Wir bieten hier Standardwerte an und nicht in settings/base.py
auf jedem anderen Rechner fehlschlagen, wenn sie in der Umgebung fehlen.
einstellungen/local.py
Was hier passiert, ist das Laden der Umgebung von settings/.env
und dann die gemeinsamen Einstellungen importieren von settings/base.py
. Danach können wir einige davon außer Kraft setzen, 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
Einstellungen/Produktion.py
Für die Produktion sollten wir keine Umgebungsdatei erwarten, aber es ist einfacher, eine zu haben, wenn wir etwas testen wollen. Wie auch immer, wir sollten ein paar Standardwerte inline bereitstellen, also settings/base.py
entsprechend reagieren können.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
Die wichtigsten Punkte, die hier von Interesse sind, sind DEBUG
y ASSETS_DEBUG
übersteuert, werden sie auf die Python os.environ
NUR dann, wenn sie in der Umgebung und in der Datei nicht vorhanden sind.
Dies sind die Standardeinstellungen für die Produktion. Sie müssen nicht in die Umgebung oder Datei aufgenommen werden, können aber bei Bedarf überschrieben werden. Toll!
einstellungen/base.py
Dies sind Ihre meist vanilla django Einstellungen, mit ein paar Conditionals und viel von Lesen Sie sie aus der Umgebung. Fast alles ist hier drin, um alle Umgebungen konsistent und so ähnlich wie möglich zu halten.
Die wichtigsten Unterschiede sind nachstehend aufgeführt (ich hoffe, sie 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
# Ths 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 Stück zeigt, welche Kraft hier steckt. ASSETS_DEBUG
hat eine sinnvolle Voreinstellung, die in den folgenden Abschnitten überschrieben werden kann settings/production.py
und selbst das kann durch eine Umgebungseinstellung außer Kraft gesetzt werden! Juhu!
In der Tat haben wir eine gemischte Hierarchie der Bedeutung:
- settings/.py - setzt Standardwerte je nach Zweck, speichert keine Geheimnisse
- settings/base.py - wird hauptsächlich von der Umgebung kontrolliert
- Einstellungen der Prozessumgebung - 12-Faktor-Baby!
- settings/.env - lokale Standardeinstellungen für einen einfachen Start