我可以在 Django 设置中定义类吗?如何在测试中覆盖这些设置?

Posted

技术标签:

【中文标题】我可以在 Django 设置中定义类吗?如何在测试中覆盖这些设置?【英文标题】:Can I define classes in Django settings, and how can I override such settings in tests? 【发布时间】:2019-05-25 08:41:43 【问题描述】:

我们将 Django 用于Speedy Net and Speedy Match(当前为 Django 1.11.17,由于我们的要求之一 django-modeltranslation,我们无法升级到更新版本的 Django)。我想将我们的一些设置定义为类。例如:

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 中定义,我该如何覆盖它?

【问题讨论】:

为什么要使用类?为什么不是字典? 我想是因为有了一个类,我可以继承它们并且只改变我想要的。但是可以用字典做类似的事情。类在代码中也更具可读性。无论如何,我想知道如何用字典覆盖设置。 @Uri:看看modify_settings 是如何处理列表的,以及如何处理字典的类似操作。如果你想做“继承”,我发现制作一个settings 模块并创建子模块也可以,settings/__init__.pyfrom .submodule import *; OVERRIDE_THING = True 来模拟继承。 【参考方案1】:

感谢@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的测试。

【讨论】:

【参考方案2】:

Django 不希望您对其低级设计选择有太大的偏离,而且通常很难解决 Django 未明确允许您自定义的事情。

    Django 的设置对象explicitly skips over 设置模块中具有非大写名称的任何对象。如果您将班级重命名为USER_SETTINGS,它将起作用。如果您真的想要保留对象的原始名称,那么一个可怕的解决方案就是欺骗 Django:

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

    我不知道这是否可以跨 Python 实现移植,但它适用于 CPython 的 dir()

    override_settings 不支持您尝试执行的操作,因此您可能需要重写该类以允许可配置全局 settings 对象。

【讨论】:

以上是关于我可以在 Django 设置中定义类吗?如何在测试中覆盖这些设置?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 ASP.NET MVC3 中将自定义类设置为我的控制器的默认基类吗?

如何在 Django 中使用不同的设置进行单元测试?

我可以动态设置html类吗?或者 Shiny 是如何设置 'html'.hasClass('Shiny-busy') 的?

我可以使用 OCMock 测试是不是调用了超类吗?

Logback - 你能从 env 变量中定义 appender 名称和类吗?

单元测试,请问我mock一个接口需要写该接口的实现类吗?