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