Django app默认?

the*_*orn 14 python django

我正在寻找一种方法来使应用程序默认设置和设置易于使用,很难出错,并且开销很小..

目前我的组织如下:

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'
Run Code Online (Sandbox Code Playgroud)

在我的项目中我有:

mysite/settings.py
    from myapp.defaults import *       # import all default from myapp
    MYAPP_HOME_ROOT = '/export/home'   # overriding myapp.defaults 
Run Code Online (Sandbox Code Playgroud)

通过此设置,我可以以常规django方式导入和使用设置(from django.conf import settingssettings.XXX).

update-3(为什么我们需要这个)

  1. 默认设置("默认值"):
    1. 如果可以通过覆盖一组合理的默认设置来配置应用程序,则应用程序更方便使用.
    2. 应用程序"具有领域知识",因此它尽可能提供合理的默认值是有意义的.
    3. 应用程序的用户需要提供每个应用程序所需的所有设置是不方便的,应该足以覆盖一个小子集并将其余部分保留为默认值.
    4. 如果默认值可以对环境做出反应,那么它非常有用.当DEBUGTrue为True时,您通常会想要做一些不同的事情,但任何其他全局设置都可能有用:例如MYAPP_IDENTICON_DIR = os.path.join(settings.MEDIA_ROOT, 'identicons')(https://en.wikipedia.org/wiki/Identicon)
    5. 项目(站点/全局)设置必须覆盖app-defaults,即MYAPP_IDENTICON_DIR = 's3://myappbucket.example.com/identicons'在(全局)settings.py文件中为其站点定义的用户应获取此值,而不是应用程序的默认值.
    6. 任何与使用settings(import .. settings; settings.FOO)的常规方法保持接近的解决方案都优于需要新语法的解决方案(因为新语法会分歧,我们会获得使用app到app的设置的新的独特方式).
    7. 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
Run Code Online (Sandbox Code Playgroud)

和用法:

myapp/views.py
    from .conf import conf as settings
    ...
    print settings.MYAPP_HOME_ROOT   # will print '/export/home' when used from mysite
Run Code Online (Sandbox Code Playgroud)

这允许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
Run Code Online (Sandbox Code Playgroud)

但这并不令人满意,因为mysite不能再覆盖设置,而myapp的默认值/设置现在分布在两个文件中......

有更简单的方法吗?

update-4:"只需使用django测试运行器......"

您正在测试的应用程序依赖于Django框架 - 您无法解决在测试应用程序之前需要首先引导框架的事实.引导过程的一部分是创建一个默认的settings.py,并进一步使用django提供的测试运行器来确保您的应用程序正在可能运行的环境中进行测试.

虽然这听起来应该是真的,但它实际上并没有多大意义,例如,没有默认值settings.py(至少不适用于可重用的应用程序).在谈论集成测试时,使用site/settings/apps/database(s)/ cache(s)/ resource-limits /等测试应用程序是有意义的.它将在生产中遇到.但是,对于单元测试,我们只想测试我们正在查看的代码单元 - 尽可能少的外部依赖(和设置).Django测试运行器做,并且应该模拟框架的主要部分,因此不能说它在任何"真实"环境中运行.

虽然Django测试运行器很棒,但它有一长串问题无法处理.对我们来说两个巨大的问题是(i)顺序运行测试是如此之慢以至于测试套件变得不被使用(并行运行时<5分钟,顺序运行时差不多一小时),(ii)有时您只需要在大型数据库上运行测试(我们将昨晚的备份恢复到测试可以运行的测试数据库 - 对于灯具而言太大了).

制作鼻子,py.test,斜纹,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)
Run Code Online (Sandbox Code Playgroud)

使用myapp中的设置变为:

from myapp import settings
print settings.get('MYAPP_HOME_ROOT')
Run Code Online (Sandbox Code Playgroud)

good:解决问题#2(在定义默认值时使用设置),解决问题#1(使用测试中的默认设置).

bad:访问设置的语法不同(settings.get('FOO')与正常情况相比settings.FOO),myapp无法为在myapp之外使用的设置提供默认值(您获得的设置from django.conf import settings不包含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 = '...'
Run Code Online (Sandbox Code Playgroud)

用法:

from myapp.conf import settings
print settings.MYAPP_HOME_ROOT
Run Code Online (Sandbox Code Playgroud)

AppConf将自动添加MYAPP_前缀,并自动检测是否MYAPP_HOME_ROOT已在项目设置中重新定义/覆盖.

pro:简单易用,解决问题#1(从测试中访问app-settings)和问题#2(在定义默认值时使用设置).只要早期加载conf.py文件,外部代码就应该能够使用myapp中定义的默认值.

骗局:非常神奇.conf.py中设置的名称与其用法不同(因为appconf会自动添加MYAPP_前缀).Extenal/opaque依赖.

nit*_*ely 5

我刚刚根据所有要求创建了django-app-defaults。它基本上是问题 ( class Conf(object):) 中强调的第二种方法的概括。

用法:

# my_app/defaults.py

# `django.conf.settings` or any other module can be imported if needed

# required
DEFAULT_SETTINGS_MODULE = True

# define default settings below
MY_DEFAULT_SETTING = "yey"
Run Code Online (Sandbox Code Playgroud)

然后在您的项目中的任何地方:

from app_defaults import settings

print(settings.MY_DEFAULT_SETTING)
# yey

# All `django.conf.settings` are also available
print(settings.DEBUG)
# True
Run Code Online (Sandbox Code Playgroud)

要为单个应用程序而不是所有应用程序加载默认设置,只需执行以下操作:

# Note: when apps or modules are explicitly passed,
# the `DEFAULT_SETTINGS_MODULE` var is not required

from app_defaults import Settings

settings = Settings(apps=["my_app"])

# or

from my_app import defaults
settings = Settings(modules=[defaults])
Run Code Online (Sandbox Code Playgroud)