【发布时间】:2017-02-14 19:36:46
【问题描述】:
我正在寻找一种让应用程序默认值和设置易于使用、不易出错且开销很小的方法。
目前我将其组织如下:
myapp/defaults.py
# application defaults
import sys
if sys.platform == 'win32':
MYAPP_HOME_ROOT = os.path.dirname(os.environ['USERPROFILE'])
else:
MYAPP_HOME_ROOT = '/home'
在我的项目中,我有:
mysite/settings.py
from myapp.defaults import * # import all default from myapp
MYAPP_HOME_ROOT = '/export/home' # overriding myapp.defaults
通过此设置,我可以以常规 django 方式(from django.conf import settings 和 settings.XXX)导入和使用设置。
update-3(我们为什么需要这个)
- 默认设置(“默认值”):
- 如果可以通过覆盖一组合理的默认设置来配置应用程序,则使用起来会更方便。
- 应用程序“具有领域知识”,因此尽可能提供合理的默认值是有意义的。
- 应用程序的用户需要提供每个应用程序所需的所有设置并不方便,覆盖一小部分并保留其余部分的默认值就足够了。
- 如果默认值可以对环境做出反应,这非常很有用。当
DEBUG为 True 时,您通常会想做一些不同的事情,但任何其他全局设置都可能有用:例如MYAPP_IDENTICON_DIR = os.path.join(settings.MEDIA_ROOT, 'identicons')(https://en.wikipedia.org/wiki/Identicon) - 项目(站点/全局)设置必须覆盖应用程序默认值,即在(全局)
settings.py文件中为其站点定义MYAPP_IDENTICON_DIR = 's3://myappbucket.example.com/identicons'的用户应获取此值,而不是应用程序的默认值。 - 任何接近使用设置的正常方式 (
import .. settings; settings.FOO) 的解决方案都优于需要新语法的解决方案(因为新语法会出现分歧,我们将获得新的和独特的方式来在应用程序之间使用设置)。 - python 的禅意大概适用于这里:
- 如果实现难以解释,那就是个坏主意。
- 如果实现易于解释,那可能是个好主意。
(原帖提出以下两个关键问题,未说明上述假设..)
问题 #1: 但是,当为应用程序运行单元测试时,没有站点,因此 settings 不会有任何 myapp.defaults。
问题 #2: 如果myapp.defaults 需要使用设置中的任何内容(例如settings.DEBUG),也存在一个大问题,因为您无法从defaults.py 导入设置(因为这将是一个循环导入)。
为了解决问题 #1,我创建了一个间接层:
myapp/conf.py
from . import defaults
from django.conf import settings
class Conf(object):
def __getattr__(self, attr):
try:
return getattr(settings, attr)
except AttributeError:
return getattr(defaults, attr)
conf = Conf() # !<-- create Conf instance
及用法:
myapp/views.py
from .conf import conf as settings
...
print settings.MYAPP_HOME_ROOT # will print '/export/home' when used from mysite
这允许conf.py 文件也可以使用“空”设置文件,并且 myapp 代码可以继续使用熟悉的settings.XXX。
它不能解决问题 #2,根据例如定义应用程序设置settings.DEBUG。我目前的解决方案是添加到Conf 类:
from . import defaults
from django.conf import settings
class Conf(object):
def __getattr__(self, attr):
try:
return getattr(settings, attr)
except AttributeError:
return getattr(defaults, attr)
if settings.DEBUG:
MYAPP_HOME_ROOT = '/Users'
else:
MYAPP_HOME_ROOT = '/data/media'
conf = Conf() # !<-- create Conf instance
但这并不令人满意,因为 mysite 不能再覆盖该设置,并且 myapp 的默认值/设置现在分布在两个文件中...
有更简单的方法吗?
update-4:“只需使用 django 测试运行器..”
您正在测试的应用程序依赖于 Django 框架 - 您无法绕过这样一个事实,即您需要先引导框架才能测试应用程序。引导过程的一部分是创建一个默认的 settings.py 并进一步使用 django 提供的测试运行器来确保您的应用正在它们可能运行的环境中进行测试。
虽然这听起来应该是真的,但实际上并没有多大意义,例如没有默认的settings.py 这样的东西(至少对于可重用的应用程序来说不是)。在谈论集成测试时,使用 site/settings/apps/database(s)/cache(s)/resource-limits/etc 测试应用程序是有意义的。它将在生产中遇到。然而,对于单元测试,我们只想测试我们正在查看的代码单元 - 尽可能少的外部依赖项(和设置)。 Django 测试运行器确实并且应该模拟框架的主要部分,因此不能说它在任何“真实”环境中运行。
虽然 Django 测试运行器很棒,但它无法处理的问题还有很多。对我们来说,两个大问题是(i)顺序运行测试非常慢,以至于测试套件变得未使用(并行运行时小于 5 分钟,顺序运行时几乎一个小时),(ii)有时您只需要在 big 数据库(我们将昨晚的备份恢复到可以运行测试的测试数据库——对于固定装置来说太大了)。
制作nose、py.test、twill、Selenium 和任何模糊测试工具的人确实非常了解测试,因为这是他们唯一的关注点。不能借鉴他们的集体经验将是一种耻辱。
我不是第一个遇到此问题的人,而且似乎没有简单或通用的解决方案。以下是两个具有不同解决方案的项目:
更新,python-oidc-provider 方法:
python-oidc-provider 包 (https://github.com/juanifioren/django-oidc-provider) 有另一种创造性的方式来解决 app-settings/defaults 问题。它使用属性在myapp/settings.py 文件中定义默认值:
from django.conf import settings
class DefaultSettings(object):
@property
def MYAPP_HOME_ROOT(self):
return ...
default_settings = DefaultSettings()
def get(name):
value = None
try:
value = getattr(default_settings, name)
value = getattr(settings, name)
except AttributeError:
if value is None:
raise Exception("Missing setting: " + name)
使用 myapp 中的设置变为:
from myapp import settings
print settings.get('MYAPP_HOME_ROOT')
好: 解决了问题 #2(在定义默认值时使用设置),解决了问题 #1(使用测试中的默认设置)。
错误: 访问设置的语法不同(settings.get('FOO') 与普通的settings.FOO),myapp 无法为将在 myapp 之外使用的设置提供默认值(您从 @987654351 获得的设置@ 将不包含来自 myapp 的任何默认值)。外部代码可以通过 from myapp import settings 获取常规设置和 myapp 默认值,但如果有多个应用程序想要这样做,这将失效...
Update2,django-appconf 包: (注:与 Django 的 AppConfig 无关..)
使用django-appconfig,在myapp/conf.py 中创建应用设置(需要提前加载,因此您应该从models.py 导入conf.py 文件——因为它是提前加载的):
from django.conf import settings
from appconf import AppConf
class MyAppConf(AppConf):
HOME_ROOT = '...'
用法:
from myapp.conf import settings
print settings.MYAPP_HOME_ROOT
AppConf 将自动添加MYAPP_ 前缀,并自动检测MYAPP_HOME_ROOT 是否已在项目设置中重新定义/覆盖。
pro: 使用简单,解决了问题 #1(从测试访问应用程序设置)和问题 #2(在定义默认值时使用设置)。只要 conf.py 文件提前加载,外部代码应该能够使用 myapp 中定义的默认值。
con: 非常神奇。 conf.py 中设置的名称与其用法不同(因为 appconf 自动添加了MYAPP_ 前缀)。外部/不透明依赖。
【问题讨论】:
-
除非您可以将其过滤为一个问题而不是长时间的讨论;我担心你的问题可能会因为“太宽泛”而被关闭。我建议通过django bug tracker 处理这个问题。
-
问题很简单:“在可重复使用的应用程序中,您如何明智地进行设置?”其他所有内容都只是足够的背景知识,可以解释为什么解决玩具问题的解决方案还不够,而且没有任何明显的解决方案可供每个人使用。
-
在使用
myapp/defaults.py解决方案时,为什么不能在from django.conf import settings内使用class Conf解决方案(顺便说一句,规范方式)?顺便说一句,所有其他解决方案(除了第一个和第一个)都会生成错误的文档。 -
@nitely 因为您网站的 settings.py(当您说
from django.conf import settings时导入)需要执行from myapp.defaults import *(以便每个站点可以覆盖应用程序设置)。如果myapp/defaults.py转身导入from django.conf import settings,那么你就有了循环导入。 -
@thebjorn 不要在您的网站设置中使用
from myapp.defaults import *。这就是class Conf的全部意义所在