Skip to main content
 首页 » 编程设计

python之模型使用时覆盖 Django 中的设置

2024年08月14日45thcjp

我们正在为 Speedy Net and Speedy Match 使用 Django (当前 Django 2.1)。我们的一些设置被模型使用。例如:

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)。

问题是,当我尝试在测试中覆盖此类设置时(您可以在 Can I define classes in Django settings, and how can I override such settings in tests? 上查看我的问题和答案),User.settings 保持不变并且不会被设置覆盖我试图覆盖。这是一个问题,因为在模型中我将 settings.MIN_SLUG_LENGTH 传递给验证器,其他模型也将其他值传递给验证器。是否可以以在生产和测试中都使用正确设置的方式定义模型和设置,包括我想覆盖它们的时间?

我知道这句话来自 https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings :

Warning

The settings file contains some settings that are only consulted during initialization of Django internals. If you change them with override_settings, the setting is changed if you access it via the django.conf.settings module, however, Django’s internals access it differently. Effectively, using override_settings() or modify_settings() with these settings is probably not going to do what you expect it to do.

We do not recommend altering the DATABASES setting. Altering the CACHES setting is possible, but a bit tricky if you are using internals that make using of caching, like django.contrib.sessions. For example, you will have to reinitialize the session backend in a test that uses cached sessions and overrides CACHES.

Finally, avoid aliasing your settings as module-level constants as override_settings() won’t work on such values since they are only evaluated the first time the module is imported.

据我所知,在这种情况下哪些是相关的,但我如何以可以覆盖它们的方式定义设置?

函数_1___set_up在 speedy/core/base/test/models.py 中是使测试工作的解决方法,但这是一种 hack,我认为从长远来看这不是一个好的解决方案。

请您参考如下方法:

问题,如你所引:

avoid aliasing your settings as module-level constants as override_settings() won’t work on such values since they are only evaluated the first time the module is imported.

有 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) 

方式二:给设置类打补丁,让实例读取django_settings

不推荐; 在生产中也会影响 USER_SETTINGS 的运行时评估,而不仅仅是在测试中 (@hynekcer)。

  • 优点:模型中没有代码更改。
  • 缺点:表达能力最差,更难调试。

  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): 

方式三:创建信号接收者setting_changed更新别名

  • 优点:对模型的代码更改最少。
  • 缺点:对于警告中的依赖属性,表现力不如方式 1。

来自 https://docs.djangoproject.com/en/dev/topics/testing/tools/#overriding-settings :

When overriding settings, make sure to handle the cases in which your app’s code uses a cache or similar feature that retains state even if the setting is changed. Django provides the django.test.signals.setting_changed signal that lets you register callbacks to clean up and otherwise reset state when settings are changed.

Django itself uses this signal to reset various data.

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