DEV Community

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

Posted on • Edited on

Настройки приложений 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 

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 "ослепнут", т.к. имена полей будут генерироваться динамически.