一、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