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}}