Django Signals 信号机制详解:内置信号、监听与自定义
本文详细解析Django中的Signals信号机制,涵盖Model signals与Request/response signals两大类别。重点讲解pre_init、post_save、m2m_changed等内置信号的触发时机与参数,演示如何通过connect方法和装饰器注册信号接收器,并介绍dispatch_uid防重机制及自定义信号的完整实现流程。
Django Model 内置信号详解
1、Model signals
django.db.models.signals 模块提供了作用于 Django Model 操作的一系列信号,用于在模型生命周期的关键节点执行自定义逻辑。
1)pre_init()
django.db.models.signals.pre_init
该信号在模型实例化时触发,具体执行时机位于 __init__() 方法之前。
它包含三个参数:
pre_init(sender, args, kwargs)
sender:指代创建该实例的模型类。
args:传递给构造函数的位置参数列表。
kwargs:通过字典形式传递的关键字参数。
2)post_init()
django.db.models.signals.post_init
作为 pre_init 的对应信号,post_init 同样作用于模型实例化过程,但它在 __init__() 方法执行完毕后触发。
它接收两个参数:
post_init(sender, instance)
sender:与 pre_init 相同,指创建实例的模型类。
instance:当前创建的模型实例对象。
3)pre_save()
django.db.models.signals.pre_save
在调用 Model 的 save() 方法保存数据之前触发。
该信号包含 5 个参数:
pre_save(sender, instance, raw, using, update_fields)
sender:对应的 Model 类。
instance:即将被保存的模型实例。
raw:布尔值,若模型是通过 loaddata 等方式全部保存则为 True。
using:指定使用的数据库别名。
update_fields:传递的待更新字段集合,若未指定则值为 None。
4)post_save()
djang.db.models.post_save
在 Model 的 save() 方法执行完毕后触发。
该信号包含 6 个参数:
post_save(sender, instance, created, raw, using, update_fields)
sender:Model 类。
instance:被保存的 Model 实例。
created:布尔值,若此次操作创建了新记录则为 True。
raw:布尔值,若模型被全部保存(如 loaddata)则为 True。
using:使用的数据库别名。
update_fields:传递的待更新字段集合,未传递时为 None。
5)pre_delete()
django.db.models.signals.pre_delete
在执行 Model 的 delete() 方法或 QuerySet 的 delete() 方法之前触发。
pre_delete(sender, instance, using)
sender:Model 类。
instance:被删除的模型实例。
using:使用的数据库别名。
6)post_delete()
django.db.models.signals.post_delete
在执行 Model 的 delete() 方法或 QuerySet 的 delete() 方法之后触发。
post_delete(sender, instance, using)
sender:Model 类。
instance:被删除的实例。需注意,此时该实例已从数据库中删除,记录不再存在,使用此实例时需格外小心。
using:使用的数据库别名。
7)m2m_changed()
django.db.models.signals.m2m_changed
当 Model 的 ManyToManyField 关系发生改变时发送。严格来说,这不是模型信号,而是由 ManyToManyField 发送,但因实现了 pre_save/post_save 和 pre_delete/post_delete 的逻辑,故归类于 Model signals 中。
该信号包含多个参数:
sender:描述 ManyToManyField 的中间模型类,Django 在定义 many-to-many 字段时自动创建,可通过 many-to-many 字段的 through 属性访问。
instance:被更新的多对多关系实例,可以是 sender 本身,也可以是 ManyToManyField 的关系类。
action:指明作用于关系更新类型的字符串,包括以下几种情况:
"pre_add"/"post_add":在向关系添加一个或多个对象前/后发送。
"pre_remove"/"post_remove":从关系中删除一个或多个对象前/后发送。
"pre_clear"/"post_clear":在关系清空之前/之后发送。
reverse:布尔值,表示正在修改的是正向关系(False)还是反向关系(True)。
model:被添加、删除或清空的对象所属的类。
pk_set:对于 add/remove 操作,pk_set 是一个包含从关系中添加或删除的对象主键的集合;对于 clear 操作,pk_set 为 None。
举例说明:
假设有两个实例,且存在如下关系:
class Topping(models.Model):
pass
class Pizza(models.Model):
toppings = ManyToManyFields(Topping)
我们像这样连接一个处理器:
from django.db.models.signals import m2m_changed
def toppings_changed(sender, **kwargs):
pass
m2m_changed.connect(toppings_changed, sender=Pizza.toppings.through)
然后我们对上面的类执行如下操作:
p = Pizza.objects.create(...)
t = Topping.objects.create(...)
p.toppings.add(t)
此时,对应的上述参数分别如下:
sender:描述 ManyToManyField 的中间类,即 Pizza.toppings.through。
instance:被更新的多对多关系实例,即 P(本例中,Pizza 对应被更改)。
action:先是 "pre_add",然后执行上面的 add() 操作,最后再调用 "post_add"。
reverse:本例中,Pizza 包含 ManyToManyField topping,调用 P.toppings.add(),属于正向更新,故 reverse 为 False。
model:被添加、删除或清空的类,本例中 Topping 被添加到 Pizza。
pk_set:{t.id}。
若我们执行以下操作:
t.pizza_set.remove(p)
此时对应的参数为:
sender:同上。
instance:t(本例中,Topping 实例被更改)。
action:先是 "pre_remove",然后执行 remove 操作,再执行 "post_remove"。
reverse:True,本例中属于反向操作。
model:p。
pk_set:{p.id}。
8)class_prepared
django.db.models.signals.class_prepared
当模型类准备好时发送,即模型被创建并注册到 Django 的模型系统中时触发。
该信号通常由 Django 内部使用,一般不会被第三方应用直接调用。
Request/Response 信号机制
2、Request/response signals
此类信号在处理 HTTP 请求的生命周期中发出。
1)request_started()
django.core.signals.request_started
在 Django 开始处理 HTTP 请求时发送。
request_started(sender, environ)
2)request_finished()
django.core.signals.request_finished
在 Django 处理完 HTTP 请求后发送。
3)got_request_exception()
django.core.signals.got_request_exception
在处理 HTTP 请求过程中遇到错误时发送。
Django 信号监听与注册方法
3、使用信号
1)监听信号
若要接收信号,可使用 Signals.connect() 方法注册一个接收器函数,当信号发送时该函数将被调用。
Signals.connect(receiver, sender=None, weak=True, dispatch_uid=None)
receiver:将连接到此信号的回调函数。
sender:指定要接收信号的特定发送方(可选)。
weak:Django 默认将信号处理程序存储为弱引用。若接收器是弱引用,可能被垃圾回收机制回收。为防止此情况,
我们在调用信号的 connect() 方法时,传递 weak=False。
dispatch_uid:为信号接收方定义的唯一标识符,用于防止重复信号发送导致的重复注册。
接下来以 HTTP 请求中的 request_finished 信号为例:
2)定义接收函数
def my_func_callback(sender, **kwargs):
print("request_finished")
如上所示,所有的接收函数必须包含 sender 和关键字参数(**kwargs)两个参数。
3)连接接收函数
有两种方法将接收器和信号连接起来,我们可以选择手动连接,如下:
from django.core.signals import request_finished
request_finished.connect(my_func_callback)
我们还可以选择通过装饰器来连接信号和接收器:
from django.dispatch import receiver
from django.core.signals import request_finished
@receiver(request_finished)
def my_func_callback(sender, **kwargs):
pass
注意:在实践中,信号处理程序通常定义在与它们相关的应用程序的 signals 子模块中,信号接收器连接在我们的应用程序配置类的 ready() 方法中。如果使用装饰器方式,我们只需要在 ready() 中导入 signals 子模块即可。
值得一提的是,在测试过程中,我们的 ready() 函数可能不止一次被执行,因此我们要保护我们的信号不要被重复注册。
4)连接到特定发送者发送的信号
在很多情况下,信号会被多次发送,但实际我们只对这些信号的某个子集感兴趣,例如前面提到的 pre_save() 信号。
此时,我们可以注册只接收特定发送者(即特定 Model)发送的信号。如下,我们可以指定我们需要接收的某个模型发送的信号:
from djang.db.models.signals import pre_save
from django.dispatch import receiver
from .model import MyModel
@receiver(pre_save, sender=MyModel)
def my_receiver(sender, **kwargs):
pass
这样,我们的 my_receiver() 函数将只有在 MyModel 被保存时被调用。
5)防止重复的信号:
在某些情况下,连接接收器到信号的代码可能会运行多次,这可能导致接收器函数注册不止一次,从而对单个信号事件调用多次。
例如使用信号在保存模型时发送电子邮件,可传递唯一标识符作为 dispatch_uid 参数,以识别接收函数。这个标识符通常是一个字符串。
最终结果是,对于每个唯一的信号,我们的接收器函数将只绑定到该信号一次。
from django.core.signals import request_finished
request_finished.connect(my_receiver, dispatch_uid="my_unique_identifier")
如我们注册时保存密码需要用到 post_save,新建 my_signals.py,在文件中加入下面代码:
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth import get_user_model
user = get_user_model()
@receiver(signal=post_save, sender=user)
def create_user(sender, instance=None, created=False, **kwarg):
password = instance.password
instance.set_password(password)
instance.save()
然后在项目 apps 中重写 ready,将我们新建的 my_signals 引入即可。

Django 自定义信号实现
3、自定义信号
1)定义信号:
在项目根目录新建文件 self_signal.py。
import django.dispatch
my_signal = django.dispatch.Signals(providing_args=["aaa","bbb"])
2)注册信号(即信号接收器)
在项目应用下的 __init__.py 文件中注册。
from self_signal import my_signal
def register_my_signal(sender, **kwargs):
print("my signal msg:", sender, **kwargs)
my_signal.connect(register_my_signal)
3)触发信号
在 views 视图中编写如下代码:
from self_signal import my_signal
my_signal.send(sender="Python", aaa=111, bbb=2)
以上这篇基于 Django signals 信号作用及用法详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









