Переопределение настроек в Django при использовании моделями
Мы используем Django для Speedy Net и Speedy Match (в настоящее время Django 1.11.17, мы не можем перейти на более новую версию Django из-за одного из наших требований, django-modeltranslation). Некоторые из наших настроек используются моделями. Например:
class USER_SETTINGS(object):
MIN_USERNAME_LENGTH = 6
MAX_USERNAME_LENGTH = 40
MIN_SLUG_LENGTH = 6
MAX_SLUG_LENGTH = 200
# Users can register from age 0 to 180, but can't be kept on the site after age 250.
MIN_AGE_ALLOWED_IN_MODEL = 0 # In years.
MAX_AGE_ALLOWED_IN_MODEL = 250 # In years.
MIN_AGE_ALLOWED_IN_FORMS = 0 # In years.
MAX_AGE_ALLOWED_IN_FORMS = 180 # In years.
MIN_PASSWORD_LENGTH = 8
MAX_PASSWORD_LENGTH = 120
MAX_NUMBER_OF_FRIENDS_ALLOWED = 800
PASSWORD_VALIDATORS = [
{
'NAME': 'speedy.core.accounts.validators.PasswordMinLengthValidator',
},
{
'NAME': 'speedy.core.accounts.validators.PasswordMaxLengthValidator',
},
]
(это определено в https://github.com/speedy-net/speedy-net/blob/staging/speedy/net/settings/global_settings.py). И тогда в моделях я использую:
from django.conf import settings as django_settings
class User(ValidateUserPasswordMixin, PermissionsMixin, Entity, AbstractBaseUser):
settings = django_settings.USER_SETTINGS
(а затем использовать атрибуты settings
, такие как settings.MIN_SLUG_LENGTH
, в классе).
Проблема в том, что когда я пытаюсь переопределить такие настройки в тестах (мой вопрос и ответ вы можете увидеть в разделе " Можно ли определить классы в настройках Django и как я могу переопределить такие настройки в тестах?"), User.settings
остается прежним и не переопределяется настройками, которые я пытался переопределить. Это проблема, так как в модели, которую я прошел settings.MIN_SLUG_LENGTH
например, для валидаторов, которым другие модели также передают другие значения. Можно ли определить модели и настройки таким образом, чтобы правильные настройки использовались как в производстве, так и в тестах, в том числе когда я хочу их переопределить?
Мне известна эта цитата с https://docs.djangoproject.com/en/dev/topics/testing/tools/:
Предупреждение
Файл настроек содержит некоторые настройки, с которыми можно ознакомиться только при инициализации внутренних компонентов Django. Если вы измените их с помощью override_settings, настройка изменится, если вы получите к ней доступ через модуль django.conf.settings, однако, внутренние компоненты Django обращаются к нему по-другому. По сути, использование override_settings() или modify_settings() с этими настройками, вероятно, не даст ожидаемого результата.
Мы не рекомендуем изменять настройку DATABASES. Изменение параметра CACHES возможно, но немного сложнее, если вы используете внутренние компоненты, которые используют кэширование, например django.contrib.sessions. Например, вам нужно будет повторно инициализировать серверную часть сеанса в тесте, который использует кэшированные сеансы и переопределяет CACHES.
Наконец, избегайте наложения ваших настроек на константы уровня модуля, поскольку override_settings() не будет работать с такими значениями, поскольку они оцениваются только при первом импорте модуля.
Что, как я понимаю, в данном случае уместно, но как определить параметры таким образом, чтобы я мог их переопределить?
0 ответов
Проблема, как вы цитировали:
избегайте псевдонимов ваших настроек как констант уровня модуля, как
override_settings()
не будут работать с такими значениями, поскольку они оцениваются только при первом импорте модуля.
Есть 3 способа обойти это, где Путь 1 > Путь 3 > Путь 2.
Способ 1: не использовать псевдоним с атрибутом класса, но classproperty
Рекомендуемые; возможно правильный путь.
- Плюсы: Наиболее выразительно, легче отлаживать.
- Против: больше кода в модели.
from django.utils.decorators import classproperty
class User(PermissionsMixin, Entity, AbstractBaseUser):
# settings = django_settings.USER_SETTINGS
@classproperty
def settings(cls):
return django_settings.USER_SETTINGS
Предупреждение: атрибуты класса, зависящие отsettings
Атрибут класса не будет работать.
Хотя способ 2 позволяет следующий код оставаться действительным, они оцениваются во время определения (импорта) класса и не могут быть разумно изменены в зависимости отoverride_settings()
, если они не classproperty
тоже.
AGE_VALID_VALUES_IN_MODEL = range(settings.MIN_AGE_ALLOWED_IN_MODEL, settings.MAX_AGE_ALLOWED_IN_MODEL)
AGE_VALID_VALUES_IN_FORMS = range(settings.MIN_AGE_ALLOWED_IN_FORMS, settings.MAX_AGE_ALLOWED_IN_FORMS)
Способ 2: класс настроек патча для чтения экземпляров django_settings
Не рекомендуется; влияет на оценку времени выполнения USER_SETTINGS также в производственной среде, а не только в тестах (@hynekcer).
- Pro: Код модели не меняется.
- Против: Наименее выразительный, труднее отлаживать.
- Определите функцию
overridable_settings
:
def overridable_settings(settings_class):
old__getattribute__ = settings_class.__getattribute__
settings_name = settings_class.__name__
def patched__getattribute__(_self, item):
from django.conf import settings as django_settings
settings = getattr(django_settings, settings_name)
return old__getattribute__(settings, item)
settings_class.__getattribute__ = patched__getattribute__
return settings_class()
django_settings.USER_SETTINGS
теперь является экземпляром класса настроек. Вместо тогоget_django_settings_class_with_override_settings
, определитьoverride_settings
:
import copy
def override_settings(settings, **overrides):
copied_settings = copy.deepcopy(settings)
for setting, value in overrides.items():
setattr(copied_settings, setting, value)
assert copied_settings != settings
return copied_settings
Применение:
@overridable_settings
class USER_SETTINGS(object):
from speedy.core.base.test import utils
# @override_settings(USER_SETTINGS=get_django_settings_class_with_override_settings(django_settings_class=django_settings.USER_SETTINGS, MIN_SLUG_LENGTH=tests_settings.OVERRIDE_USER_SETTINGS.MIN_SLUG_LENGTH))
@override_settings(USER_SETTINGS=utils.override_settings(django_settings.USER_SETTINGS, MIN_SLUG_LENGTH=tests_settings.OVERRIDE_USER_SETTINGS.MIN_SLUG_LENGTH))
def test_slug_min_length_fail_username_min_length_ok(self):
Способ 3: создать приемник сигнала setting_changed
обновить псевдоним
- Плюс: минимальное изменение кода модели.
- Против: Менее выразительно, чем способ 1, в отношении зависимых атрибутов в Caveat.
Из https://docs.djangoproject.com/en/dev/topics/testing/tools/:
При переопределении параметров не забудьте обработать случаи, когда код вашего приложения использует кеш или аналогичную функцию, которая сохраняет состояние, даже если параметр изменен. Django предоставляет
django.test.signals.setting_changed
сигнал, который позволяет вам регистрировать обратные вызовы для очистки и иного сброса состояния при изменении настроек.Сам Django использует этот сигнал для сброса различных данных.
from django.core.signals import setting_changed
from django.dispatch.dispatcher import receiver
def register_django_setting_alias(setting_alias, django_setting):
def decorator(cls):
@receiver(setting_changed, weak=False)
def update_setting_alias(setting, value, **_):
if setting == django_setting:
setattr(cls, setting_alias, value)
return cls
return decorator
Применение:
@register_django_setting_alias('settings', 'USER_SETTINGS')
class User(PermissionsMixin, Entity, AbstractBaseUser):
settings = django_settings.USER_SETTINGS