一、Django信号定义:
Django包含“信号调度器”,可以在Django框架运行函数时,发送信号给指定的接收函数,使指定的接收函数做相应的操作。
信号发生器是高耦合的程序典范。
二、Django的内置信号种类:
Django官网关于信号https://docs.djangoproject.com/en/3.0/ref/signals/
1、Models信号
from django.db.models.signals import pre_init
pre_init
# 实例化Model之前(在Models的__init__之前),发送信号
# sender:创建的模型类名
# 传递到__init__的列表参数
# 传弟到__init__的字典参数
post_init(sender, instance)
# 实例化Models时,在Models的__init__完成之后,发送信号
# serder: 创建的模型名
# instance: 模型的实例
pre_save
# 在Models的save方法前,发送信号
# sender: 创建的模型名
# instance: 模型的实例
# raw:A boolean; True if the model is saved exactly as presented (i.e. when loading a fixture). One should not query/modify other records in the database as the database might not be in a consistent state yet.
# using:正在使用的数据库别名
# update_fields: 指定model.save()更新的字段,如果不指定,默认为None
post_save
# 在Models的save方法之后。
# sender:模型类名
# instance: 一个被保存的确切的实例
# created: A boolean; True if a new record was created.
# raw: 同pre_save
# using: 同pre_save
# update_fields: 同pre_save
pre_delete
# 的Models和Queryset的delete方法开始之前
# sender: 创建的模型名
# instance: 一个被删除的确切的实例
# using: 正在使用的数据库的别名
post_delete
# Models和Queryset的delete方法之后
# sender: 模型的类名
# instance: 一个确切的实例 ,#请注意,对象将不再在数据库中,因此要非常小心地处理这个实例。
# using: 数据库别名
m2m_changed
# 在更改ManyToManyField时发送信号。action指定了跟踪的变更方法,看起来和上面几个不太一样,它不是严格意义的信号
class_prepared()
# 模型类已经“准备”时发送——也就是说,在模型已经被Django的模型系统定义和注册之后。
https://docs.djangoproject.com/en/3.0/ref/signals/
2、管理类信号
from django.db.models.signals import pre_migrate
per_migrate
# 在migrate命令之前发送信号
post_migrate
# 在migrate和flush命令结束后发送信号
3、请求/响应信号
from django.core.signals import request_started
request_started
# 处理客户端请求前
request_finished
# 向客户端发送响应后
got_request_exception
# 处理客户端请求的过程中,出现错误时,发送信号
4、测试信号
form django.test.signals import setting_changed
setting_changed
# 当通过django.test.TestCase.settings()上下文管理器或django.test.override_settings()装饰器/上下文管理器更改设置的值时,将发送此信号。它实际上发送了两次:应用新值时(“setup”)和恢复原始值时(“teardown”)。使用enter参数来区分这两者。
template_rendered
# 当渲染模板时,只在测试系统起作用
5、数据库包装
from django.db.backends.signals import connection_created
connection_created
# 启动数据库连接时数据库包装器发送的信号。如果您想将任何post连接命令发送到SQL后端,这一点特别有用。
三、监听信号
要接收一个信号,使用signal .connect()方法注册一个接收器函数。当信号发送时调用receiver函数。按照函数注册的顺序。接收函数一次调用一个。
Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None)
# receiver: 接收信号的函数
# sender: 信号发送者
# weak: 信号接收函数与信号发送者是弱关联,如果接收函数是本地函数经常被当作垃圾回收,设置为weak=False更改
# dispatch_uid: 唯一标识,如果同一个
# 方法一:使用connect注册
from django.core.signals import requst_finished
def my_callback(sender,*args, **kwgrgs): # 定义一个接收信号的函数
print("request finished!")
request_finished.connect(my_callback) # 注册接收信号的函数
# 方法二:使用装饰器
# 1、
from django.core.signals import request_finished
from django.dispach import receiver
@receiver(request_finished) # 使用装饰器,每次请求完成后my_callback执行一次
def my_callback(sender,*args, **kwgrgs): # 定义一个接收信号的函数
print("request finished!")
# 2、指定装饰某个确定的发送者
from django.db.models.signals import pre_save # 导入pre_save信号
from django.dispatch import receiver # 导入装饰器
from myapp.models import MyModel # 导入DB模型
@receiver(pre_save, sender=MyModel) # 只有MyModel表保存的时候才发送信号给my_handler
def my_handler(sender, **kwargs):
print('pre save!')
# 如何防止重复的信号
# 在某些情况下,连接接收器和信号的代码可能会运行多次。这可能导致您的接收器函数被多次注册,从而对单个信号事件调用多次。
# 如果此行为存在问题(例如在模型保存时使用信号发送电子邮件),则传递惟一标识符作为dispatch_uid参数,以标识接收方函数。这个标识符通常是一个字符串,尽管任何hashable对象都足够了。最终结果是,对于每个唯一的dispatch_uid值,你的接收器函数将只被绑定到信号一次:
from django.core.signals import request_finished
request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
四、自定义信号
为什么要用自定义信号?
隐式调用函数的信号,不方便调式。如果发送和接收信号都在一个项目中,最好使用显式函数调用,方便调试。
# ------1、声明一个信号:--------
from django.dispatch import Signal # 导入信号对象
pizza_done = Signal(providing_args=["toppings", "size"])
# 声明pizza_done信号,并使用providing_args参数,指定了接收者toppings和size
# 注意:providing_args列表可以在任何需要的时候更改,所以没有必要首次就尝试获取一个正确的API
# -----2、发送信号-------
# 在Django中有两种发送信号的方式
# Signal.send(sender, **kwargs)[source] # 不捕获错误,接收器在遇到错误时,有可能不会收到信号
# Signal.send_robust(sender, **kwargs)[source] # 捕获错误,并确保所有接收器都接收信号
# sender参数:通常是类
# **kwargs : 和声明信号时,参数providing_args相同?
class PizzaStore:
...
def send_pizza(self, toppings, size):
pizza_done.send(sender=self.__class__, toppings=toppings, size=size) # pizza_done使用send方法发送
...
#-----3、信号和接收器断开-----
# Signal.disconnect(receiver=None, sender=None, dispatch_uid=None)
# 参数与Signal.connect()相同
# 如果接收器断开,返回值是True,否则,返回值是False
# 接收器参数指示已注册的接收器断开连接。如果使用dispatch_uid来标识接收器,则它可能是"无"。
五、实例:
1、使用自带的pre_request信号
# 在任意位置新建一个新的pyton文件,这里我在myapp下新建一个custom_signal.py文件,用户来定义信号
from django.core.signals import request_start # 导入自带的request_start信号
def re_callback(sender, **kwargs): # 定义Callback函数
print('request start!')
request_start.connect(re_callback) # 注册信号
# 使用信号,在要使用信号的APP的__init__.py文件下,导入注册的信号文件,自动触发信号
form myapp import custom_signal
2、自定义信号的使用
# 还是在myapp下的custom_signal.py编辑
from django.signals import Signal
pizza_done = Signal.dispatch(provid_args=['toppings', 'size'])
def my_callback(sender, **kwargs)
print('pizza done!')
print(kwargs['toppings'])
print(kwargs['size'])
pozza_done.connect(my_callback)
# 调用:函数在哪调用,就在哪里导入。例:我在访问index时让发送信号
# views.py
from myapp import custom_signal
from django.shortcuts import render
def index(request):
from myapp import custom_signal
custom_signal.pizza_done.send(sender='zen meban', toppings='tttt', size='ssss')
return render(request, 'index.html')
# 结果:当客户端访问index时,后端显示
callback test!
tttt
ssss
https://docs.djangoproject.com/en/3.0/topics/signals/#receiver-functions