TL; DR: Kuncinya adalah memodifikasi os.environment
sebelum Anda mengimpor settings/base.py
apa pun settings/<purpose>.py
, ini akan sangat menyederhanakan hal-hal.
Hanya memikirkan semua file yang saling terkait ini membuat saya sakit kepala. Menggabungkan, mengimpor (kadang-kadang kondisional), mengesampingkan, menambal apa yang sudah diatur dalam DEBUG
pengaturan kasus berubah nanti. Sungguh mimpi buruk!
Selama bertahun-tahun saya mengalami semua solusi yang berbeda. Mereka semua agak pekerjaan, tapi begitu menyakitkan untuk mengelola. WTF! Apakah kita benar-benar membutuhkan semua kerumitan itu? Kami mulai dengan hanya satu settings.py
file. Sekarang kita membutuhkan dokumentasi hanya untuk menggabungkan semua ini dengan benar dalam urutan yang benar!
Saya harap saya akhirnya mencapai sweet spot (saya) dengan solusi di bawah ini.
Mari kita rekap tujuan (beberapa umum, beberapa milikku)
Jaga kerahasiaan rahasia - jangan menyimpannya di repo!
Atur / baca kunci dan rahasia melalui pengaturan lingkungan, gaya 12 faktor .
Memiliki default fallback yang masuk akal. Idealnya untuk pengembangan lokal Anda tidak perlu apa-apa selain standar.
... tetapi cobalah untuk menjaga produksi default tetap aman. Lebih baik melewatkan pengaturan yang ditimpa secara lokal, daripada harus mengingat untuk menyesuaikan pengaturan default yang aman untuk produksi.
Memiliki kemampuan untuk menghidupkan DEBUG
/ mematikan dengan cara yang dapat memiliki efek pada pengaturan lain (mis. Menggunakan javascript terkompresi atau tidak).
Beralih antara pengaturan tujuan, seperti lokal / pengujian / pementasan / produksi, harus didasarkan hanya pada DJANGO_SETTINGS_MODULE
, tidak lebih.
... tetapi memungkinkan parameterisasi lebih lanjut melalui pengaturan lingkungan seperti DATABASE_URL
.
... juga memungkinkan mereka untuk menggunakan pengaturan tujuan yang berbeda dan menjalankannya secara lokal berdampingan, misalnya. pengaturan produksi pada mesin pengembang lokal, untuk mengakses basis data produksi atau menguji style sheet uji asap.
Gagal jika variabel lingkungan tidak ditetapkan secara eksplisit (membutuhkan nilai kosong minimum), terutama dalam produksi, misalnya. EMAIL_HOST_PASSWORD
.
Menanggapi pengaturan default DJANGO_SETTINGS_MODULE
di manage.py selama django-admin startproject
Jauhkan conditional untuk minimum, jika kondisi yang jenis lingkungan bertujuan (misalnya. Untuk produksi set file log dan rotasi itu), menggantikan pengaturan dalam terkait pengaturan file bertujuan.
Jangan lakukan itu
Jangan biarkan Django membaca pengaturan DJANGO_SETTINGS_MODULE membentuk file.
Ugh! Pikirkan bagaimana meta ini. Jika Anda perlu memiliki file (seperti docker env) baca itu ke lingkungan sebelum menatap proses Django.
Jangan menimpa DJANGO_SETTINGS_MODULE dalam kode proyek / aplikasi Anda, mis. berdasarkan nama host atau nama proses.
Jika Anda malas untuk mengatur variabel lingkungan (seperti untuk setup.py test
) lakukan di tooling sebelum Anda menjalankan kode proyek Anda.
Hindari sihir dan menambal tentang bagaimana Django membaca pengaturan itu, preprocess pengaturan tetapi tidak mengganggu sesudahnya.
Tidak ada omong kosong berbasis logika yang rumit. Konfigurasi harus diperbaiki dan terwujud tidak dihitung dengan cepat. Memberikan default mundur adalah logika yang cukup di sini.
Apakah Anda benar-benar ingin melakukan debug, mengapa secara lokal Anda memiliki set pengaturan yang benar tetapi dalam produksi pada server jauh, pada satu dari seratus mesin, sesuatu dikomputasi secara berbeda? Oh! Tes unit? Untuk pengaturan? Serius?
Larutan
Strategi saya terdiri dari django-environment luar biasa yang digunakan dengan ini
file gaya, memberikan os.environment
standar untuk pengembangan lokal, beberapa settings/<purpose>.py
file minimal dan pendek yang memiliki
import settings/base.py
SETELAH yang os.environment
ditetapkan dari INI
file. Ini secara efektif memberi kita semacam pengaturan injeksi.
Kuncinya di sini adalah memodifikasi os.environment
sebelum Anda mengimpor settings/base.py
.
Untuk melihat contoh lengkapnya, lakukan 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
pengaturan / .env
Default untuk pengembangan lokal. File rahasia, sebagian besar untuk mengatur variabel lingkungan yang diperlukan. Tetapkan nilai kosong jika tidak diperlukan dalam pengembangan lokal. Kami memberikan standar di sini dan tidak settings/base.py
gagal pada mesin lain jika mereka hilang dari lingkungan.
pengaturan / local.py
Apa yang terjadi di sini, adalah memuat lingkungan dari settings/.env
, lalu mengimpor pengaturan umum dari settings/base.py
. Setelah itu kita dapat mengganti beberapa untuk memudahkan pembangunan lokal.
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
pengaturan / production.py
Untuk produksi, kita seharusnya tidak mengharapkan file lingkungan, tetapi lebih mudah memilikinya jika kita menguji sesuatu. Tapi bagaimanapun, jangan sampai memberikan beberapa default inline, sehingga settings/base.py
dapat merespons sesuai.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
Poin utama yang menarik di sini adalah DEBUG
dan ASSETS_DEBUG
menimpa, mereka akan diterapkan pada python os.environ
SAJA jika mereka HILANG dari lingkungan dan file.
Ini akan menjadi default produksi kami, tidak perlu menempatkannya di lingkungan atau file, tetapi mereka dapat diganti jika diperlukan. Rapi!
pengaturan / base.py
Ini sebagian besar pengaturan vanilla django Anda, dengan beberapa persyaratan dan banyak membacanya dari lingkungan. Hampir semuanya ada di sini, menjaga semua lingkungan yang dituju konsisten dan semirip mungkin.
Perbedaan utama di bawah ini (saya harap ini cukup jelas):
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',)
Bit terakhir menunjukkan kekuatan di sini. ASSETS_DEBUG
memiliki default yang masuk akal, yang dapat ditimpa settings/production.py
dan bahkan yang dapat ditimpa oleh pengaturan lingkungan! Yay!
Akibatnya, kami memiliki hierarki kepentingan campuran:
- settings / .py - menetapkan standar berdasarkan tujuan, tidak menyimpan rahasia
- settings / base.py - sebagian besar dikendalikan oleh lingkungan
- pengaturan lingkungan proses - 12 faktor bayi!
- settings / .env - default lokal untuk startup yang mudah