Эта статья предназначена для разработчиков, использующих фреймворк Django. Она дает глубокое представление о способах настройки проекта Django, а также о плюсах и минусах различных подходов.
Скачивайте книги ТОЛЬКО на русском языке у нас в телеграм канале: PythonBooksRU
В статье вы также найдете рекомендации относительно инструментов, лучших практик и архитектурных решений, проверенных временем и успешными проектами. А если после прочтения статьи у вас останутся вопросы, вы всегда можете оставить комментарий ниже.
Основные проблемы управления настройками Django
Различные окружения. Обычно у вас есть несколько окружений: local, dev, ci, qa, staging, production и т.д. Каждое окружение может иметь свои специфические настройки (например: DEBUG = True
, более подробное логирование, дополнительные приложения, некоторые моделируемые данные и т.д.). Вам нужен подход, который позволит вам сохранить все эти конфигурации настроек Django.
Конфиденциальные данные. У вас есть SECRET_KEY
в каждом проекте Django. Вдобавок к этому могут быть пароли БД и токены для сторонних API, таких как Amazon или Twitter. Эти данные нельзя хранить в VCS.
Обмен настройками между членами команды. Вам нужен общий подход, чтобы исключить человеческий фактор при работе с настройками. Например, разработчик может добавить стороннее приложение или какую-то интеграцию API и не добавить определенные настройки. На больших (или даже средних) проектах это может вызвать реальные проблемы.
Настройки Django – это код на языке Python. Это проклятие и благословение одновременно. Это дает вам большую гибкость, но также может быть и проблемой – вместо пар ключ-значение, settings.py может иметь очень запутанную логику.
Подходы к настройке параметров Django
Не существует встроенного универсального способа настройки параметров Django. Но книги, open-source и рабочие проекты дают множество рекомендаций относительно того, как это лучше сделать. Давайте кратко рассмотрим самые популярные из них, чтобы изучить их слабые и сильные стороны.
settings_local.py
Это самый старый метод. Я использовал его, когда впервые настраивал проект Django на рабочем сервере. Я видел, как многие люди использовали его в то время, и я все еще вижу это сейчас.
Основная идея этого метода заключается в том, чтобы расширить все специфические для локальной среды настройки в файле settings_local.py, который игнорируется VCS (git). Вот пример:
Файл settings.py:
ALLOWED_HOSTS = ['example.com'] DEBUG = False DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'production_db', 'USER': 'user', 'PASSWORD': 'password', 'HOST': 'db.example.com', 'PORT': '5432', 'OPTIONS': { 'sslmode': 'require' } } } ... from .settings_local import *
Файл settings_local.py:
ALLOWED_HOSTS = ['localhost'] DEBUG = True DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'local_db', 'HOST': '127.0.0.1', 'PORT': '5432', } }
Плюсы:
- Секреты, которых нет в VCS.
Минусы:
- settings_local.py не находится в VCS, поэтому вы можете потерять некоторые настройки окружения Django.
- Файл настроек Django – это Python-код, поэтому settings_local.py может иметь неочевидную логику.
- Вам необходимо иметь settings_local.example (в VCS), чтобы поделиться конфигурациями Django по умолчанию для разработчиков.
Отдельный файл настроек для каждой среды
Это расширение предыдущего подхода. Он позволяет хранить все конфигурации в VCS и делиться настройками по умолчанию между разработчиками.
В этом случае существует несколько файлов, из которых проекты на Django получают настройки, и вы создаете пакет settings со следующей структурой файлов:
settings/ ├── __init__.py ├── base.py ├── ci.py ├── local.py ├── staging.py ├── production.py └── qa.py
settings/local.py:
from .base import * ALLOWED_HOSTS = ['localhost'] DEBUG = True DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'local_db', 'HOST': '127.0.0.1', 'PORT': '5432', } }
Чтобы указать для запускаемого проекта, какие настройки Django использовать, необходимо задать дополнительный параметр:
python manage.py runserver --settings=settings.local
Плюсы:
- Все окружения находятся в VCS.
- Легко обмениваться настройками между разработчиками.
Минусы:
- Необходимо найти способ обработки секретных паролей и маркеров.
- “Наследование” настроек может быть трудно отследить и поддерживать.
Переменные окружения
Чтобы решить проблему с конфиденциальными данными, в Django можно использовать переменные окружения.
import os SECRET_KEY = os.environ['SECRET_KEY'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': os.environ['DATABASE_NAME'], 'HOST': os.environ['DATABASE_HOST'], 'PORT': int(os.environ['DATABASE_PORT']), } }
Это самый простой пример с использованием Python os.environ, и у него есть несколько проблем:
- Необходимо обрабатывать исключения KeyError.
- Нужно вручную преобразовывать типы (см. использование
DATABASE_PORT
в примере выше).
Чтобы исправить KeyError, вы можете написать свою собственную обертку. Например:
import os from django.core.exceptions import ImproperlyConfigured def get_env_value(env_variable): try: return os.environ[env_variable] except KeyError: error_msg = 'Set the {} environment variable'.format(var_name) raise ImproperlyConfigured(error_msg) SECRET_KEY = get_env_value('SECRET_KEY') DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': get_env_value('DATABASE_NAME'), 'HOST': get_env_value('DATABASE_HOST'), 'PORT': int(get_env_value('DATABASE_PORT')), } }
Также вы можете установить значения по умолчанию для этой обертки и добавить преобразование типов. Но на самом деле нет необходимости писать эту обертку, потому что вы можете использовать стороннюю библиотеку (мы поговорим об этом позже).
Плюсы:
- Конфиг Django отделен от кода.
- Нет конфликта окружений – у вас один и тот же код для всех окружений.
- Отсутствие наследования в настройках, более чистый и последовательный код.
- Существует теоретическое обоснование использования переменных окружения Django – 12 факторов.
Минусы:
- Вам нужно найти способ делиться базовым конфигом между разработчиками.
12 факторов
12 факторов – это сборник рекомендаций по созданию распределенных веб-приложений, которые можно будет легко развернуть и масштабировать в облаке. Он был создан компанией Heroku, известным провайдером облачного хостинга.
Как следует из названия, сборник состоит из двенадцати частей:
- Одно приложение — один репозиторий
- Явные зависимости
- Конфигурация
- Сторонние службы
- Сборка, релиз и выполнение
- Приложение – набор процессов
- Настройка портов
- Параллелизм
- Быстрый запуск и корректное завершение
- Соотношение среды разработки и развертывания
- Логирование
- Задачи администрирования
Каждый пункт описывает рекомендуемый способ реализации определенного аспекта проекта. Некоторые из этих пунктов покрываются такими инструментами, как Django, Python, pip. Некоторые покрываются паттернами проектирования или настройкой инфраструктуры. В контексте данной статьи нас интересует одна часть: Конфигурация.
Ее основное правило – хранить конфигурацию в окружении. Следуя этой рекомендации, мы получим строгое отделение конфигурации от кода.
Подробнее вы можете прочитать на сайте 12factor.net.
Примечание редакции: также предлагаем почитать “Плюсы и минусы использования Python для веб-разработки”.
django-environ
Исходя из вышесказанного, мы видим, что переменные окружения являются идеальным местом для хранения настроек Django.
Теперь пришло время поговорить о наборе инструментов.
Написание кода с использованием os.environ иногда может оказаться сложным и потребовать дополнительных усилий для обработки ошибок. Вместо этого лучше использовать django-environ.
Технически это слияние:
Этот инструмент предоставляет хорошо функционирующий API для чтения значений из переменных окружения или текстовых файлов, ручного преобразования типов и т.д. Давайте рассмотрим несколько примеров.
Файл Django settings.py до:
import os SITE_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.realpath(__file__)))) DEBUG = True TEMPLATE_DEBUG = DEBUG DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'production_db', 'USER': 'user', 'PASSWORD': 'password', 'HOST': 'db.example.com', 'PORT': '5432', 'OPTIONS': { 'sslmode': 'require' } } } MEDIA_ROOT = os.path.join(SITE_ROOT, 'assets') MEDIA_URL = 'media/' STATIC_ROOT = os.path.join(SITE_ROOT, 'static') STATIC_URL = 'static/' SECRET_KEY = 'Some-Autogenerated-Secret-Key' CACHES = { 'default': { 'BACKEND': 'django_redis.cache.RedisCache', 'LOCATION': '127.0.0.1:6379/1', } }
Файл Django settings.py после:
import environ root = environ.Path(__file__) - 3 # get root of the project env = environ.Env() environ.Env.read_env() # reading .env file SITE_ROOT = root() DEBUG = env.bool('DEBUG', default=False) TEMPLATE_DEBUG = DEBUG DATABASES = {'default': env.db('DATABASE_URL')} public_root = root.path('public/') MEDIA_ROOT = public_root('media') MEDIA_URL = env.str('MEDIA_URL', default='media/') STATIC_ROOT = public_root('static') STATIC_URL = env.str('STATIC_URL', default='static/') SECRET_KEY = env.str('SECRET_KEY') CACHES = {'default': env.cache('REDIS_CACHE_URL')}
.env-файл:
DEBUG=True DATABASE_URL=postgres://user:password@db.example.com:5432/production_db?sslmode=require REDIS_CACHE_URL=redis://user:password@cache.example.com:6379/1 SECRET_KEY=Some-Autogenerated-Secret-Key
Структура настроек
Вместо того, чтобы разделять настройки по окружениям, вы можете разделить их по источникам: Django, сторонние приложения (Celery, DRF, и т.д.) и ваши собственные настройки.
Структура файлов:
project/ ├── apps/ ├── settings/ │ ├── __init__.py │ ├── djano.py │ ├── project.py │ └── third_party.py └── manage.py
Файл __init__.py
:
from .django import * # All Django related settings from .third_party import * # Celery, Django REST Framework & other 3rd parties from .project import * # You custom settings
Каждый модуль может быть расположен как пакет, и вы можете разделить его на более мелкие части:
project/ ├── apps/ ├── settings/ │ ├── project │ │ ├── __init__.py │ │ ├── custom_module_foo.py │ │ ├── custom_module_bar.py │ │ └── custom_module_xyz.py │ ├── third_party │ │ ├── __init__.py │ │ ├── celery.py │ │ ├── email.py │ │ └── rest_framework.py │ ├── __init__.py │ └── djano.py └── manage.py
Соглашения об именовании
Именование переменных – одна из самых сложных частей разработки. Так же, как и именование настроек. Мы не можем полагаться на Django или сторонние приложения, но мы можем следовать этим простым правилам для наших пользовательских (проектных) настроек:
- Давайте осмысленные имена своим настройкам.
- Всегда используйте префикс с именем проекта для ваших пользовательских (проектных) настроек.
- Пишите описания для своих настроек в комментариях.
Плохой пример:
API_SYNC_CRONTAB = env.str('API_SYNC_CRONTAB')
Хороший пример:
# Run job for getting new tweets. # Accept string in crontab format. By default: every 30 minutes. MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB = env.str( 'MYAWESOMEPROJECT_TWEETS_API_SYNC_CRONTAB', default='30 * * * *' )
Измените MYAWESOMEPROJECT на настоящее название проекта.
Настройка Django: лучшие практики
- Храните настройки в переменных окружения.
- Записывайте значения по умолчанию для релизной конфигурации (за исключением секретных ключей и токенов).
- Не храните в переменных конфиденциальные настройки и не помещайте их в VCS.
- Разделяйте настройки на группы: Django, сторонние, проект.
- Соблюдайте соглашения об именовании для пользовательских (проектных) настроек.
Заключение
Файл настроек – это маленькая, но очень важная часть любого проекта Django. Если настройка Django выполнена неправильно, у вас возникнет множество проблем на всех этапах разработки. Но если вы сделаете все продуманно, файл настроек станет хорошей основой для вашего проекта, которая позволит ему расти и масштабироваться в будущем.
Используя подход с задействованием переменных окружения, вы можете легко перейти от монолитной к микросервисной архитектуре, обернуть свой проект в контейнеры Docker и развернуть его на любой VPS или облачной хостинговой платформе, такой как: Amazon, Google Cloud или ваш собственный кластер Kubernetes.
Перевод статьи Alex Ryabtsev «Configuring Django Settings: Best Practices».