@mkone112
Начинающий питонист.

Какова оптимальная структура конфигурации Django?

Изучаю Django. Сразу столкнулся с несколькими проблемами при конфигурировании - по умолчанию(при создании проекта) фреймворк хранит все настройки в одном файле(settings.py).

Плюсы:
  • Просто и топорно.

Минусы:
  • Нет разделения на development/production конфигурации.
  • settings.py здоровый, чем больше растёт проект - тем труднее его поддерживать.
  • settings.py должен храниться в vcs, но он содержит чувствительные данные, которые в vcs попадать не должны.
  • Для любого изменения конфигурации нужно лезть в файлы.

Попытка ноль

Сначала я попробовал воспользоваться стандартным решением:

Разделил settings.py на:
  • settings.base.py
  • settings.development.py
  • settings.production.py


И поместил их в отдельную директорию(/config).

Плюсы:
  • Файлы меньше - теперь поддерживать это немного проще
  • Разные конфигурации для разработки и деплоя.

Минусы:
  • Файлов стало побольше(но это терпимо).
  • Все еще нужно редактировать конфиги для изменения настроек.
  • Чувствительные данные все еще улетают в vcs.

Вынес чувствительные данные в development.env и production.env и добавил их в .gitignore
В settings.base.py эти данные загружаются в переменные окружения, и создается интерфейс для работы с ними:
import environ

env = environ.Env()

READ_DOT_ENV_FILE = env.bool("DJANGO_READ_DOT_ENV_FILE", default=False)
if READ_DOT_ENV_FILE:
			    # OS environment variables take precedence over variables from .env
			    env.read_env(str(ROOT_DIR.path(".env")))

Затем в settings.*.py использую эти значения или дефолтные:
"""Пример фрагмента settings.*.py"""

SECRET_KEY = env.str('SECRET_KEY', default=get_random_secret_key())

DEBUG = env.bool('DEBUG', default=False)

ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])

DATABASES = {'default': env.db('DATABASE_URL')}

STATIC_URL = env.str('STATIC_URL', default='/static/')

EMAIL_HOST = env.str('EMAIL_HOST', default='localhost')

EMAIL_PORT = env.int('EMAIL_PORT', default=25)

# Login for access to SMTP-server
EMAIL_HOST_USER = env.str('EMAIL_HOST_USER')

EMAIL_HOST_PASSWORD = env.str('EMAIL_HOST_PASSWORD')

EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True)

EMAIL_BACKEND = env.str(
    'EMAIL_BACKEND',
    default='django.core.mail.backends.smtp.EmailBackend'
)

Плюсы:
  • Разные конфиги для development/production.
  • Настройки можно легко менять на лету через переменные окружения.
  • Чувствительные данные хранятся отдельно.
  • Судя по гитхабу это весьма распространенное решение.

Минусы:
  • Не уверен, насколько это безопасно.
  • Много файлов - 5 вместо одного.
  • Читабельность настроек сильно — я бы даже сказал фатально — упала.



Я долго пытался обойти эти проблемы, и после того как наткнулся на dynaconf пришел к следующей структуре:

# .secrets.toml содержит все чувствительные данные(добавлен в .gitignore). Я решил что будет логично оставить
# его в корне проекта(мне спокойнее когда я не вижу его в корне публичного репозитория).
.secrets.toml
# Мне показалось рациональным добавить к директориям приложений префикс `app_`.
app_first/
app_second/
...
base_project_dir/
config/	            # Все конфиги проекта хранятся в одной отдельной директории.
├── __init__.py
├── settings/
│       ├── settings.py      # Базовые настройки
│       ├── development.toml
│       └── production.toml
├── urls.py
└── wsgi.py
...
manage.py


.secrets.toml

[development]
# General
# -----------------------------------------------------------------------------

SECRET_KEY = '<development_secret_key>'

[production]
# General
# -----------------------------------------------------------------------------

SECRET_KEY = '<production_secret_key>'


# PostgreSQL
# ------------------------------------------------------------------------------
DATABASES__default__USER = '<postgresql_username>'
DATABASES__default__PASSWORD='<postgresql_password>'



settings.py

import os
import dynaconf

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))


# Applications
#------------------------------------------------------------------------------

INSTALLED_APPS = [
    'account.apps.AccountConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

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',
]

ROOT_URLCONF = 'bookmarks.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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 = 'bookmarks.wsgi.application'


# Password validation
#------------------------------------------------------------------------------

# https://docs.djangoproject.com/en/2.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Log-in
#------------------------------------------------------------------------------

LOGIN_REDIRECT_URL = 'dashboard'

LOGIN_URL = 'login'

LOGOUT_URL = 'logout'


# Internationalization
#------------------------------------------------------------------------------

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


# Media
# -----------------------------------------------------------------------------

MEDIA_URL = '/media/'


settings = dynaconf.DjangoDynaconf(
    __name__,
    ENVVAR_PREFIX_FOR_DYNACONF='BOOKMARKS'
)  # noqa



development.toml

[development]
# General
# -----------------------------------------------------------------------------
DEBUG = true

ALLOWED_HOSTS = ["localhost", "0.0.0.0", "127.0.0.1"]


# Applications
# -----------------------------------------------------------------------------

# https://django-extensions.readthedocs.io/en/latest/
INSTALLED_APPS = ["django_extensions", 'dynaconf_merge']


# Database
#------------------------------------------------------------------------------

# https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES__default__ENGINE = 'django.db.backends.sqlite3'
DATABASES__default__NAME = '@format {this.BASE_DIR}/db.sqlite3'


# Static files
# -----------------------------------------------------------------------------

STATIC_URL = '/static/'


# Media files
# -----------------------------------------------------------------------------

MEDIA_ROOT = '@format {this.BASE_DIR}/media'


# Email
# -----------------------------------------------------------------------------

EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'



Плюсы:
  • Проще/читабельнее.
  • Разделение на development/production.
  • Все кроме ключей хранится в vcs.
  • Настройки легко меняются на лету.

Опционально:
  • Настройки можно хранить где-угодно - в переменных окружения, конфигурационных файлах различных форматов(yaml, toml, ini, etc), бд, etc.

Минусы:
  • Не особо распространено.
  • Все-еще 4 файла вместо одного.


Вопрос: является ли мое решение адекватным? Если у подхода недостатки?
  • Вопрос задан
  • 132 просмотра
Решения вопроса 2
@bacon
Раньше использовал несколько файлов, мне это было неудобно. Сейчас один settings.py, но все различия задаются через переменные окружения.
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы