Django 可插拔应用的默认设置规范?
如果在 settings.py
中没有定义某个默认设置,处理这个问题的“django风格”方法是什么呢?
我现在在应用里放了一个 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()
优点:
- 只需写一次变量名,就能从django设置中提取同名的默认值。
缺点:
- 不习惯使用
globals()
,不知道会有什么影响。
2: 每次调用都写 getattr(settings, 'MY_SETTING', default_settings.MY_SETTING)
优点: - 非常清晰。
缺点: - 重复。
3: 总是定义设置为 FOO = getattr(settings, 'FOO', '...这里是默认设置...')
优点: - 默认值总是会被覆盖。
缺点:
- 重复(必须定义变量两次 - 一次是字符串形式,一次是变量形式)。
- 设置的可读性降低,因为现在是第三个参数。
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
(实际上,这真的是个致命的缺陷!)
我很想听听大家的反馈。
6 个回答
这样怎么样?
在我的应用程序的设置文件 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 的这个回答,我觉得这样使用 globals() 是可以的。
我发现这里的每个解决方案似乎都倾向于创建应用设置、代理或其他东西的内部副本。这让人感到困惑,并且在运行时修改设置时(就像在测试中那样)会产生问题,修改设置。
在我看来,所有的设置都应该放在 django.conf.settings
里,只有这里。你不应该从其他地方读取它们,也不应该为了后续使用而复制它们(因为它们可能会改变)。你应该一次性设置好它们,之后就不用再担心默认值了。
我理解在内部使用应用设置时想去掉应用前缀的冲动,但在我看来这也是个坏主意。当你在找 SOME_APP_FOO
时,可能找不到结果,因为它在内部只是用 FOO
。这不是很让人困惑吗?为了几个字母而这样做?记得 明确更好 吗?
在我看来,最好的方法就是在 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 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
中的设置方式——如果你想的话,可以实现一些名称的转换
附言:有一个关于在运行时不修改设置的警告,但没有解释原因。所以我认为在初始化期间这一次可能是个合理的例外;)
附言2:不要把单独的模块命名为 settings
,因为在从 django.conf
导入 settings
时可能会让人困惑。
我觉得在你的应用包里创建一个 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")