信号处理程序应该放在 django 项目中的啥位置?

Posted

技术标签:

【中文标题】信号处理程序应该放在 django 项目中的啥位置?【英文标题】:Where should signal handlers live in a django project?信号处理程序应该放在 django 项目中的什么位置? 【发布时间】:2011-02-12 17:25:02 【问题描述】:

我刚刚开始在 django 项目中实现信号侦听器。虽然我了解它们是什么以及如何使用它们。我很难弄清楚我应该把它们放在哪里。来自 django 网站的文档是这样说的:

Where should this code live?

你可以把信号处理和 您喜欢的任何地方的注册码。 但是,您需要确保 它所在的模块会提前导入 on 以便信号处理得到 在任何信号需要之前注册 被发送。这使您的应用程序的 models.py 是一个放置的好地方 信号处理程序的注册。

虽然这是一个很好的建议,但在我的 models.py 中包含非模型类或方法只会让我犯错。

那么,存储和注册信号处理程序的最佳实践/规则是什么?

【问题讨论】:

【参考方案1】:

这是在 Django 1.7 发布时添加到 documentation 的:

严格来说,信号处理和注册代码可以放在任何你喜欢的地方,但建议避免应用程序的根模块及其模型模块,以尽量减少导入代码的副作用。

实际上,信号处理程序通常定义在与其相关的应用程序的信号子模块中。信号接收器连接在应用程序配置类的 ready() 方法中。如果您使用的是 receiver() 装饰器,只需在 ready() 中导入信号子模块。

在 Django 1.7 中更改:由于之前版本的 Django 中不存在 ready(),因此信号注册通常发生在模型模块中。

最佳实践是在信号子模块的 handlers.py 中定义您的处理程序,例如一个看起来像这样的文件:

yourapp/signals/handlers.py

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel

@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    pass

注册信号处理程序的最佳位置是在定义它的应用的 AppConfig 中,使用 ready() 方法。这将如下所示:

yourapp/apps.py

from django.apps import AppConfig

class TasksConfig(AppConfig):
    name = 'tasks'
    verbose_name = "Tasks"

    def ready(self):
        import yourproject.yourapp.signals.handlers #noqa

确保通过直接在 settings.py 的 INSTALLED_APPS 或应用的 __init__ 中指定来加载 AppConfig。请参阅see the ready() documentation 了解更多信息。

注意:如果您也为其他应用提供信号来监听,请将它们放在信号模块中的 __init__ 中,例如一个看起来像这样的文件:

yourapp/signals/__init__.py

import django.dispatch

task_generate_pre_save = django.dispatch.Signal(providing_args=["task"])

然后另一个应用可以通过导入和注册来监听您的信号,例如from yourapp.signals import task_generate_pre_save。将信号与处理程序分开可以保持干净。

Django 1.6 说明:

如果您仍然停留在 Django 1.6 或更低版本,那么您将做同样的事情(在 yourapp/signals/handlers.py 中定义您的处理程序),但您将通过 __init__ 加载处理程序而不是使用 AppConfig您的应用程序的 .py 文件,例如类似:

yourapp/__init__.py

import signals

这不如使用 ready() 方法好,因为它经常导致循环导入问题。

【讨论】:

正如文档所说,你覆盖了 ready,你可能想做一些类似 super(ReportsConfig, self).ready() 的事情,以防 django 决定用一些东西填充 ready() (从 1.7.1 开始)。 0 目前为空) 我认为这个答案是最好的,因为它是唯一解决进口副作用的答案。我来这里是为了寻找最佳实践,因为我正在清理一个应用程序,该应用程序正是由于这种副作用而损坏的。唉,该应用程序在 django 1.6 上运行,最佳实践仅适用于 django 1.7。让__init__ 导入信号的临时解决方法对我不起作用,所以我想知道是否还有其他地方可以导入信号,直到我们准备好升级到更高的 django 版本。 yourapp/signals/__init__.py中不应该有from . import handlers(或类似的)吗? 你不应该在某个地方也导入 handlers.py 模块吗?我正在尝试这个,它似乎没有定义信号的处理程序。 fwiw 我不需要在 TaskConfig 类代码块的最后一行中的yourproject.。我已经用这个结构来工作了,所以考虑一下这个 qa :)【参考方案2】:

我实际上喜欢让它们成为模型本身的类方法。这将所有内容都放在一个类中,这意味着您不必担心导入任何内容。

【讨论】:

您通常在哪里将处理程序连接到信号? @DataGreed:在相关models.py的底部。 如果您正在收听该模型发出的信号,那么将所有听众都放在那里也会使整个练习毫无意义,不是吗?信号的重点是解耦。听众不应该接受对这些远程事件感兴趣的代码吗?问题是如何确保在发射器之前加载监听器。 就我而言,我想收听模型Foo 的信号,它是fooapp 的一部分。但是信号接收器是一个扩展,并且确实存在于不同的应用程序中(例如otherapp)。 就 John Mee 而言,这与仅覆盖 save() 等没有太大区别。【参考方案3】:

我刚刚遇到这个问题,由于我的信号与模型无关,我想我会添加我的解决方案。

我正在记录有关登录/注销的各种数据,并且需要连接到django.contrib.auth.signals

我已将信号处理程序放入 signals.py 文件中,然后从 __init__.py 模块文件中导入信号,因为我相信这会在应用程序启动后立即调用(使用 print 语句进行测试表明甚至在读取设置文件之前就调用它。)

# /project/__init__.py
import signals

在signals.py中

# /project/signals.py
from django.contrib.auth.signals import user_logged_in

def on_logged_in(sender, user, request, **kwargs):
    print 'User logged in as: \'0\''.format(user)

user_logged_in.connect(on_logged_in)

我对 Django (/python) 很陌生,所以我愿意接受任何人告诉我这是一个糟糕的主意!

【讨论】:

这感觉合乎逻辑,但我建议在应用程序级别进行。 小心,这种逻辑很可能会导致重复信号被触发。 user_logged_in.connect(on_logged_in) 很可能会传入 dispatch_uid 参数。更多信息请访问docs.djangoproject.com/en/dev/topics/signals/…。 谢谢您-很高兴知道。我正在使用这种方法(记录 IP / 用户代理)记录所有登录,并且到目前为止没有任何重复 - 尽管这并不意味着线下的小改动不会造成问题!【参考方案4】:

我最近刚刚阅读了this 文章,该文章介绍了在布置项目/应用程序时的最佳实践,它建议您所有的自定义调度程序信号都应该放在一个名为signals.py 的文件中。但是,这并不能完全解决您的问题,因为您仍然需要在某处导入它们,而且越早导入越好。

模型建议很好。由于您已经在 signals.py 文件中定义了所有内容,因此文件顶部不应超过一行。这类似于admin.py 文件的布局方式(顶部是类定义,底部是注册所有自定义管理类的代码),如果您定义信号,则将它们连接到同一个文件中。

希望对您有所帮助!最终取决于您的喜好。

【讨论】:

我也想将我的信号处理程序放在signals.py 文件中,但不知道之后应该如何调用它。通过在我的models.py 文件中导入它,我得到了一个非常干净的解决方案,而不会“污染”我的 models.py 文件。谢谢! :) 那里有交叉导入:signals.py 尝试从 models.py 导入模型【参考方案5】:

每个应用程序中的models.py 和signals.py 都是推荐的连接信号的地方,但是,在我看来,它们并不是保持信号和处理程序调度的最佳解决方案。调度应该是在 django 中发明信号和处理程序的原因。

苦苦挣扎了很久,终于找到了解决办法。

在应用文件夹中创建一个连接器模块

所以我们有:

app/
    __init__.py
    signals.py
    models.py
    connectors.py

在 app/connectors.py 中,我们定义了信号处理程序并连接它们。提供了一个例子:

from signals import example_signal
from models import ExampleModel
from django.db.models.signals import post_save, post_delete

def hanndler(sender, *args, **kwargs):
    pass

post_save.connect(hander, sender=ExampleModel)

然后在models.py中,我们在文件末尾添加如下一行:

from app import connector

一切都在这里完成。

这样,我们可以把信号放在signals.py中,所有的handlers放在connectors.py中。模型和信号没有混乱。

希望它提供另一种解决方案。

【讨论】:

那么,signals.py 中有什么内容?从您的示例看来,这只是自定义信号。通常我们只是将信号和连接器结合起来,因为大多数都没有自定义信号。 @dalore 是的,所有自定义信号都放在 signals.py 中。我们有许多定制的信号。但是如果你没有很多,这个文件可以省略。 与@dal 相同的问题 请注意,所有这些现在都是旧建议,django 现在的方式是使用 appconfig 导入信号处理程序所在的处理程序。在 signals.py 中自定义信号【参考方案6】:

关于AppConfig的小提醒。不要忘记设置:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'

【讨论】:

【参考方案7】:

我将它们保存在一个单独的文件中 signals.py ,在定义所有模型之后在 models.py 中。我导入它们并将模型连接到信号。

信号.py

#  necessary imports

def send_mail_on_save(<args>):
    # code here 

models.py

# imports
class mymodel(models.Model):
    # model here

# import signals
from signals import send_mail_on_save
# connect them 
post_save.connect(send_mail_on_save,sender=mymodel)

这为我提供了逻辑上的分离,当然将它们保存在 models.py 中没有错,但是这样更易于管理。

希望这会有所帮助!

【讨论】:

您将信号处理程序放在“signals.py”中,如果我们将其命名为“handlers.py”会怎样 将文件命名为 signals.py 或 handler.py 都没有关系。这只是一个约定而不是规则。

以上是关于信号处理程序应该放在 django 项目中的啥位置?的主要内容,如果未能解决你的问题,请参考以下文章

我应该将 SQL 文件放在我的 Java 项目中的啥位置?

我应该将前端代码放在我的后端项目中的啥位置以及如何/何时运行它?

我应该将瞬态域类放在 grails 应用程序中的啥位置?

我应该将我的 XML bean 放在 Spring Boot 应用程序中的啥位置?

我应该将 Angular 代码放在这个 Mean.js 应用程序中的啥位置以使该列表可排序?

我应该将 javascript 库放在 Grails 应用程序的啥位置?