Django 框架包含了一个信号机制,它允许若干个发送者(sender)通知一组接收者(receiver)某些特定操作或事件(events)已经发生了, 接收者收到指令信号(signals)后再去执行特定的操作。本文主要讲解Django信号(signals)的工作机制、应用场景,如何在项目中使用信号以及如何自定义信号。
信号的工作机制
Django 中的信号工作机制依赖如下三个主要要素:
- 发送者(sender):信号的发出方,可以是模型,也可以是视图。当某个操作发生时,发送者会发出信号。
- 信号(signal):发送的信号本身。Django内置了许多信号,比如模型保存后发出的
post_save
信号。 - 接收者(receiver):信号的接收者,其本质是一个简单的回调函数。将这个函数注册到信号上,当特定的事件发生时,发送者发送信号,回调函数就会被执行。
信号的应用场景
信号主要用于Django项目内不同事件的联动,实现程序的解耦。比如当模型A有变动时,模型B与模型C收到发出的信号后同步更新。又或当一个数据表数据有所改变时,监听这个信号的函数可以及时清除已失效的缓存。另外通知也是一个信号常用的场景,比如有人刚刚回复了你的贴子,可以通过信号进行推送。
注意:Django中信号监听函数不是异步执行,而是同步执行,所以需要异步执行耗时的任务时(比如发送邮件或写入文件),不建议使用Django自带的信号。
Django常用内置信号
python
#Model signals
pre_init # django的modal执行其构造方法前,自动触发
post_init # django的modal执行其构造方法后,自动触发
pre_save # django的modal对象保存前,自动触发
post_save # django的modal对象保存后,自动触发
pre_delete # django的modal对象删除前,自动触发
post_delete # django的modal对象删除后,自动触发
m2m_changed # django的modal中使用m2m字段操作第三张表(add,remove,clear)前后,自动触发
class_prepared # 程序启动时,检测已注册的app中modal类,对于每一个类,自动触发
Management signals
pre_migrate # 执行migrate命令前,自动触发
post_migrate # 执行migrate命令后,自动触发
Request/response signals
request_started # 请求到来前,自动触发
request_finished # 请求结束后,自动触发
got_request_exception # 请求异常后,自动触发
Test signals
setting_changed # 使用test测试修改配置文件时,自动触发
template_rendered # 使用test测试渲染模板时,自动触发
Database Wrappers
connection_created # 创建数据库连接时,自动触发
这些信号都非常有用。举个例子:使用pre_save
信号可以在将用户的评论存入数据库前对其进行过滤,或则检测一个模型对象的字段是否发生了变更。
注意 :监听pre_save
和post_save
信号的回调函数不能再调用save()
方法,否则回出现死循环。另外Django的update
方法不会发出pre_save
和post_save
的信号。
内置信号使用(当user表创建用户,就给用户发个邮件)
1 写个函数 #放到__init__里
python
from django.db.models.signals import pre_save
import logging
def callBack(sender, **kwargs):
logging.debug('%s创建了一个%s对象'%(sender._meta.model_name,kwargs.get('instance').title))
2 绑定内置信号
python
pre_save.connect(callBack)
3 等待触发
python
pre_save #django的modal对象保存前,自动触发
当save()之前会触发callBack函数
还有另一种写法内置信号
python
from django.db.models.signals import pre_save
from django.dispatch import receiver
#监听pre_save的触发,只要pre_save触发,就会触发my_callback函数
@receiver(pre_save)
def my_callback(sender, **kwargs):
print("对象创建成功")
print(sender)
print(kwargs)
#加装饰器的这种方法其实源码内部还是将自定义函数与内置信号绑定
def _decorator(func):
if isinstance(signal, (list, tuple)):
for s in signal:
s.connect(func, **kwargs)
else:
signal.connect(func, **kwargs)
return func
return _decorator
自定义信号:
1 定义信号(一般创建一个py文件)(toppings,size 是接受的参数)
python
import django.dispatch
pizza_done = django.dispatch.Signal(providing_args=["toppings", "size"])
2 注册信号
python
def callback(sender, **kwargs):
print("callback")
print(sender,kwargs)
pizza_done.connect(callback)
3 触发信号
python
from 路径 import pizza_done
pizza_done.send(sender='seven',toppings=123, size=456)
如何正确放置Django信号的监听函数代码
python
当一个app的与信号相关的自定义监听函数很多时,此时models.py代码将变得非常臃肿。
一个更好的方式把所以自定义的信号监听函数集中放在app对应文件夹下的signals.py文件里,
便于后期集中维护。
信号的用法:
1.做双写一致性的缓存更新
比如轮播图表,在redis中加了缓存,可以使用信号,当轮播图表更新了,就触发自定义的信号,删除redis中的缓存。
2.我们再来看一个复杂一点的例子。我们有一个Profile模型,与User模型是一对一的关系。我们希望创建User对象实例时也创建Profile对象实例,而使用post_save更新User对象时不创建新的Profile对象。这时我们就可以自定义create_user_profile和save_user_profile两个监听函数,同时监听sender(User模型)发出的post_save信号。由于post_save可同时用于模型的创建和更新,我们用if created这个判断来加以区别。
python
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
birth_date = models.DateField(null=True, blank=True)
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
def save_user_profile(sender, instance, **kwargs):
instance.profile.save()