TL; DR: фокус полягає в тому, щоб змінити їх, os.environment
перш ніж імпортувати settings/base.py
будь-який settings/<purpose>.py
, це значно спростить речі.
Думка про всі ці файли, що переплітаються, болить у мене. Поєднання, імпорт (іноді умовно), переопределення, виправлення того, що вже було встановлено у випадку, якщо DEBUG
налаштування змінилися згодом. Який кошмар!
Через роки я перебирав усі різні рішення. Всі вони дещо працюють, але так боляче керувати. WTF! Чи нам справді потрібні всі ці клопоти? Ми почали лише з одного settings.py
файлу. Тепер нам потрібна документація, щоб правильно поєднати все це в правильному порядку!
Я сподіваюся, що я нарешті потрапив на (моє) солодке місце з рішенням нижче.
Давайте підкажемо цілі (деякі загальні, якісь мої)
Зберігайте секрети в секреті - не зберігайте їх у репо!
Встановлення / читання ключів та секретів за допомогою параметрів середовища, 12 факторного стилю .
Мати обгрунтовані параметри за замовчуванням. Ідеально для місцевого розвитку вам більше за замовчуванням не потрібно нічого.
… Але намагайтеся зберегти виробництво за замовчуванням у безпеці. Краще пропустити налаштування, що перевизначає локально, ніж пам'ятати, щоб налаштувати параметри за замовчуванням, безпечні для виробництва.
Мати можливість вмикати DEBUG
/ вимикати таким чином, що може впливати на інші налаштування (наприклад, за допомогою стисненого JavaScript чи ні).
Перемикання між цільовими налаштуваннями, як-от локальне / тестування / постановка / виробництво, повинно базуватися лише на DJANGO_SETTINGS_MODULE
, не більше.
… Але дозволити подальшу параметризацію за допомогою таких параметрів середовища DATABASE_URL
.
… Також дозволити їм використовувати різні налаштування цілей та запускати їх локально поруч, наприклад. налаштування виробництва на локальній машині розробника, щоб отримати доступ до виробничої бази даних або аркушів стильового стилю для тестування диму.
Збій, якщо змінна середовища не встановлена явно (вимагає мінімум порожнього значення), особливо у виробництві, наприклад. EMAIL_HOST_PASSWORD
.
Відповідь до DJANGO_SETTINGS_MODULE
встановленого за замовчуванням в Manag.py під час запуску проекту django-admin
Тримайте умовні до мінімуму, якщо умова визначили тип середовища (наприклад, для файлу журналу виробництва набору та цьогообертання), настройки перевизначення відповідного файлу замислили настройки.
Не роби
Не дозволяйте django прочитати налаштування DJANGO_SETTINGS_MODULE з файлу.
Тьфу! Подумайте, як це мета. Якщо вам потрібен файл (наприклад, docker env), прочитайте це в середовищі, перш ніж заглядати в процес django.
Не змінюйте DJANGO_SETTINGS_MODULE у своєму проекті / коді програми, наприклад. на основі імені хоста або імені процесу
Якщо вам лінь встановити змінну середовища (як для setup.py test
), зробіть це в інструментах безпосередньо перед запуском коду проекту.
Уникайте магії та виправлення того, як django читає його налаштування, попередньо обробляйте налаштування, але не заважайте потім.
Немає складної логіки, заснованої на логіці. Конфігурація має бути виправлена та матеріалізована, а не обчислюватися під час руху. Надання за замовчуванням просто достатньо логіки тут.
Ви дійсно хочете налагоджувати, чому локально у вас правильний набір налаштувань, але у виробництві на віддаленому сервері, на одній із сотень машин щось обчислюється по-іншому? Ой! Одиничні тести? Для налаштувань? Серйозно?
Рішення
Моя стратегія складається з чудової Джанго-окружа використовується з ini
файлами стилів, забезпечуючи по os.environment
замовчуванням для місцевого розвитку, деякі мінімальні і короткі settings/<purpose>.py
файли , які мають
import settings/base.py
ПІСЛЯos.environment
був створений зINI
файлу. Це ефективно дає нам ін'єкцію налаштувань.
Хитрість тут полягає в тому, щоб змінити їх os.environment
перед імпортомsettings/base.py
.
Щоб побачити повний приклад, перейдіть на репо: 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
налаштування / .env
За замовчуванням для місцевого розвитку. Таємний файл, в основному встановлює необхідні змінні середовища. Встановіть їх у порожні значення, якщо вони не потрібні для місцевого розвитку. Ми надаємо за замовчуванням тут, а не вsettings/base.py
можемо вийти з ладу на будь-якій іншій машині, якщо вона відсутня в оточенні.
налаштування / local.py
Тут відбувається завантаження середовища settings/.env
, а потім імпорт загальних налаштувань settings/base.py
. Після цього ми можемо змінити декілька, щоб полегшити місцевий розвиток.
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
налаштування / production.py
Для виробництва ми не повинні очікувати файлу оточення, але його легше мати, якщо ми щось тестуємо. Але в будь-якому випадку, щоб уникнути вбудованих кількох значень за замовчуванням, тому settings/base.py
можна відповісти відповідним чином.
environ.Env.read_env(Path(__file__) / "production.env", DEBUG='False', ASSETS_DEBUG='False')
from .base import *
Головні моменти, які тут цікавляться, є DEBUG
і ASSETS_DEBUG
переосмислення, вони будуть застосовані до пітонаos.environ
ТОЛЬКО, якщо вони МІССУЮТЬ із середовища та файлу.
Це будуть наші виробничі настройки за замовчуванням, не потрібно розміщувати їх у середовищі чи файлах, але вони можуть бути скасовані за потреби. Акуратно!
налаштування / base.py
Це ваші в основному налаштування джанго ванілі, з кількома умовами та читанням їх з оточення. Практично все є тут, підтримуючи всі цільові середовища послідовними та максимально схожими.
Основні відмінності наведені нижче (я сподіваюся, що вони пояснюють себе):
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',)
Останній біт показує тут потужність. ASSETS_DEBUG
має розумний дефолт, який можна змінитиsettings/production.py
і навіть той, який може бути замінений налаштуваннями середовища! Так!
По суті, ми маємо змішану ієрархію важливості:
- settings / .py - встановлює параметри за замовчуванням залежно від призначення, не зберігає секрети
- settings / base.py - здебільшого контролюється середовищем
- налаштування навколишнього середовища - дитина з 12 факторами!
- settings / .env - локальні параметри за замовчуванням для легкого запуску