1-Django4.2 - 信号

before

Python3.10 + django4.2.3 + win11

自定义信号:https://docs.djangoproject.com/zh-hans/4.2/topics/signals/#defining-and-sending-signals
内置信号:https://docs.djangoproject.com/zh-hans/4.2/ref/signals/

Django有一个"信号调度器(signal dispatcher)"。其实就是观察者模式,又叫发布-订阅(Publish/Subscribe) 。当发生一些动作的时候,发出信号,然后监听了这个信号的函数就会执行。

通俗来讲,就是一些动作发生的时候,信号允许特定的发送者去提醒一些接受者。用于在框架执行操作时解耦。

在Django中,我们可以使用Django提供的内置信号,也可以自定义信号。

示例项目结构

在展开讲解之前,说下项目的主要文件:

demo4/					# 项目根目录
├── demo4  				# 项目同名目录
|    ├── urls.py        # 全局路由
|    └──  __init__.py   # 这个文件很重要,每次运行Django项目时,该文件都会执行,所以每当有需要启动项目做一些配置时,都可以在这个文件中完成,我演示内置信号时,在这里完成
├── api  				# 应用名
|    ├── views.py       # 主要示例位置
|    └──  models.py  
└── my_signal.py   		# 自定义信号文件,用于讲解自定义信号时使用

后续,操作起来,知道我的代码是在哪里进行的。

内置信号

Django4.2信号官档:https://docs.djangoproject.com/zh-hans/4.2/ref/signals/#signals

常用的内置信号

# -------------- 模型信号 --------------
# 每当实例化一个 Django 模型时,这个信号都会在模型的 __init__() 方法的开头发出
django.db.models.signals.pre_init

# 和 pre_init 一样,但这个是在 __init__() 方法完成后发送的
django.db.models.signals.post_init


# 这是在模型的 save() 方法开始时发送的
django.db.models.signals.pre_save

# 就像 pre_save 一样,但在 save() 方法的最后发送
django.db.models.signals.post_save
"""
注意,pre_save和post_save拿到的模型类对象是一样的,
所以,不要认为,如果想要实现拿到数据更改前和更改后的数据,用这俩是不行的,因为这俩拿到的模型类对象是一样的
后面我会举例子说明
"""


# 在模型的 delete() 方法和查询集的 delete() 方法开始时发送
django.db.models.signals.pre_delete

# 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送
django.db.models.signals.post_delete


# -------------- 请求/响应信号 --------------
# 当 Django 开始处理一个 HTTP 请求时发送
django.core.signals.request_started

# 当 Django 完成向客户端发送 HTTP 响应时发送
django.core.signals.request_finished

# 当 Django 在处理一个传入的 HTTP 请求时遇到异常时,就会发出这个信号
django.core.signals.got_request_exception

注意,关于请求和响应的信号,官档也说了:

警告

Signals can make your code harder to maintain. Consider using a middleware before using request/response signals.

信号会使代码更难维护。在使用请求/响应信号之前,请考虑使用中间件。

完整的内置信号

# -------------- 模型信号 --------------
# 每当实例化一个 Django 模型时,这个信号都会在模型的 __init__() 方法的开头发出
django.db.models.signals.pre_init

# 和 pre_init 一样,但这个是在 __init__() 方法完成后发送的
django.db.models.signals.post_init


# 这是在模型的 save() 方法开始时发送的
django.db.models.signals.pre_save

# 就像 pre_save 一样,但在 save() 方法的最后发送
django.db.models.signals.post_save
"""
注意,pre_save和post_save拿到的模型类对象是一样的,
所以,不要认为,如果想要实现拿到数据更改前和更改后的数据,用这俩是不行的,因为这俩拿到的模型类对象是一样的
后面我会举例子说明
"""

# 在模型的 delete() 方法和查询集的 delete() 方法开始时发送
django.db.models.signals.pre_delete

# 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送
django.db.models.signals.post_delete


# 当一个模型实例上的 ManyToManyField 被改变时发出。严格来说,这不是一个模型信号,
# 因为它是由 ManyToManyField 发送的,但由于它是对 pre_save/post_save 和 pre_delete/post_delete 的补充,
# 当涉及到跟踪模型的变化时,它被包含在这里
django.db.models.signals.m2m_changed

# 当一个模型类被“准备好”时发送——也就是说,一旦一个模型被定义并注册到Django的模型系统中。
# Django在内部使用这个信号;它通常不用于第三方应用程序。
django.db.models.signals.class_prepared


# -------------- 管理信号 --------------
# 管理信号是django-admin 发出的信号。
# 由 migrate 命令在开始安装应用程序之前发出。对于缺乏 models 模块的应用程序,它不会发出
django.db.models.signals.pre_migrate

# 在 migrate (即使没有运行迁移)和 flush 命令结束时发出。对于缺乏 models 模块的应用程序,它不会被发出
# 该信号的处理者不能进行数据库模式的改变,因为如果在 migrate 命令期间运行 flush 命令,可能会导致 flush 命令失败。
django.db.models.signals.post_migrate


# -------------- 请求/响应信号 --------------
# 当 Django 开始处理一个 HTTP 请求时发送
django.core.signals.request_started

# 当 Django 完成向客户端发送 HTTP 响应时发送
django.core.signals.request_finished

# 当 Django 在处理一个传入的 HTTP 请求时遇到异常时,就会发出这个信号
django.core.signals.got_request_exception


# -------------- 测试信号 --------------
# 只有当 运行测试 时才会发出信号
# 当通过 django.test.TestCase.settings() 上下文管理器或 django.test.override_settings() 装饰器/上下文管理器# 改变配置值时,会发出这个信号。
# 它实际上被发送了两次:当应用新的值时("setup")和当恢复原始值时("drawdown")。使用 enter 参数来区分这两种情况。
# 你也可以从 django.core.signals 导入这个信号,以避免在非测试情况下从 django.test 导入
django.test.signals.setting_changed

# 当测试系统渲染一个模板时发出。这个信号在 Django 服务器正常运行时不会发出,只有在测试时才会发出
django.test.signals.template_rendered


# -------------- 数据库包装器 --------------
# 当数据库连接启动时,数据库包装器发出的信号
# 当数据库包装器与数据库进行初始连接时发送。 如果你想向 SQL 后端发送任何连接后的命令,这一点特别有用。
django.db.backends.signals.connection_created

内置信号的使用

内置信号的基本写法

相关信号的回调函数所在文件

demo4\__init__.py中实现信号相关代码,里有两种方式触发信号的回调函数。

方式1:

# 导入相关信号
from django.db.models.signals import post_save

def my_callbak(sender, **kwargs):
    """ 回调函数 """
    print(sender, kwargs)

post_save.connect(my_callback)  # 信号触发回调函数

方式2:

# 导入相关信号
from django.core.signals import request_started, request_finished
# 以装饰器的形式激活信号,所以要先导入装饰器
from django.dispatch import receiver

@receiver(post_save)
def my_callbak(sender, **kwargs):
    """ 回调函数 """
    print(sender, kwargs)

具体示例演示

来了个问题:当对表做新增/更新/删除操作时,触发信号执行,我们可以通过示例中的代码执行情况,来决定什么场景下用什么信号搭配。

相关信号的回调函数所在文件都在demo4\__init__.py,并且代码不变了:

from django.db.models.signals import pre_init, post_init, pre_save, post_save, pre_delete, post_delete
from django.dispatch import receiver

@receiver(pre_init)
def pre_init_callback(sender, **kwargs):
    """ 每当实例化一个 Django 模型时,这个信号都会在模型的 __init__() 方法的开头发出 """
    print('pre_init_callback', sender)
    print('pre_init_callback', kwargs)


@receiver(post_init)
def post_init_callback(sender, **kwargs):
    """ 和 pre_init 一样,但这个是在 __init__() 方法完成后发送的 """
    print('post_init_callback', sender)
    print('post_init_callback', kwargs)


@receiver(pre_save)
def pre_save_callback(sender, **kwargs):
    """ 这是在模型的 save() 方法开始时发送的 """
    print('pre_save_callback', sender)
    print('pre_save_callback', kwargs)
    print('pre_save_callback', kwargs['instance'].name)


@receiver(post_save)
def post_save_callback(sender, **kwargs):
    """ 就像 pre_save 一样,但在 save() 方法的最后发送 """
    print('post_save_callback', sender)
    print('post_save_callback', kwargs)
    print('post_save_callback', kwargs['instance'].name)


@receiver(pre_delete)
def pre_delete_callback(sender, **kwargs):
    """ 在模型的 delete() 方法和查询集的 delete() 方法开始时发送 """
    print('pre_delete_callback', sender)
    print('pre_delete_callback', kwargs)


@receiver(post_delete)
def post_delete_callback(sender, **kwargs):
    """ 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送 """
    print('post_delete_callback', sender)
    print('post_delete_callback', kwargs)

models.py,并且代码不变,且手动做好了数据库的迁移命令了。

from django.db import models


class User(models.Model):
    name = models.CharField(max_length=64, default='')

    def __str__(self):
        return self.name

urls.py,并且代码不变了:

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

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

接下来,在视图中,调整代码,观察信号的执行情况。

新增操作

首先views.py代码长这样:

from django.shortcuts import HttpResponse
from api.models import User


def index(request):
    print('index')
	# 执行创建一条数据,下面两种写法都能触发信号
    # User.objects.create(name='zhangkai')
    obj = User(name='zhangkai')
    obj.save()
    return HttpResponse("index")

控制台关于信号的主要输出:

index
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF98437D60>, 'args': (), 'kwargs': {'name': 'zhangkai'}}
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF98437E80>, 'instance': <User: zhangkai>}
pre_save_callback <class 'api.models.User'>
pre_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF98437F70>, 'instance': <User: zhangkai>, 'raw': False, 'using': 'default', 'update_fields': None}
pre_save_callback zhangkai
post_save_callback <class 'api.models.User'>
post_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF984740A0>, 'instance': <User: zhangkai>, 'created': True, 'update_fields': None, 'raw': False, 'using': 'default'}
post_save_callback zhangkai

注释版:

# 请求进入视图函数
index

# 执行create命令时,内部首先会实例化一个模型类对象,所以pre_init在实例化对象时,信号触发于:__init__() 方法刚执行(pre_init)和执行结束(post_init)
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF98437D60>, 'args': (), 'kwargs': {'name': 'zhangkai'}}   # 实例化对象,拿到要创建的name值
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF98437E80>, 'instance': <User: zhangkai>}   # 创建好了User对象

# 上一步创建好了对象之后,紧接着要执行save方法了,那么save方法刚执行时,触发信号pre_save,此时我们是能在信号中拿到模型类对象的
pre_save_callback <class 'api.models.User'>
pre_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF98437F70>, 'instance': <User: zhangkai>, 'raw': False, 'using': 'default', 'update_fields': None}
pre_save_callback zhangkai

# save方法执行完了,信号post_save被触发,通过打印可以看出,此时的状态是'created': True  也可以看到此时的模型类对象和pre_save时的模型类对象是一个
post_save_callback <class 'api.models.User'>
post_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000002CF984740A0>, 'instance': <User: zhangkai>, 'created': True, 'update_fields': None, 'raw': False, 'using': 'default'}
post_save_callback zhangkai

编辑操作

首先views.py代码长这样:

from django.shortcuts import HttpResponse
from api.models import User


def index(request):
    print('index')

    # 首先,这么写update,是不会触发模型信号的
    # User.objects.filter(name='zhangkai3').update(name='zhangkai4')

    # 必须是save才可以触发信号
    obj = User.objects.filter(name='zhangkai4').first()
    obj.name = "zhangkai5"
    obj.save()
    return HttpResponse("index")

控制台关于信号的主要输出:

index
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354B7D60>, 'args': (2, 'zhangkai4'), 'kwargs': {}}
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354B7E80>, 'instance': <User: zhangkai4>}
pre_save_callback <class 'api.models.User'>
pre_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354B7F70>, 'instance': <User: zhangkai5>, 'raw': False, 'using': 'default', 'update_fields': None}
pre_save_callback zhangkai5
post_save_callback <class 'api.models.User'>
post_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354F40A0>, 'instance': <User: zhangkai5>, 'created': False, 'update_fields': None, 'raw': False, 'using': 'default'}
post_save_callback zhangkai5

注释版:

# 请求进入视图函数
index

# 更新的话,也是要先拿到被更新的对象,所以这里算是拿到了这个对象更新之前的原数据了
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354B7D60>, 'args': (2, 'zhangkai4'), 'kwargs': {}}
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354B7E80>, 'instance': <User: zhangkai4>}

# 更新开始以及更新后,拿到的数据就是更新后的对象,所以,如果有需求要记录数据变更前后的状态,可以用这四个触发器进行结合或者post_init和post_save这俩就能搞定
pre_save_callback <class 'api.models.User'>
pre_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354B7F70>, 'instance': <User: zhangkai5>, 'raw': False, 'using': 'default', 'update_fields': None}
pre_save_callback zhangkai5
post_save_callback <class 'api.models.User'>
post_save_callback {'signal': <django.db.models.signals.ModelSignal object at 0x000001F0354F40A0>, 'instance': <User: zhangkai5>, 'created': False, 'update_fields': None, 'raw': False, 'using': 'default'}
post_save_callback zhangkai5

删除操作

首先views.py代码长这样:

from django.shortcuts import HttpResponse
from api.models import User


def index(request):
    print('index')
    User.objects.filter(name='zhangkai7').delete()
    return HttpResponse("index")

控制台关于信号的主要输出:

index
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087D60>, 'args': (1, 'zhangkai7'), 'kwargs': {}}
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087E80>, 'instance': <User: zhangkai7>}
pre_delete_callback <class 'api.models.User'>
pre_delete_callback pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087D60>, 'args': (1, 'zhangkai7'), 'kwargs': {}}
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087E80>, 'instance': <User: zhangkai7>}
{'signal': <django.db.models.signals.ModelSignal object at 0x00000135280C4190>, 'instance': <User: zhangkai7>, 'using': 'default', 'origin': <QuerySet [<User: zhangkai7>]>}
post_delete_callback <class 'api.models.User'>
post_delete_callback {'signal': <django.db.models.signals.ModelSignal object at 0x00000135280C4280>, 'instance': <User: zhangkai7>, 'using': 'default', 'origin': <QuerySet []>}

注释版:

# 请求进入视图函数
index

# 删除的话,也是要先拿到被删除的对象,所以这里算是拿到了这个对象删除之前的原数据了
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087D60>, 'args': (1, 'zhangkai7'), 'kwargs': {}}
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087E80>, 'instance': <User: zhangkai7>}

# 接下来的执行流程就有点难理解了,先看删除相关的两个信号的意思
# pre_delete 在模型的 delete() 方法和查询集的 delete() 方法开始时发送
# post_delete 就像 pre_delete 一样,但在模型的 delete() 方法和查询集的 delete() 方法结束时发送
# 所以,当执行模型的delete方法,触发删除的两个信号执行
pre_delete_callback <class 'api.models.User'>
pre_delete_callback 
# 然后又执行了queryset的delete方法,又触发删除的两个信号执行
pre_init_callback <class 'api.models.User'>
pre_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087D60>, 'args': (1, 'zhangkai7'), 'kwargs': {}}
# 下面打印可以看到,拿到了查询集
post_init_callback <class 'api.models.User'>
post_init_callback {'signal': <django.db.models.signals.ModelSignal object at 0x0000013528087E80>, 'instance': <User: zhangkai7>}
{'signal': <django.db.models.signals.ModelSignal object at 0x00000135280C4190>, 'instance': <User: zhangkai7>, 'using': 'default', 'origin': <QuerySet [<User: zhangkai7>]>}
# 删掉查询集中的模型类对象,成功之后,查询集为空了
post_delete_callback <class 'api.models.User'>
post_delete_callback {'signal': <django.db.models.signals.ModelSignal object at 0x00000135280C4280>, 'instance': <User: zhangkai7>, 'using': 'default', 'origin': <QuerySet []>}

自定义信号

还是说一下如何自定义新号吧,毕竟更灵活。

自定义信号的使用,分三步。

1. 定义信号

my_signal.py中实现你的自定义信号:

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

# 实例化一个自定义信号,信号名随意
pizza_done = django.dispatch.Signal()

"""
# Signal源码
class Signal:
    def __init__(self, use_caching=False):  
        """
        Create a new signal.
        use_caching=False表示我们不使用缓存,内部也会创建一个空缓存
        """
        self.receivers = []
        self.lock = threading.Lock()
        self.use_caching = use_caching
"""

2. 编写信号的回调函数

demo4\__init__.py中:

from django.dispatch import receiver
from my_signal import pizza_done


@receiver(pizza_done)
def pizza_done_callback(sender, **kwargs):
    """ 回调函数 """
    print("pizza_done_callback", sender)
    print("pizza_done_callback", kwargs)

3. 在需要的地方触发信号

views.py:

from django.shortcuts import HttpResponse
from api.models import User
from my_signal import pizza_done  # 在需要的地方导入自定义的信号

class A:
    pass

def index(request):
    print('index')

    # 首先,下面定义这些变量或者对象,都是再说,可以传递给你信号内部使用的
    user_obj = User.objects.filter(name='zhangkai')
    a = A()
    d = {"name": "zhangkai"}
    li = [1, 2, 3, 4]
    t = (1, 2, 3)
    se = {1, 2, 3}
    # 注意,sender的值必须是可哈希的,也就是你不能传例如列表、字典、set,通常sender我们传递一个类或者对象
    # 除了sender作为第一个参数之外,其他参数任意,参数的值任意
    pizza_done.send(sender=user_obj, a=a, d=d, t=t, li=li, se=se)  # 触发信号的回调函数执行
    return HttpResponse("index")

打印结果:

index
pizza_done_callback <QuerySet [<User: zhangkai>, <User: zhangkai>, <User: zhangkai>, <User: zhangkai>]>
pizza_done_callback {'signal': <django.dispatch.dispatcher.Signal object at 0x000002D99AD17460>, 'a': <api.views.A object at 0x000002D99BAC9570>, 'd': {'name': 'zhangkai'}, 't': (1, 2, 3), 'li': [1, 2, 3, 4], 'se': {1, 2, 3}}
posted @ 2023-07-29 14:06  听雨危楼  阅读(138)  评论(0编辑  收藏  举报