Django 信号(Signals)详解(循序渐进)
一、信号(Signal)概述
1. 什么是 Django 信号?
Django 信号(Signal) 是一种 观察者模式(Observer Pattern) 的实现,允许不同部分的代码在发生特定事件时进行通信,而不需要直接调用。
作用:当某个事件发生时,Django 会自动通知相关的信号处理函数,让它们执行相应的操作。
2. 信号的核心概念
- 发送者(Sender):触发信号的对象(一般是 Django 模型)。
- 信号(Signal):预定义的信号类型,例如
post_save
、pre_delete
等。 - 接收器(Receiver):监听信号的函数,接收到信号后执行相应操作。
- 连接(Connect):将信号与接收器绑定,使其在事件发生时被触发。
二、Django 内置信号
信号名称 | 触发时机 |
---|---|
pre_save |
调用 save() 之前 |
post_save |
调用 save() 之后 |
pre_delete |
调用 delete() 之前 |
post_delete |
调用 delete() 之后 |
m2m_changed |
多对多字段修改时 |
request_started |
HTTP 请求开始时 |
request_finished |
HTTP 请求结束时 |
got_request_exception |
视图抛出异常时 |
pre_migrate |
迁移前 |
post_migrate |
迁移后 |
三、信号的基本使用
1. 监听 post_save
信号
场景: 当 User
被创建时,自动创建 Profile
。
(1)创建模型
from django.db import models
from django.contrib.auth.models import User
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(blank=True)
(2)在 signals.py
里定义信号
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from .models import Profile
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
"""当用户创建时,自动创建 Profile"""
if created:
Profile.objects.create(user=instance)
@receiver(post_save, sender=User)
: 监听User
模型的post_save
事件。created=True
时表示 新创建 的用户,此时自动创建Profile
。
(3)注册信号
在 apps.py
里:
from django.apps import AppConfig
class MyAppConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'myapp'
def ready(self):
import myapp.signals # 确保信号被加载
四、信号的高级用法
1. 使用 pre_delete
软删除
场景: 当 User
被删除时,不是真正删除,而是标记为已删除。
(1)修改模型
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
is_deleted = models.BooleanField(default=False) # 软删除标记
(2)监听 pre_delete
from django.db.models.signals import pre_delete
@receiver(pre_delete, sender=User)
def soft_delete_user(sender, instance, **kwargs):
"""用户删除时,不真正删除,而是标记 is_deleted"""
instance.is_deleted = True
instance.save()
pre_delete
触发时,不直接删除,而是修改is_deleted
。
2. 使用 m2m_changed
监听多对多关系
场景: 监听 Book
和 Author
之间的多对多关系变更。
(1)创建模型
class Author(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
authors = models.ManyToManyField(Author)
(2)监听 m2m_changed
from django.db.models.signals import m2m_changed
@receiver(m2m_changed, sender=Book.authors.through)
def book_authors_changed(sender, instance, action, **kwargs):
"""当 Book 的 authors 关系发生变化时触发"""
print(f"Action: {action}, Instance: {instance}")
m2m_changed
触发时,可以获取action
:pre_add
:添加前post_add
:添加后pre_remove
:移除前post_remove
:移除后pre_clear
:清空前post_clear
:清空后
五、手动连接和断开信号
1. 手动连接信号
除了使用 @receiver
,我们也可以手动绑定信号:
from django.db.models.signals import post_save
def my_signal_handler(sender, instance, **kwargs):
print(f"{instance} 被保存了!")
post_save.connect(my_signal_handler, sender=User)
优点:
- 可以在不同的地方注册信号,灵活性更高。
2. 断开信号
有时我们希望临时关闭某个信号:
post_save.disconnect(my_signal_handler, sender=User)
这样,post_save
信号就不会再触发 my_signal_handler
。
六、信号的调试和优化
1. 避免信号循环调用
如果 save()
方法中调用 save()
,可能会导致 无限循环:
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
instance.profile = Profile.objects.create(user=instance)
instance.save() # ⚠️ 这里会导致递归调用
解决方案:
- 在
save()
方法里 禁用信号:
from django.db.models.signals import post_save
@receiver(post_save, sender=User)
def create_user_profile(sender, instance, created, **kwargs):
if created:
post_save.disconnect(create_user_profile, sender=User)
instance.profile = Profile.objects.create(user=instance)
instance.save()
post_save.connect(create_user_profile, sender=User)
2. 使用 weak=False
避免接收器被垃圾回收
默认情况下,@receiver
使用弱引用,如果接收器没有其他引用,可能会被回收:
post_save.connect(my_signal_handler, sender=User, weak=False)
七、总结
知识点 | 说明 |
---|---|
post_save |
在模型 save() 之后触发 |
pre_save |
在模型 save() 之前触发 |
post_delete |
在 delete() 之后触发 |
pre_delete |
在 delete() 之前触发 |
m2m_changed |
监听多对多关系变更 |
@receiver |
绑定信号的装饰器 |
connect() |
手动绑定信号 |
disconnect() |
手动解绑信号 |
避免循环调用 | 在 save() 里 断开信号 处理 |
Django 信号提供了强大的事件监听能力,可以帮助我们自动执行任务、减少代码耦合,但需要注意避免循环调用、调试信号、提高性能。🚀