M
M
mkone1122020-11-12 07:05:25
Django
mkone112, 2020-11-12 07:05:25

What is the optimal Django config structure?

I am learning Django. I immediately ran into several problems when configuring - by default (when creating a project), the framework stores all settings in one file (settings.py).

Pros:

  • Simple and clumsy.

Minuses:
  • There is no division into development/production configurations.
  • settings.py is healthy, the more the project grows, the more difficult it is to maintain.
  • settings.py should be stored in the vcs, but it contains sensitive data that should not be in the vcs.
  • For any configuration change, you need to climb into the files.

Attempt zero

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

Разделил 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 вместо одного.
  • Читабельность настроек сильно — я бы даже сказал фатально — упала.



I tried for a long time to get around these problems, and after I stumbled upon dynaconf, I came up with the following structure:

# .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'



Pros:
  • Easier/more readable.
  • Separation into development/production.
  • Everything except the keys is stored in vcs.
  • Settings are easily changed on the fly.

Optional:
  • Settings can be stored anywhere - in environment variables, configuration files of various formats (yaml, toml, ini, etc), database, etc.

Minuses:
  • Not very common.
  • Still 4 files instead of one.


Question: Is my solution adequate? If the approach has disadvantages?

Answer the question

In order to leave comments, you need to log in

1 answer(s)
D
Dr. Bacon, 2020-11-12
@mkone112

I used to use several files, it was inconvenient for me. Now one settings.py, but all differences are set through environment variables.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question