Могу ли я определить классы в настройках Django и как я могу переопределить такие настройки в тестах?

Мы используем Django для Speedy Net и Speedy Match (в настоящее время Django 1.11.17, мы не можем перейти на более новую версию Django из-за одного из наших требований, django-modeltranslation). Я хочу определить некоторые из наших настроек как классы. Например:

class UserSettings(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

    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.UserSettings

(а затем использовать атрибуты settings, такие как settings.MIN_USERNAME_LENGTH, в классе).

Но это исключение

AttributeError: 'Settings' object has no attribute 'UserSettings'

(но это не выдает исключение, если я использую там константу, которая не является классом).

Это первая проблема. Тем временем я определил вместо этого:

from speedy.net.settings import global_settings as speedy_net_global_settings

class User(ValidateUserPasswordMixin, PermissionsMixin, Entity, AbstractBaseUser):
    settings = speedy_net_global_settings.UserSettings

Вторая проблема, как мне переопределить такие настройки в тестах? Например, я использую следующий код:

from speedy.core.settings import tests as tests_settings

@override_settings(MAX_NUMBER_OF_FRIENDS_ALLOWED=tests_settings.OVERRIDE_MAX_NUMBER_OF_FRIENDS_ALLOWED)

в https://github.com/speedy-net/speedy-net/blob/staging/speedy/core/friends/tests/test_views.py. Но если MAX_NUMBER_OF_FRIENDS_ALLOWED будет определен в классе UserSettingsкак мне переопределить это?

2 ответа

Решение

Спасибо @Blender за подсказку:

Объект настроек Django явно пропускает любые объекты в вашем модуле настроек с не прописными именами. Если вы переименуете свой класс в USER_SETTINGS, он будет работать.

Я не знал, что все настройки должны быть прописными. Поэтому я переименовал class UserSettings в class USER_SETTINGS (хотя PyCharm это не нравится), но я проверил и также можно добавить этот код в конец файла:

USER_SETTINGS = UserSettings

Без переименования класса.

Что касается моего второго вопроса - как мне переопределить такие настройки в тестах? Я добавил файл с именем utils.py:

def get_django_settings_class_with_override_settings(django_settings_class, **override_settings):
    class django_settings_class_with_override_settings(django_settings_class):
        pass

    for setting, value in override_settings.items():
        setattr(django_settings_class_with_override_settings, setting, value)

    return django_settings_class_with_override_settings

(Вы можете увидеть это на https://github.com/speedy-net/speedy-net/blob/staging/speedy/core/base/test/utils.py)

А потом в тестах:

from django.conf import settings as django_settings
from django.test import override_settings

from speedy.core.settings import tests as tests_settings
from speedy.core.base.test.utils import get_django_settings_class_with_override_settings

    @override_settings(USER_SETTINGS=get_django_settings_class_with_override_settings(django_settings_class=django_settings.USER_SETTINGS, MAX_NUMBER_OF_FRIENDS_ALLOWED=tests_settings.OVERRIDE_USER_SETTINGS.MAX_NUMBER_OF_FRIENDS_ALLOWED))
    def test_user_can_send_friend_request_if_not_maximum(self):
        self.assertEqual(first=django_settings.USER_SETTINGS.MAX_NUMBER_OF_FRIENDS_ALLOWED, second=4)

Я проверил, и я должен определить другой класс (в этом случае, class django_settings_class_with_override_settings потому что если я изменю класс django_settings_class непосредственно это также влияет на другие тесты, которые не использовали @override_settings,

Django не ожидает, что вы сильно отклонитесь от его низкоуровневых дизайнерских решений, и, как правило, трудно обойти вещи, которые Django явно не позволяет настраивать.

  1. Объект настроек Django явно пропускает любые объекты в вашем модуле настроек с не прописными именами. Если вы переименуете свой класс в USER_SETTINGS, это будет работать. Если вы действительно хотите сохранить оригинальное имя вашего объекта, ужасным решением было бы обмануть Джанго:

    class UserSettings:
        ...
    
    class AlwaysUppercaseStr(str):
        def isupper(self):
            return True
    
    globals()[AlwaysUppercaseStr('UserSettings')] = globals().pop('UserSettings')
    

    Я понятия не имею, является ли это переносимым между реализациями Python, но он работает с CPython dir(),

  2. override_settings не поддерживает то, что вы пытаетесь сделать, поэтому вам, вероятно, придется переписать этот класс, чтобы settings объект для настройки.

Другие вопросы по тегам