Django 提供一个“信号分发器”,允许解耦的应用在框架的其它地方发生操作时会被通知到。 简单来说,信号允许特定的sender通知一组receiver某些操作已经发生。 这在多处代码和同一事件有关联的情况下很有用。
Django提供一组内建的信号,允许用户的代码获得Django特定操作的通知。 它们包含一些有用的通知:
django.db.models.signals.pre_save
&django.db.models.signals.post_save
在模型 save()
方法调用之前或之后发送。
django.db.models.signals.pre_delete
&django.db.models.signals.post_delete
django.db.models.signals.m2m_changed
模型上的 ManyToManyField
修改时发送。
django.core.signals.request_started
&django.core.signals.request_finished
Django开始或完成HTTP请求时发送。
关于完整列表以及每个信号的完整解释,请见内建信号的文档 。
你也可以定义和发送自己的自定义信号;参见下文。
要接收信号,请使用Signal.connect()
方法注册receiver函数。 receiver函数在信号发送时调用。
Signal.
connect
(receiver, sender=None, weak=True, dispatch_uid=None)[source]¶参数: |
|
---|
通过注册一个在每次HTTP请求结束时调用的信号,让我们来看一看它是如何工作的。 我们将会连接到request_finished
信号。
首先,我们需要定义receiver函数。 Receiver可以是任何Python函数或者方法:
def my_callback(sender, **kwargs):
print("Request finished!")
请注意,该函数接受一个sender
参数,以及通配符关键字参数(**kwargs
);所有信号处理程序都必须接受这些参数。
我们过一会儿再关注sender,现在先看一看**kwargs
参数。 所有信号都发送关键字参数,并且可以在任何时候修改这些关键字参数。 对于request_finished
,它的文档描述是不发送任何参数,这意味着我们似乎可以将我们自己的信号处理器编写成my_callback(sender)
。
这是错误的 -- 实际上,如果你这么做了,Django 会抛出异常。 这是因为信号在任何时候都可能添加参数,你的receiver 必须能够处理这些新的参数。
有两种方法可以将一个receiver连接到信号。 你可以采用手动连接的方法:
from django.core.signals import request_finished
request_finished.connect(my_callback)
或者使用receiver()
装饰器来自动连接:
下面是使用装饰器连接的方法:
from django.core.signals import request_finished
from django.dispatch import receiver
@receiver(request_finished)
def my_callback(sender, **kwargs):
print("Request finished!")
现在,我们的my_callback
函数会在每次请求结束时调用。
这段代码应该放在哪里?
严格来说,信号处理和注册的代码应该放在你想要的任何地方,但是推荐避免放在应用的根模块和models
模块中,以尽量减少产生导入代码的副作用。
实际上,信号处理函数通常定义在应用相关的signals
子模块中。 信号receiver在你应用配置类中的ready()
方法中连接。 如果你使用receiver()
装饰器,只需要在ready()
内部导入signals
子模块就可以。
一些信号会发送多次,但是你只想接收这些信号的一个确定的子集。 例如,考虑 django.db.models.signals.pre_save
信号,它在模型保存之前发送。
大多数情况下,你并不需要知道所有模型何时保存 -- 只需要知道一个特定的模型何时保存。
在这些情况下,你可以通过注册来接收只由特定sender 发出的信号。 对于django.db.models.signals.pre_save
的情况, sender 是被保存的模型类,所以你可以认为你只需要由某些模型发出的信号:
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):
...
my_handler
函数只在MyModel
实例保存时被调用。
不同的信号使用不同的对象作为sender;有关每个特定信号的详细信息,你需要参考内建信号的文档。
在一些情况下,连接receiver 到信号的代码可能会执行多次。 这可能会导致receiver函数被多次注册,因此对于单个信号事件多次进行调用。
如果这样的行为会导致问题(例如在任何时候模型保存时使用信号来发送邮件),可以传递一个唯一的标识符作为dispatch_uid
参数来标识你的receiver 函数。 标识符通常是一个字符串,虽然任何可计算哈希的对象都可以。 最后的结果是,对于每个唯一的dispatch_uid
值,你的receiver 函数都只绑定到信号一次:
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
你的应用可以利用信号功能来提供自己的信号。
所有信号都是 django.dispatch.Signal
的实例。 providing_args
是一个列表,由信号将提供给监听者的参数名称组成。 理论上是这样,但是实际上并没有任何检查来保证向监听者提供了这些参数。
像这样:
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
这段代码声明了pizza_done
信号,它向接受者提供size
和 toppings
参数。
要记住你可以在任何时候修改参数的列表,所以首次尝试的时候不需要完全确定API。
Django中有两种方法用于发送信号。
要发送信号,请调用Signal.send()
(所有内置信号都使用它)或Signal.send_robust()
。 你必须提供sender
参数(大部分时候是一个类),并且可以提供任意数量的其他关键字参数。
例如,这样来发送我们的pizza_done
信号:
class PizzaStore(object):
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
...
send()
和send_robust()
返回一个元组对的列表
[(receiver, response), ... ]
,表示被调用的receiver函数及其响应值的列表。
send_robust()
与 send()
在处理receiver 函数时产生的异常有所不同。 send()
不会捕获receiver产生的异常;它只是让错误向上传播。 所以在错误产生的情况,不是所有receiver都会获得通知。
send_robust
捕获所有继承自Python ()Exception
类的异常,并且确保所有receiver都能得到信号的通知。 如果发生错误,错误实例会在产生错误的receiver 的二元组中返回。
调用send_robust()
的时候,所返回的错误的__traceback__
属性上会带有 traceback。
调用Signal.disconnect()
来断开信号的接收器。 和Signal.connect()
中描述的参数相同。 如果接收器成功断开,返回 True
,否则返回False
。
receiver
参数表示要断开的已注册receiver。 如果使用dispatch_uid
标识receiver,它可能为None
。
自1.9版以来已弃用: weak
参数已被弃用,因为它不起作用。 它将在Django 2.0中被删除。
2017年9月6日