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
в manage.py во время запуска проекта django-admin
Держите условные к минимуму, если условие определили тип среды (например, для лога - файла производство набора и этого вращения), настройки переопределения соответствующего файла замыслили настройки.
Не
Не позволяйте django читать настройки DJANGO_SETTINGS_MODULE из файла.
Тьфу! Подумайте, как это мета. Если вам нужен файл (например, docker env), прочитайте его в среду перед запуском процесса django.
Не переопределяйте DJANGO_SETTINGS_MODULE в коде вашего проекта / приложения, например. основанный на имени хоста или имени процесса.
Если вам лень устанавливать переменные окружения (например, for setup.py test
), сделайте это в инструментах непосредственно перед запуском кода проекта.
Избегайте магии и исправлений того, как django читает свои настройки, предварительно обрабатывайте настройки, но не вмешивайтесь впоследствии.
Никакой сложной логики, основанной на глупости. Конфигурация должна быть фиксированной и материализованной, а не вычисленной на лету. Предоставление запасных значений по умолчанию - достаточно логики.
Вы действительно хотите отладить, почему локально у вас есть правильный набор настроек, но при работе на удаленном сервере, на одной из ста машин что-то вычисляется по-другому? Ой! Модульные тесты? Для настроек? Шутки в сторону?
Решение
Моя стратегия состоит из превосходной django-environment, используемой с 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
Это ваши в основном настройки ванильного django, с несколькими условными выражениями и множеством чтений из среды. Здесь есть почти все, поддерживая согласованное и максимально похожее окружение.
Основные различия приведены ниже (я надеюсь, что они говорят сами за себя):
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 - локальные настройки по умолчанию для легкого запуска