111.Django信号详解

1. 概述

​ 信号可以在框架中的其他位置发生操作时通知分离的应用程序,简而言之,就是信号允许特定的sender通知一组receiver某些操作已经发生,这在多处代码和同一事件有关联的情况下很有用。

2. 内置信号

内置信号的完整文档:https://docs.djangoproject.com/zh-hans/2.2/ref/signals/

模型层中定义的内置信号,在 django.db.models.signals 模块中:

  1. pre_init:实例化模型时,此信号在模型的_init_()方法的开头发送

    此信号发送的参数:

    • instance:模型类
    • args:模型初始化的位置参数
    • kwargs:模型初始化的关键字参数
  2. post_init:和pre_init一样,但是这个_init_()方法在方法完成时发送

    此信号发送的参数:

    • instance:刚刚初始化完成的模型实例
  3. pre_save:在模型 save()方法开始时发送

  4. post_save:在模型 save()方法完成时发送

  5. pre_delete:在模型 delete()方法开始时发送

  6. post_delete:在模型 delete()方法结束时发送

  7. m2m_changed:多对多关系中,模型更改时发送

对web请求,也有内置信号,在 django.core.signals 模块中

  1. request_started:Django开始处理HTTP请求时发送
  2. request_finished:当Django完成向客户端发送HTTP响应时发送

3. 定义信号

所有的信号都是django.dispatch.Signal实例

初始化参数:providing_args=None, use_caching=False

  • providing_args:信号将为sender提供的参数名称列表
  • use_caching:默认为False,是否使用缓存,设置为True,会为每个sender对应的接收器进行缓存(保存到sender_receivers_cache),调用 .connect() 或 .disconnect() 后会清除缓存。内置信号基本都设置为True

示例:

from django import dispatch

pizza_done = dispatch.Signal(providing_args=["toppings", "size"])

4. 接收器receiver

接收器可以是任何python函数或方法,参数最少必须是:sender, **kwargs,示例如下:

def my_callback(sender, **kwargs):
    print("第一个接收器函数!")
    
def my_callback1(sender, arg1=None, **kwargs):
    print("第二个接收器函数!")

参数说明:

  • sender:信号的发送对象,其实可以是任意对象,取决于发送时传递的参数
  • **kwargs:信号对象定义时的providing_args指定的参数

5. 信号注册(连接到接收器)

有两种方式可以将信号连接上接收器

  1. connect方法

    使用 Signal.connect() 监听信号,发送信号时调用接收器函数,Signal是信号对象

    Signal.connect(receiver,sender = None,weak = True,dispatch_uid = None)

    参数说明:

    • receiver - 将连接到此信号的回调函数

    • sender - 指定信号的特定发送者,为None就是任何发送者都接收

    • weak - Django默认将信号处理程序存储为弱引用。因此,如果您的接收器是本地函数,它可能是垃圾收集。为了防止这种情况,请在调用信号connect()方法时设置weak=False

    • dispatch_uid - 在可能发送重复信号的情况下信号接收器的唯一标识符,就是一个不重复的字符串

    示例如下:

    from django.core.signals import request_finished
    
    request_finished.connect(my_callback)
    
    
  2. receiver装饰器

    receiver(signal, **kwargs)

    参数说明:

    • signal:信号对象或信号对象列表
    • **kwargs:这个装饰器也是使用的connect方法,这个关键字参数就是传递给connect方法的关键字参数

    receiver源码:

    def receiver(signal, **kwargs):
        """
        A decorator for connecting receivers to signals. Used by passing in the
        signal (or list of signals) and keyword arguments to connect::
    
            @receiver(post_save, sender=MyModel)
            def signal_receiver(sender, **kwargs):
                ...
    
            @receiver([post_save, post_delete], sender=MyModel)
            def signals_receiver(sender, **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
    

    使用示例如下:

    from django.core.signals import request_finished
    from django.dispatch import receiver
    
    @receiver(request_finished)
    def my_callback(sender, **kwargs):
        print("Request finished!")
        
    
  3. 只接收特定发送者发送的信号

    # 只接收特定模型MyModel发送的信号
    @receiver(pre_save, sender=MyModel)
    def my_handler(sender, **kwargs):
        ...
    
  4. 防止重复信号

    在某些情况下,将接收器连接到信号的代码可能会多次运行,这可能导致接收器功能被多次注册,因此对于单个信号事件被多次调用(例如,在保存模型时使用信号发送电子邮件时),使用唯一标识符作为dispatch_uid参数以标识接收方函数,此标识符通常使用字符串,使用后有确保接收器仅对每个唯一dispatch_uid值绑定一次信号:

    request_finished.connect(my_callback, dispatch_uid="my_unique_identifier")
    

注意事项:

上述的信号定义和注册代码,理论上可以在任意代码位置,但是模型相关的信号建议避免在应用程序的根模块及其models模块中注册,以尽量减少 import 代码的副作用

通常情况下:

  • 信号定义:在对应子应用的signals模块中,如果使用connect注册的话,receive也定义在 signals 模块中

  • 信号注册:在对应子应用的 apps 中的 AppConfig 类中的 ready 方法中实现,示例如下:

    apps.py:

    class TestAppConfig(AppConfig):
        name = 'test_app'
    
        def ready(self):
            # 通过 get_model 来获取特定模型对象,参数 MyModel 就是 模型类的名称字符串
            pre_save.connect(receiver, sender=self.get_model('MyModel'))
    

6. 信号断开连接

信号要断开连接,使用以下方法:

Signal.disconnect(receiver = None,sender = None,dispatch_uid = None)

参数说明见 Signal.connect

receiver参数表示已注册的接收器断开连接,当使用了dispatch_uid时,这个参数可以为None

返回值:如果有接收器被断开连接则返回True,没有则返回False

7. 发送信号

两种发送信号的方法:

  1. Signal.send(sender, **kwargs):所有内置信号都是使用此方法发送信息。这个方法不能捕获由接收器抛出的异常; 它只是允许错误向上传播,因此,在出现错误时,不是所有接收器都可以被通知信号。

  2. Signal.send_robust(sender, **kwargs):捕获从Exception类派生的所有错误,并确保所有接收器都收到信号通知。如果发生错误,则会在引发错误的接收器的元组对中返回错误实例。正常的返回值是:[(receiver, response), … ],response是receiver的返回值,发生错误时是[(receiver, err), … ],错误的tracebacks保存在 err.__traceback__属性上

8. 项目中应用信号

8.1 内置信号的使用

app.py

from django.apps import AppConfig
from django.db.models.signals import *
from .signals import pre_save_receiver
from django.dispatch import receiver

# 保存之后:内置信号方法2:使用装饰器
@receiver(post_save, dispatch_uid='signals_app_post_save_receiver')
def post_save_receiver(sender, **kwargs):
    print(f'保存模型之后sender:{sender},kwargs:{kwargs}')
class SignalsAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'signals_app'
    # 注册信号
    def ready(self) -> None:
        pre_save.connect(pre_save_receiver, sender=self.get_model('Person')) # 指定了模型

views.py

from django.shortcuts import render,HttpResponse
from .models import Person
from django.dispatch import Signal
# Create your views here.
def pre_save_person(request):
    Person.objects.create(pname = '小赵', age=23)
    return HttpResponse('内置信号测试')

signals.py

# signals_study/signals_app/signals.py

# 定义信号:方法1,connect注册调用信号
def pre_save_receiver(sender, **kwargs):
    print(f'保存模型之前sender:{sender},kwargs:{kwargs}')

在这里插入图片描述

8.2 自定义信号

views

from django.shortcuts import render,HttpResponse
from .models import Person
from django.dispatch import Signal
from.signals import index_signals
# Create your views here.
def pre_save_person(request):
    Person.objects.create(pname = '小赵', age=23)
    return HttpResponse('内置信号测试')

def signals_test(request):
    # 发送信号
    index_signals.send(sender='signals_test',request=request)
    return HttpResponse('自定义信号测试')

定义信号

# signals_study/signals_app/signals.py

# 定义接收器:方法1,connect注册调用信号
from django import dispatch
from django.dispatch import Signal
from django.dispatch.dispatcher import receiver
def pre_save_receiver(sender, **kwargs):
    print(f'保存模型之前sender:{sender},kwargs:{kwargs}')

# 自定义信号
from django.dispatch import Signal
index_signals = Signal(providing_args=['request'])

# 定义接收器
@receiver(index_signals,dispatch_uid='signals_app_index_signals_receiver') #注册接收器:信号名,(uid 子应用名_接收器名)
def index_signals_receiver(sender, **kwargs):
    request = kwargs.get('request')
    ip = request.META['REMOTE_ADDR']
    print(f'sender:{sender}自定义信号测试Ip:{ip},kwargs:{kwargs}')

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想成为数据分析师的开发工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值