Переопределение настроек в 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: Код модели не меняется.
  • Против: Наименее выразительный, труднее отлаживать.

  1. Определите функцию 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()
  1. 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
Другие вопросы по тегам