为Django“插件”应用设置别名

2 投票
2 回答
1344 浏览
提问于 2025-04-15 17:12

有没有什么简单的方法可以通过别名来加载一个 Django 应用?我觉得这样可以让应用更容易被插件化。

在 settings.py 文件中,有一种常用的写法:

INSTALLED_APPS = ('... ','...', )

这里的 INSTALLED_APPS 是一个包含应用名称的元组。这种方式没问题,但我不想这样明确地写出某些应用。

我想要一个叫做 app handle 的东西,比如 external_login_app,它可以映射到 drupal_loginmediawiki_login(这些由最终用户或第三方提供),而实际的自定义模块/应用的名称则通过 settings.py 中的一个字符串变量来提供。

这个可插拔的部分(比如 mediawiki_login)必须是一个完整的应用,拥有自己的视图、模型、表单等,而 external_login_app 必须在代码的其他地方都能访问。

这里的目标是将分散的代码与这样的插件解耦。

编辑 1:这是我正在尝试的:

在 settings.py 中:

EXTERNAL_LOGIN = __import__(EXTERNAL_LOGIN_APP) 
#setting name must be upper case according to
#django docs

但是我的自定义登录应用也依赖于设置文件,所以看起来我遇到了循环导入的问题,错误信息是 module external_login does not have attribute views。这个问题似乎很棘手,因为我甚至无法在通过 __import__ 语句导入的视图中使用简单的东西,比如 render_to_response

编辑 2:经过一段时间的尝试,我发现使用 __import__() 在 settings.py 中调用是行不通的,因为几乎不可避免地会出现循环依赖。

到目前为止,我找到的最佳方法是将 __import__() 调用放在应用的其他 .py 文件中,这些文件提供了通用的“消费者”接口——也就是调用插件函数的那个:

settings.py 中:正如 Michał 在他的回答中建议的那样

EXTERNAL_LOGIN_APP = 'drupal_login'
INSTALLED_APPS = (
                  '...',
                  EXTERNAL_LOGIN_APP,
                )

例如,在 app/views.py 中:

from django.conf import settings
EXTERNAL_LOGIN = __import__(settings.EXTERNAL_LOGIN_APP, \
                            [], [], ['api','forms'])

def login_view(request, external=False):
    if external:
        form = EXTERNAL_LOGIN.forms.LoginForm(request.POST)
        if form.is_valid():
            login = form.cleaned_data['login']
            password = form.cleand_data['password']
            if EXTERNAL_LOGIN.api.check_password(login, password):
                #maybe create local account, 
                #do local authentication
                #set session cookies for the 
                #external site to synchronize logins, etc.

2 个回答

4

设置

LOGIN_APP_NAME = 'drupal_login' # or 'mediawiki_login', or whatever

要在settings.py文件中尽早设置,然后把LOGIN_APP_NAME(记得不要加引号!)放到INSTALLED_APPS里,而不是放实际应用的名字。

如果你需要更复杂的功能来决定用哪个应用,那可以考虑在INSTALLED_APPS里放一个像external_login_app()这样的函数调用(同样不要加引号!),然后让这个external_login_app函数根据settings.py里的某个设置,或者某个配置文件的内容等返回应该返回的东西。(编辑:Tobu的回答展示了这样的函数可能需要返回什么,以及如何使用__import__来实现。)

不过,我不太确定这样做能在多大程度上解耦网站的各个部分——如果用户还是需要修改settings.py,那为什么不让他们直接在合适的地方填入应用的名字呢?(编辑:好吧,现在我有点明白在这里用正确的解决方案能得到什么了……我想讨论还在继续,关于如何最好地完成所需的事情。)

编辑:我在原提问者修改问题之前就发了这个,现在第二次编辑提到了把登录应用的名字移动到settings.py里的一个单独变量中,并把这个变量包含在INSTALLED_APPS里。现在原问题的两个编辑都已经到位,我想我能更清楚地看到问题了,虽然这让我觉得现在的情况需要一个abstract_login应用,配合后端支持'*_login'模块来处理drupal、mediawiki等登录方案。嗯,我现在不能提供这样的东西……希望其他人能做到。如果我有更好的主意能让整个事情变得简单,我会再编辑的。

2

在设置里定义应用的名称(就像Michał和你编辑的那样)是个不错的做法:

EXTERNAL_LOGIN_APP = 'drupal_login'
INSTALLED_APPS = (
                  '...',
                  EXTERNAL_LOGIN_APP,
                )

在设置中,或者在一个公共模块里,你还可以考虑导入这个应用:

def external_login_app():
    return __import__(EXTERNAL_LOGIN_APP, …)

然后在你导入的内容之后使用它:

external_login_app = settings.external_login_app()

撰写回答