Django 1.6 Signals

其实这个是 django 的钩子制度,通过 signal 处理可以利用钩子写出更具扩展性的代码:

一、内置的 Signal

1. save() 调用前后 [仅对单个模型对象 save() 生效]:
django.db.models.signals.pre_save
django.db.models.signals.post_save
2. delete() 调用前后 [对单对象和 QuerySet 都生效]:
django.db.models.signals.pre_delete
django.db.models.signals.post_delete
3. 当多对多对象修改时:
django.db.models.signals.m2m_changed
4. HTTP 请求开始及结束时:
django.core.signals.request_started
django.core.signals.request_finished

当然还不止这些,其他的内置信号以及具体参数请看文档

二、侦听信号的方法(接收一个钩子)

上面提到的都是内置的 Signal 对象,当然我们也可以创建自定义的 Signal 对象,如果要侦听一个 Signal,直接在该 Signal 上面调用一次:

Signal.connect(receiver[, sender=None, weak=True, dispatch_uid=None])

就可以为 Signal 事件(钩子)添加一个 callback 函数(receiver),其他参数后面再说;

receiver 函数应该是这个样子:

def my_callback(sender, **kwargs):
    print("Request finished!")

其中 **kwargs 是定义在钩子 Signal 对象时指定的参数,注册 Signal 触发点的时候可以将参数传进来被 receiver 获取到。

* 关于 callback 函数应该放在哪里:

官方文档说放哪都行,只要在钩子出发之前 import 到这些代码即可,所以还是放在 models 里面比较靠谱。

具体的话有两种方式可以写一个钩子,第二种使用 decorator 更简洁,推荐后一种:

# 第一种方法
from django.core.signals import request_finished

request_finished.connect(my_callback)

# 第二种方法
from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

三、指定侦听特殊 sender 的信号

举个例子就好了,例如这样可以只抓住 MyModelpre_save 事件:

rom 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):
    ...

具体的 sender 属性要看信号的定义方式。

四、防止信号被重复侦听

如果你写的侦听 callback 函数的那段代码所在的 module 被重复 import,就有可能发出一个 Signal 却执行了多次的 callback 函数。

这时候需要显式指定一个不重复的 dispatch_uid(看前面的 Signal.connect 函数的参数,类型可以是字符串),这样可以重复注册的 dispatch_uid 处理函数会在第一次之后的注册时被跳过。

from django.core.signals import request_finished

request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")

五、自定义一个信号(定义一个钩子)

注意:每个 signal 是一个 Signal 对象,而不是一个派生类,因此这样来声明一个 signal:

import django.dispatch

pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])

注意上面,仅仅是创建一个 Signal 对象而已;

六、信号的发送(发射一个钩子)

有两种方法:

Signal.send(sender, **kwargs)
Signal.send_robust(sender, **kwargs)

通过这样来调用:

class PizzaStore(object):
    ...

    def send_pizza(self, toppings, size):
        pizza_done.send(sender=self.__class__, toppings=toppings, size=size)
        ...

sendsend_robust 函数都会返回一个列表,对于每个侦听的实例(狗子的接收会产生一个 (receiver, response)tuple,由此可以在发射钩子的地方做一些后期处理);其中 receiver 是那个侦听的函数,response 是这次钩子接收的时候 receiver 的返回值;

至于 sendsend_robust 的不同是: 如果用 send,接收函数的时候抛出异常,程序会被中断,后面的处理函数也不会被执行; 如果用 send_robust,接收函数的异常会被接住,抛出的异常实例会在 send_robust 返回列表的那个 tuple 里面的 response 地方返回;

七、取消一个信号的侦听

Signal.disconnect([receiver=None, sender=None, weak=True, dispatch_uid=None])

这样即可,如果指定了 dispatch_uid 的话,前面的参数可以被忽略。


【转载请附】愿以此功德,回向 >>

原文链接:https://www.huangwenchao.com.cn/2014/05/django-signals.html【Django 1.6 Signals】

发表评论

电子邮件地址不会被公开。 必填项已用*标注