可插拔应用程序的 Django 默认设置约定?

Posted

技术标签:

【中文标题】可插拔应用程序的 Django 默认设置约定?【英文标题】:Django default settings convention for pluggable app? 【发布时间】:2012-01-15 17:54:03 【问题描述】:

如果settings.py 中未定义默认设置,那么在应用程序中处理默认设置的 djangonautic 方式是什么?

我目前在应用程序中放置了一个default_settings 文件,并且我考虑了一些选项。我倾向于第一个选项,但在使用globals() 时可能存在我不知道的陷阱

我经常看到应用程序在使用该设置的文件顶部执行FOO = getattr(settings, 'FOO', False),但我认为如果值/名称很长,这种方法存在可读性/重复问题。


1:将设置放在函数中并迭代局部变量/设置全局变量

def setup_defaults():
    FOO = 'bar'
    for key, value in locals().items():
        globals()[key] = getattr(settings, key, value)

setup_defaults()

优点:

只需编写一次 var name 即可从 django 设置中提取同名的默认值。

缺点:

不习惯使用 globals() 也不知道有什么影响

2:每次通话都写getattr(settings, 'MY_SETTING', default_settings.MY_SETTING)

优点: - 很清楚。

缺点:- 重复


3:始终将设置定义为FOO = getattr(settings, 'FOO', '...setting here...')

优点: - 默认值总是被覆盖

缺点:

重复(必须定义 var 两次 - 一次以字符串形式,一次以 var 形式) 设置不那么可读,因为它现在是第三个参数

4:为get_or_default(setting)创建实用函数

优点:

简单 不必重复设置字符串表示

缺点:

必须调用它

5:创建设置类

class Settings(object):
    FOO = 'bar'

    def __init__(self):
         # filter out the startswith('__') of 
         # self.__dict__.items() / compare to django.conf.settings?

my_settings = Settings()

缺点:

不能 from foo.bar.my_settings import FOO(实际上,这是一个糟糕的交易破坏者!)

我很想听听反馈。

【问题讨论】:

@DrTyrsa,哈哈,好点子!我想出了一个骗局。我们只是说它在视觉上与其他常量不一致.. 这些在 Django 1.11 中似乎都不再适用了。 Djangonautic,+1 【参考方案1】:

我认为在您的应用程序包中创建 settings.py 是很常见的,您可以在其中定义如下设置:

from django.conf import settings
FOO = getattr(settings, 'FOO', "default_value")

在您的应用中,您可以从应用的 settings 模块导入它们:

from myapp.settings import *

def print_foo():
    print FOO

但我认为每个人都同意 Django 缺乏更好的通用架构!如果您正在寻找一种更复杂的方法来处理这个问题,有一些第三方应用程序可以解决这个问题,例如 django-appconf,但您是否想为您的应用程序引入更多依赖项是您的决定!

2020 年更新

settings.py 中,将settings.* 放在属性之前。

from django.conf import settings
settings.FOO = getattr(settings, 'FOO', "default_value")

【讨论】:

优秀的答案。这也可以扩展到models.py,参见this tutorial for an overview。 虽然这很好,但我在测试使用该设置的代码时遇到了问题。 django.test.override_settings 似乎不能很好地使用这种方法 - 如果我覆盖测试的设置,这不会被我从 myapp.settings 导入的代码拾取。 @PhilGyford 在这里也一样。我通过在主要的django.conf.settings 对象上设置我的MYAPP_SETTING 来解决这个问题。然后,在myapp 代码中,我将始终导入django.conf.settings,并从那里使用MYAPP_SETTING。它可以工作,但是,在运行时修改设置对象......不知道这是否可以 - 像 django-appconf 这样的解决方案可能会更好地处理这个问题? 另外,对于那些不想要其他依赖项的人:reload(settings)(您自己的设置)...在覆盖块内,你很好! 或代替reload 只需使用AppConfig.ready,如另一个答案中所述。真正的节省者。【参考方案2】:

似乎我在那里看到的每个解决方案都倾向于创建应用程序设置、代理、包装或其他任何内容的内部副本。当设置为 modified in run time like they do in tests 时,这会造成混淆并产生问题。

对我来说,所有设置都属于django.conf.settings,并且只存在于那里。您不应从其他地方阅读它们,也不应将其复制以备后用(因为它们可能会更改)。您应该设置它们一次,以后不要担心默认值。

我理解在内部使用应用设置时删除应用前缀的冲动,但这也是恕我直言的一个坏主意。在寻找SOME_APP_FOO 时遇到麻烦不会产生结果,因为它在内部与FOO 一样使用。令人困惑对吗?为了什么,几个字母?还记得that explicit is better吗?

恕我直言,最好的方法是在 Django 自己的设置中设置这些默认值,为什么不使用已经存在的管道?没有模块导入钩子或劫持models.py 总是被导入来初始化一些额外和复杂的元类管道。

为什么不使用AppConfig.ready 来设置默认值?

class FooBarConfig(AppConfig):
    name = 'foo_bar'

    def ready(self):
        from django.conf import settings
        settings = settings._wrapped.__dict__
        settings.setdefault('FOO_BAR_SETTING', 'whatever')

或者更好的是在一个单独的模块中以干净简单的方式定义它们并将它们导入(或接近如何)Settings class 这样做:

class FooBarConfig(AppConfig):
    name = 'foo_bar'

    def ready(self):
        from . import app_settings as defaults
        from django.conf import settings
        for name in dir(defaults):
            if name.isupper() and not hasattr(settings, name):
                setattr(settings, name, getattr(defaults, name))

我不确定使用__dict__ 是不是最好的解决方案,但你明白了,你可以随时使用hasattr/setattr 组合来获得效果。

这样你的应用设置是:

    暴露给其他人——如果他们在极少数情况下应该依赖它们,如果应用程序当然被配置为相互依赖 像任何其他设置一样正常读取 在他们自己的模块中很好地声明 够懒 控制它们在 django.conf.settings 中的设置方式——如果你愿意,你可以实现一些名称的转置

PS。有一个warning about not modifying settings in run time,但它没有解释原因。所以我认为这一次,在初始化期间可能是一个合理的例外;)

PS2。不要将单独的模块命名为 settings,因为当您从 django.conf 导入 settings 时,这可能会让人感到困惑。

【讨论】:

不知道为什么这没有更多的赞成票。绝对是唯一的出路。如果app_settings.py 不适用于单元测试,那么这不是一个解决方案.. 这个解决方案完美(使用hasattrsetattr 在运行时不修改设置与多线程应用程序有关。如果您知道自己在做什么,那么在运行时修改设置并没有什么坏处。 优秀的解决方案!我会以此激励自己 但是如果您想通过自定义应用程序context_preprocessor 在应用程序模板中提供app_settings 怎么办?【参考方案3】:

这个怎么样?

在 myapp/settings.py 中:

from django.conf import settings

FOO = 'bar'
BAR = 'baz'

_g = globals()
for key, value in _g.items():
    _g[key] = getattr(settings, key, value)

在 myapp/other.py 中:

import myapp.settings

print myapp.settings.FOO

鉴于 ncoghlan 的 this answer,我觉得以这种方式使用 globals() 没问题。

【讨论】:

这不是在全局变量上设置各种东西【参考方案4】:

针对 Phil Gyford 的 comment,暴露了设置没有在测试中覆盖的问题(因为已经导入到模块中),我所做的是在 __init__.py 中定义一个 AppSettings 类:

__init__ 方法将每个设置初始化为None load 方法从 getter 加载所有设置 每个设置的静态吸气剂

然后在代码中:

from . import AppSettings

def in_some_function():
    some_setting = AppSettings.get_some_setting()

或者,如果您想一次性加载它们(但测试中的覆盖设置不适用于受影响的模块):

from . import AppSettings

app_settings = AppSettings()
app_settings.load()

def in_some_function():
   print(app_settings.some_setting)

然后,您可以在测试中使用 override_settings 装饰器,并且仍然有一些使用应用程序设置的干爽和清晰的方式,但每次您想要获取设置时都会执行更多指令(仅用于测试.. .).

【讨论】:

+1 表示努力为这个非常具体的问题编写答案。但是,好像有点复杂?请参阅我的评论,了解轻量级(但可能是不好的做法?)方法:***.com/questions/8428556/… @benzkji 你是对的,它有点复杂,在这里查看这个答案的增强版本:github.com/Genida/django-appsettings【参考方案5】:

您可以使用django-zero-settings,它可以让您定义您的默认值和用户设置的设置键以自动覆盖默认值、自动导入字符串、删除设置管理、缓存、预检查等。

像您的示例一样创建应用设置:

from zero_settings import ZeroSettings

app_settings = ZeroSettings(
    key="APP",
    defaults=
        "FOO": "bar"
    ,
)

那么你可以像这样使用它:

from app.settings import app_settings

print(app_settings.FOO)  # which prints bar

用户设置将自动覆盖默认设置,例如:

# this is settings.py, Django settings file
SECRET_KEY = "some_key"
# other settings ...
# the key `APP` is same key arg for ZeroSettings
APP = 
    "FOO": "not_bar"

然后:

from app.settings import app_settings

print(app_settings.FOO)  # this time you get not_bar

【讨论】:

【参考方案6】:

3 号是最好的,因为它是最简单的。而且外观非常一致。

第 1 点:很容易被忽视。如果我打开你的代码并且我不会滚动到底部,我会错过它,我会认为不能在我自己的模块中覆盖这些设置。

数字 2:不仅重复,而且因为太长而难以阅读,而且默认值将被多次定义并分散在您的代码中。

第 4 点:外观不一致、重复调用。

数字 5:不一致,我们希望设置在模块中而不是在类中定义。好吧,至少我确实希望 find 被定义为模块,因为我已经看到很多应用程序使用方法 3,并且我自己使用它,所以我可能会有偏见。

【讨论】:

评论#1,在顶部添加评论即可轻松解决! 1号和2号哪个?您可以将您的 cmets 留在引用的答案中

以上是关于可插拔应用程序的 Django 默认设置约定?的主要内容,如果未能解决你的问题,请参考以下文章

构建可插拔应用程序:如何包含流行库的分支并防止名称冲突?

21 参考中间件,实现可插拔式设计

阿里开源可插拔 React 跨端框架- UmiJS

Openstack Horizon 可插拔Panels 和 Groups

Openstack Horizon 可插拔Panels 和 Groups

Foundatio - .Net Core用于构建分布式应用程序的可插拔基础块