DEV Community

Гимаев Наиль
Гимаев Наиль

Posted on • Edited on

2 1

Настройки приложений Django

Вводная

Для всех последующих примеров кода предполагается, что выполнен импорт settings

from django.conf import settings

Проблема

Если мы захотим использовать какую-то из настроек django в своём коде без упоминания его в settings.py, то проблем не возникнет.

# Обычно никто не задаёт значение для CSRF_COOKIE_AGE, 
print(settings.CSRF_COOKIE_AGE) # никаких ошибок, всё работает

Часто для своего приложения нужны свои настройки1. Чтобы приложение не упало из-за отсутствия настроек в settings.py нужны проверки и подстановка значения по умолчанию.

print(getattr(settings, 'MY_APP_PARAM_1', default_value_of_param_1))

Выглядит как-то не очень, но если учесть, что одна и та же настройка может использоваться в нескольких местах, то код преображается в нечто такое:

# my_app/conf.py
default = {
    'MY_APP_PARAM_1': default_value_of_param_1,
    # Представьте, что здесь перечислены остальные настройки
}
def get_param_value(param_name):
    return getattr(settings, param_name, default_value_of_param_1)

# my_app/any_place.py
from .conf import get_param_value
print(get_param_value('MY_APP_PARAM_1')) # выглядит не очень, но работает
print(get_param_value('MY_APP_PARAM_L')) # IDE не помешает нам ошибиться

Хочется, чтобы работа с настройками была одинаковой для всех приложений, чтобы вид настройки был похож на settings.MY_APP_PARAM_1 и чтобы работало автодополнение.

Решение

Я не стал придумывать велосипед, а взял чужой. Подозреваю, что на просторах PyPi можно найти ещё с десяток, поэтому свой туда забрасывать не стал, хотя найти аналогичный мне там не удалось.
Скачать результат можно на github gist
Код в основном взят из django-rest-framework, из него выкинуто всё, что мешает переиспользованию. Файлик кладётся в папку доступную для всех приложений django. У меня это папка utils.
Посмотрим как это работает

# my_app\settings.py
from utils.django import AppSettings

# Описание настроек приложения.
# 1. Все поля находятся в одном месте
# 2. Видны все значения по умолчанию
# 3. При желании можно добавить типизацию
class MyAppSettings(AppSettings):
    URL = ''
    USER: str = ''
    PASSWORD = ''

# Объект с инициализированными данными 
# 1. Достаточно написать "conf." в любом IDE
#    и редактор сам предложит название настройки
# 2. Все обращения приложения к настройкам должны
#    происходить через этот объект, тогда не будет риска,
#    что приложение "сломается" из-за внешних факторов
conf = MyAppSettings('MY_APP_CONF')
# django_project\settings.py
# ...

# Настройки Django-проекта
# 1. Могут быть заданны частично
# 2. Могут быть совсем не заданы
# 3. Могут быть заданы явно, без использования .env
MY_APP_CONF = {
    'URL': env('MY_APP_URL', default=''),
    'USER': env('MY_APP_USER', default=''),
    'PASSWORD': env('MY_APP_PASSWORD', default=''),
}
# my_app\using.py
from .settings import conf
# Здесь создаётся условный клиент с подключением к некому сервису
client = get_data(conf.URL, conf.USER, conf.PASSWORD)

На что следует обратить внимание:

  1. Описание настроек приложения и значений по умолчанию делается очень просто.
  2. Все настройки приложения задаются в одном словаре, т.е. не будут размазаны по коду и не нужны префиксы к каждой настройке.
  3. Не нужны проверки на существование при использовании настроек. В худшем случае будет значение по умолчанию.

  1. Я называю настройкой константу, которая влияет на поведение программы и значение которой можно задать в settings.py 

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (2)

Collapse
 
flybot profile image
FlyBot

Дважды декларируются URL, USER, PASSWORDБ что есть неправильно
Правильно использовать декоратор

def apply_defaults(cls):
defaults = {
'default_value1':True,
'default_value2':True,
'default_value3':True,
}
for name, value in defaults.items():
setattr(cls, name, some_complex_init_function(value, ...))
return cls

@apply_defaults
class MyAppSettings(AppSettings):
pass

Collapse
 
gimntut profile image
Гимаев Наиль • Edited

Спасибо за замечание.


# my_app\settings.py
def apply_defaults(cls):
  defaults = {
    'default_value1':True,
    'default_value2':True,
    'default_value3':True,
  }
  for name, value in defaults.items():
    setattr(cls, name, some_complex_init_function(value, ...))
    return cls

@apply_defaults
class MyAppSettings(AppSettings):
  pass

conf = MyAppSettings('MY_APP_CONF')

К сожалению, предложенный тобой вариант не меняет ситуацию. my_app\settings.py теперь выглядит по другому, но django_project\settings.py остался прежним, а значит дублирование осталось. При этом IDE "ослепнут", т.к. имена полей будут генерироваться динамически.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay