django项目中的信号处理程序应该放在哪里?


143

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

该代码应该放在哪里?

您可以将信号处理和注册代码放在您喜欢的任何位置。但是,您需要确保该模块所在的模块尽早导入,以便在需要发送任何信号之前注册信号处理。这使您的应用程序的models.py成为注册信号处理程序的好地方。

尽管这是一个很好的建议,但是在我的models.py中包含非模型类或方法只会给我带来错误的印象。

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

Answers:


41

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


2
您通常在哪里将处理程序连接到信号?
DataGreed

1
@DataGreed:在相关models.py的底部。
丹尼尔·罗斯曼

102
如果您正在听那个模型发出的信号,那么将所有听众都放在那里会使整个练习毫无意义,不是吗?信号的要点是去耦。侦听器是否不应该使用对这些远程事件感兴趣的代码?问题是如何确保侦听器在发射器之前加载。
约翰·米

就我而言,我想听一个模型信号,Foo它是的一部分fooapp。但是信号接收器是扩展程序,确实存在于其他应用程序中(例如otherapp)。
guettli 2014年

2
约翰眉的角度来看,它不仅仅是覆盖保存(没有太大的不同),等等
马特·

245

Django 1.7发布时,它已添加到文档中

严格来说,信号处理和注册代码可以存在于您喜欢的任何位置,尽管建议避免使用应用程序的根模块及其模型模块,以最大程度地减少导入代码的副作用。

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

在Django 1.7中进行了更改:由于在早期版本的Django中不存在ready(),因此信号注册通常发生在models模块中。

最佳实践是在信号子模块中的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

然后,使用ready()方法在定义它的应用程序的AppConfig中注册信号处理程序的最佳位置。看起来像这样:

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 。有关更多信息,请参见ready()文档

注意:如果您也要提供信号让其他应用程序也可以收听,请将它们放在__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中定义处理程序),而不是使用AppConfig,而是通过__init__.py加载处理程序。您的应用,例如:

yourapp / __ init__.py

import signals

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


3
正如文档所述,您覆盖了ready,您可能需要执行诸如super(ReportsConfig,self).ready()之类的操作,以防django决定在其中填充某些东西(从1.7.0开始为当前为空)
w- -

3
我认为这个答案是最好的,因为它是解决进口问题的唯一方法。我来这里是为了寻找最佳实践,因为我正在清理一个应用程序,而这种应用程序正是由于这种副作用而损坏的。las,该应用程序正在django 1.6上运行,最佳实践仅在django 1.7上有效。让__init__导入信号的临时解决方法对我不起作用,所以我想知道在准备好升级到更高版本的Django之前,是否还有其他地方可以导入信号。
kasperd 2014年

里面不应该有from . import handlers(或类似的)东西yourapp/signals/__init__.py吗?
dhobbs 2015年

您还不应该在某个地方导入handlers.py模块吗?我正在尝试这样做,它似乎没有为信号定义处理程序。
安德烈斯

1
首先,我不需要yourproject.TaskConfig类代码块的最后一行。我已经使用了这种结构,因此请考虑以下问题:)
Greg Kaleka

40

我只是碰到了这一点,由于我的信号与模型无关,所以我想添加自己的解决方案。

我正在登录/注销时记录各种数据,并且需要连接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)很陌生,所以欢迎任何告诉我这是一个糟糕主意的人!


3
这听起来很合逻辑,但我建议在应用程序级别进行。
尼尔斯

2
小心,此逻辑很可能会导致重复触发信号。user_logged_in.connect(on_logged_in)应该很可能会传递dispatch_uid参数。有关更多信息,请访问docs.djangoproject.com/en/dev/topics/signals/…
Scott Coates 2013年

谢谢你-很高兴知道。我正在使用此方法记录所有登录信息(记录IP /用户代理),到目前为止,还没有任何重复-尽管这并不意味着在线路上进行小的更改不会造成问题!
Hugo Rodger-Brown

13

最近,我刚刚读这篇关于最佳实践的文章,其中涉及到布置您的项目/应用程序的过程,并且建议您将所有自定义调度程序信号都放在一个名为的文件中signals.py。但是,这并不能完全解决您的问题,因为您仍然需要将它们导入某个位置,并且越早导入它们越好。

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

希望有帮助!最终,它取决于您的偏好。


1
我还想将信号处理程序放在一个signals.py文件中,但不知道随后如何调用它。通过将其导入到我的models.py文件中,我得到了一个非常干净的解决方案,而没有“污染”我的models.py文件。谢谢!:)
Danilo Bargen

10
那里有交叉导入:
signals.py

8

在每个应用程序中,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,并将所有处理程序放入connectors.py。模型和信号没有混乱。

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


1
那么,signals.py中有什么呢?从您的示例看来,这只是自定义信号。通常,我们只是将信号和连接器组合在一起,因为大多数都没有自定义信号。
dalore

@dalore是的,所有自定义信号都放在signal.py中。我们有许多定制信号。但是,如果您没有很多,则可以忽略此文件。
塞缪尔

同样的问题@dal
2009东海生日贺

1
请注意,所有这些现在都是旧建议,现在的django方法是使用appconfig将信号处理程序所在的处理程序导入。在
signals.py中输入

3

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

signal.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”,该怎么办?
Abdul Fatah

1
将文件命名为signal.py还是handler.py都没有关系。这只是一个惯例而不是规则。
14'5

3

小提醒一下AppConfig。不要忘记设置:

# yourapp/__init__.py

default_app_config = 'yourapp.apps.RockNRollConfig'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.