20 Фев


2018

Хорошая практика разделения настроек приложения Django.

Несколько моделей настроек для разных сред в Django

В процессе погружения django я как-то пришел к мысли что было бы неплохо разделить блоки кода для продакшена и для разработки, при этом, хотелось бы, чтобы при развертывании приложения на боевом сервере было приложено минимальное количество усилий и ручных изменений (тут конечно должен быть пункт про докер...но объективно говоря я до него еще не дорос). Итак, я хочу рассказать про 2 варианта оптимизации процесса разделения настроек прилжения (и еще 1 вариант как я делал изначально, и как делать совсем не нужно..нооо..это пришло только с опытом).

Как я к этому пришел? И как топорно делал раньше...

Изначально, в сравнительно небольшом проекте...на боевом сервере был свой файл настроек, который после 1 деплоя был благополучно добалвен в гитигнор. И поскольку настройки менялись..достаточно редко, это не особо меня беспокоило. Однако в скоре пришлось добавлять новые приложения и функционал, и простая заливка через ssh с изменением параметров вручную уже начала немного осложнять жизнь. И первой идеей было вынести из блока настроек уникальные куски кода, первоначально я вынес в отдельный файл секретный ключ, настройки базы данных, дебаг режим, а также инициализировал список установленных приложений (при разработке я использовал django debug toolbar). И..это работало, и сейчас оглядываясь назад я мог сказать что это не самый плохой вариант и уж точно не самая ужасная моя идея. Минус для меня был только 1, если переносить проект, либо юзать его на тестовом сервере придется заного писать уникальные настройки (хотя их так и так придется писать, однако в моем случае они просто не пушились в репозиторий..уникальные же). Также у меня были модули которые работали по разному на разных системах.

- ну и что? это же питон..он кросплатформенный...

Разумеется, однако я разрабатываю по большей части на маке, и при работе с gui я использоваю немного иные методы чем в posix системах. Пиком осмысления стало размещение 1 из моих приложений на 5 серверах, каждый из которых пришлось руками настраивать и прописывать конфиг файл (ну опять же, стоит юзать докер), и где-то на 3 или 4ой машине я вдруг задумался..что тот метод который использую я неочень удобен..и вот далее идут решения из разряда хорошей практики.

Разделение настроек внутри пакета на отедьные файлы.

Смысл достаточно прост, примерно тоже что пытался сделать я, только при запуске приложения выбираются нужные настройки, общие хранятся в файле одинаковы настроек (допустим base.py) рядом хранятся файлы dev.py и prod.py, разумеется их мб сколько угодно много для разных серверов. Плюсом тут играет то, что модули в питоне по своеему поведению напоминают классы, так что в случае чего можно и переопределить перменную в локальной области видимости, аналог наследования это импорты.

и так, для начала создаем папку settings (в той директории где у вас были settings.py), затем создаем в ней файлы base.py, dev.py и prod.py а также не забываем __init__.py (это же все таки пакет) Папка настроек должна выглядить так:

└── settings
    ├── __init__.py
    ├── base.py
    ├── dev.py
    ├── production.py
    └── test_server.py

Теперь сформируем base.py это файл с настройками идентичными во всех проектах:

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    SECRET_KEY = 'u unique secret key'
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'rest_framework',
        'rest_framework.authtoken',
        'myapp',
        'constance'
    ]
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': ('myapp/templates',),
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    WSGI_APPLICATION = 'myapp.wsgi.application'
    ROOT_URLCONF = 'myapp.urls'
    ...

Теперь можем импортировать модуль base.py в prod.py 

from settings.base import *    
    DEBUG = False
    ALLOWED_HOSTS = ['u_domain_name']
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'myapp',
            'USER': 'User',
            'PASSWORD': 'u_pass',
            'HOST': '127.0.0.1',
            'PORT': '5432',
        }
    }
    ...

И такой же для разрабоки, dev.py

from settings.base import *
    DEBUG = True
    ALLOWED_HOSTS = []
    MIDDLWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware',]
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': 'database_name',
            'USER': 'user_name',
            'PASSWORD': 'user_pas',
            'HOST': '127.0.0.1',
            'PORT': '5432',
        }
    }

Окей, с настройкой просто, как юзать? Запустить можно с указанием файла настроек:

python manage.py runserver --settings=myapp.settings.dev
# или так
python manage.py runserver --settings=myapp.settings.prod

Либо (вариант скорее для продакшена) изменить в файле manage.py

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings")
# на 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myapp.settings.development")

И на этом пожалуй все. Хочу сразу заявить что этот метод мне показал более опытный разработчик на англоязычном ресурсе и тут вы найдете гораздо более емкое описание этого метола, а также множество полезных кейсов для django и не только.

Использование django-configurations

А этот метод мне подсказали в тематическом паблике в телеграме, и на данный момент я использую именно его. Смысла что-то описывать по настройке нет, все детально разжовано в документации. Но, хочу упоминуть моменты которые ввели в ступор лично меня. При разработке проблем у меня не было, все достаточно ясно. Gunicorn подхватил настройки и запустил нормально приложение, тут главное не забыть отредактировать .wsgi файл вашего приложения, он должен выглядить так(взято с документации)

import os

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'mysite.settings')
os.environ.setdefault('DJANGO_CONFIGURATION', 'Dev')

from configurations.wsgi import get_wsgi_application

application = get_wsgi_application()

А также добавить в bash_profile, либо в bashrc пользователя из под которого запускате приложение:

export DJANGO_CONFIGURATION=Dev
export DJANGO_SETTINGS_MODULE=mysite.settings

А вот с чем у меня действительно возникли проблемы (и на самом деле это единственная причина по которой я сел писать этот туториал) это supervisor, при запуске через него (и я напомню, gunicorn запускался вполне нормально) у меня возникала следующая ошибка в лог файле:

Configuration cannot be imported, environment variable DJANGO_SETTINGS_MODULE is undefined

И по описанию ошибки вполне понятно что не так, но почему так происходит я понятия не имею (если вы знаете напишите мне ;)), сперва я нашел старый топик (13ого года) о подобной проблеме, и решением было добавить в конфиг файл супервайзера окружение среды, что собственно я и сделал:

# /etc/supervisor/conf.d/myapp.conf
environment=
    DJANGO_CONFIGURATION=Dev
    DJANGO_SETTINGS_MODULE=MySite.settings

На что лог ошибок в свою очереь заявил:

gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>

Окей после нескольких часов гугления проблемы и выискивание неисправностей не в тех местах я приешл к тому что добавил в gunicorn.conf.py прямое указание переменных окружения среды:

raw_env = [
    "DJANGO_CONFIGURATION=Dev",
    "DJANGO_SETTINGS_MODULE=myapp.settings",
]

После этого все запустилось. Я честным обарзом пытался выяснить почему супервайзер не видел их изначально, но нагуглить мне так и не удалось, как и выяснить это на тостере

DRF
Django