Django中的信号signal【新编辑】

   在真实的企业生产环境中,我们会遇到各种各样的需求,比如对客户端请求进行过滤,将满足某些条件的客户端请求过滤掉,这时我们可以利用Django的中间件来实现该需求;或者,我希望每次model的save()方法被调用前后,都要写一条日志到日志文件中,而此时我们可以通过Django提供的内置信号post_save来实现,本文介绍一下Django的高级功能——信号。

  “信号分发器”允许解耦的应用在框架的其它地方发生操作时会被通知到。 简单来说,信号允许特定的sender通知一组receiver某些操作已经发生。 这在多处代码和同一事件有关联的情况下很有用。

信号的概念

  Django2.1官文对signal的解释如下: 

Django includes a "signal dispatcher" which helps allow decoupled applications get notified when actions occur elsewhere in the framework.
In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.
They're especially useful when many pieces of code may be interested in the same events.

  翻译过来就是:Django框架内部包含了一个信号调度器,它的作用是可以将框架内部发生的任何操作都通知到功能独立的应用程序,当然,我们也可以缩小发送者和接收者的范围,即指定具体的发送者和接受者,假设我们的程序中有多个业务逻辑都在等待某一个事件发生之后再继续执行后面的代码,那么此时,信号是非常有用的。

Django内置的信号

  Django内置的信号如下(加粗的是比较常用的):

Model signals
    pre_init                    # django的model执行其构造方法前,自动触发
    post_init                   # django的model执行其构造方法后,自动触发
    pre_save                    # django的model对象保存前,自动触发
    post_save                   # django的model对象保存后,自动触发
    pre_delete                  # django的model对象删除前,自动触发
    post_delete                 # django的model对象删除后,自动触发
    m2m_changed                 # django的model中使用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          # 创建数据库连接时,自动触发

Django内置信号的使用 

注册内置信号

  对于Django内置的信号,仅需注册指定信号,当程序执行相应操作时,自动触发注册函数。注册信号,写入与project同名的文件夹下的_init_.py文件中,也是换数据库引擎的地方。

   如上图,项目名为signalTest,我们在与项目同名的包的__init__.py文件中注册Django内置的信号,然后使用就可以了。

监听信号的两种方式

方式一:直接监听

def my_callback(sender, **kwargs):
    print("Request finished!")

from django.core.signals import request_finished request_finished.connect(my_callback)

方式二:使用装饰器监听

def my_callback(sender, **kwargs):
    print("Request finished!")
from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

注册时指定发送者

from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import MyModel

# 在MyModel对应的数据保存前执行my_handler函数
@receiver(pre_save, sender=MyModel)
def my_handler(sender, **kwargs):
    xxx

  注册完内置信号后,只要满足触发条件,对应的函数会在相应的条件下去执行,不用人手动去调用。  

内置信号详细介绍 ***

 

  更多内置信号的说明请参考这篇博客:https://www.cnblogs.com/liwenzhou/p/9745331.html

自定义信号的使用 *****

  首先要知道的是,我们用的信号都是django.dispatch.Signal这个类的实例。

首先定义信号 

  在任意的py文件中定义信号:

import django.dispatch
signal_done = django.dispatch.Signal(providing_args=["height", "width"])

然后在与项目同名的包的__init__文件中注册信号

def callback(sender, **kwargs):
    print("callback")
    print(sender,kwargs)
 
signal_done.connect(callback)

最后在需要触发信号的地方使用自定义信号

from 路径 import signal_done
 
signal_done.send(sender='Naruto',height=123, width=456)

  最后需要注意:由于内置信号的触发者已经集成到Django中,所以其会自动调用;而对于自定义信号则需要开发者在对应位置指定触发

在路由分发前注册信号的方法 *****

  上面介绍的一种方式是在项目同名的包的__init__.py文件中去注册信号

  我们也可以利用Django在路由分发之前做一下信号的注册操作。 

  这里用到了Django启动的机制:django.dispatch.Signal在django.setup()的过程中,它会遍历settings.INSTALLED_APPS列表中的每一项,并调用该AppConfig的ready方法,因此,将recevier订阅signal的过程放置于ready方法中就能保证该代码的执行。

   我们来拿一个具体的项目为例。

一、项目的目录结构如下

  (1)把所有的信号都写在了signals包中,并且signals包中的__init__.py文件中实例化Signal类的对象(注意Python在import一个包的时候会执行里面的__init__文件),执行的操作我写在了handlers.py文件中;

  (2)然后,利用Django的启动的原理,我把信号的注册写在了apps.py的SignalappConfig类的ready方法中,保证在路由分发之前就注册自定义的信号。

二、注册信号的具体写法

signals/__init__.py:

# -*- coding:utf-8 -*-
from django.dispatch import Signal

my_signal = Signal(providing_args=[])

signals/handlers.py:

# -*- coding:utf-8 -*-

def my_callback(sender,**kwargs):
    print('my_callback...')

signalapp/apps.py

from django.apps import AppConfig

# 从外部导入自定义信号及处理的函数
from signals import my_signal
from signals.handlers import my_callback

class SignalappConfig(AppConfig):
    name = 'signalapp'

    def ready(self):
        # 注册信号
        my_signal.connect(my_callback)

三、使用自定义的信号

  做一个简单的路由与视图测试一下这个自定义的信号是否成功:

  signalTest/urls.py:

from django.contrib import admin
from django.urls import path

from signalapp import views


urlpatterns = [
    path('admin/', admin.site.urls),

    path('index/',views.index,name='index'),
]

  sjgnalapp/views.py:

from django.shortcuts import render,HttpResponse

# 导入自定义信号的处理函数
from signals.handlers import my_callback


def index(request):
    
    # 使用自定义信号
    my_callback(sender='index')
    return HttpResponse('OK')

  启动Django程序后我们在浏览器中输入127.0.0.1:8000/index,可以看到在后台打印出了自定义信号处理函数中所打印的数据: 

my_callback...

定制信号的发送者

  默认情况下,某些信号会被多次发送,但是,通常,我们只希望接收某个或者某些特定的发送者发出的信号,比如说django.db.models.signals.pre_saves,该信号,它在每个model的save()方法被执行的时候被发送,不过,很多情况下,我们只想记录某个特定的model的save()方法被执行时的日志。
  在上述情况下,我们可以指定只接受我们指定的信号发送者发出的信号。
  还是用django.db.models.signals.pre_saves举例,下面我们来演示如何指定发送者

from django.db.models.signals import pre_save
from django.dispatch import receiver from myapp.models import MyModel @receiver(pre_save, sender=MyModel) def my_handler(sender, **kwargs): xxx

断开信号 

  如果不希望再接收某个信号,我们可以调用Signal.disconnect()方法。

更多内容详见官网文档

Django2.1信号signal文档

posted on 2018-03-20 23:06  江湖乄夜雨  阅读(874)  评论(0编辑  收藏  举报