Django signal system - event-driven programming model | Daoman PythonAI
#django signal system - event-driven programming model
📂 Stage: Part 2 - Advanced Features
🎯 Difficulty level: Intermediate
⏰ Estimated study time: 2 hours
🎒 Prerequisite knowledge: 模型设计与ORM操作
Core concept: What is a Django signal?
Django signal is the standard implementation of Observer Pattern, used for loosely coupled application component communication: when an "event" occurs (such as model save/delete, request start/end), the signal is triggered and all receivers who "subscribe" to the signal are automatically notified.
Three core roles of signals
Get started quickly: the most basic signal usage
1. Introduce dependencies
from django.dispatch import Signal, receiver
from django.db.models.signals import post_save
from myapp.models import User
2. Receiver registration (two methods)
Method 1: Decorator syntax (recommended)
@receiver(post_save, sender=User)
def send_welcome_email(sender, instance, created, **kwargs):
"""新用户注册成功后发送欢迎邮件"""
if created:
# 实际项目请用Celery异步
print(f"向 {instance.email} 发送欢迎邮件")
Method 2: Manual connection
def create_user_profile(sender, instance, created, **kwargs):
"""新用户注册成功后创建档案"""
if created:
from myapp.models import UserProfile
UserProfile.objects.create(user=instance)
# 在 apps.py 的 ready() 中调用此代码
post_save.connect(create_user_profile, sender=User)
3. Signal triggering (no need to write additional code)
when executingUser.objects.create_user(...)oruser.save()hour,post_saveThe signal will trigger automatically.
Commonly used built-in signals
Django has three types of commonly used signals built-in, covering core scenarios.
1. Model signal (most commonly used)
Automatically triggered when operating the database model:
from django.db.models.signals import (
pre_init, # 模型实例化前(几乎不用)
post_init, # 模型实例化后
pre_save, # 模型保存前
post_save, # 模型保存后(★核心:区分created/更新)
pre_delete, # 模型删除前
post_delete, # 模型删除后
m2m_changed, # 多对多关系变更
)
Model signal example: automatically update article word count
from django.db.models.signals import pre_save
from django.dispatch import receiver
from myapp.models import Article
@receiver(pre_save, sender=Article)
def auto_count_words(sender, instance, **kwargs):
"""文章保存前自动统计字数"""
if instance.content:
instance.word_count = len(instance.content.strip().split())
2. Request signal
Triggered when processing an HTTP request:
from django.core.signals import (
request_started, # 请求开始
request_finished, # 请求结束
got_request_exception, # 请求异常(可用于全局错误记录)
)
3. Application/Project Signal
from django.db.models.signals import (
pre_migrate, # 迁移执行前
post_migrate, # 迁移执行后(★核心:创建初始数据)
)
Apply Signal Example: Create Super Administrator after Migration
from django.db.models.signals import post_migrate
from django.dispatch import receiver
from django.contrib.auth.models import User
@receiver(post_migrate)
def create_superuser(app_config, **kwargs):
"""仅在myapp迁移完成后创建超级管理员"""
if app_config.name == 'myapp':
if not User.objects.filter(username='admin').exists():
User.objects.create_superuser(
'admin', 'admin@example.com', 'admin123'
)
Custom signals: meet personalized needs
When built-in signals are not enough, custom signals can be created.
1. Define custom signals
# myapp/signals.py
from django.dispatch import Signal
# 定义一个「订单完成」的信号,携带order、user参数
order_completed = Signal()
2. Send custom signals
# myapp/services.py
from myapp.models import Order
from myapp.signals import order_completed
def complete_order(order_id):
"""标记订单完成并发送信号"""
order = Order.objects.get(id=order_id)
order.status = 'COMPLETED'
order.save()
# 发送信号(★显式调用)
order_completed.send(
sender=Order,
order=order,
user=order.user
)
3. Receive custom signals
# myapp/signals.py
from django.dispatch import receiver
from .models import Order
@receiver(order_completed, sender=Order)
def send_order_confirmation(sender, order, user, **kwargs):
"""订单完成后发送确认通知"""
print(f"向 {user.email} 发送订单 {order.id} 的确认")
Best Practices and Pitfall Guidelines
⚠️ Pitfall 1: Signals must be imported when the application starts
If the signal module is not imported, the receiver will not take effect! must be inapps.pyofready()Explicit import in:
# myapp/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = 'myapp'
def ready(self):
# 显式导入信号模块,触发装饰器的注册逻辑
import myapp.signals
⚠️ Pitfall 2: The default is synchronous blocking
If the signal receiver performs time-consuming operations (such as sending emails, calling APIs), it will directly block the main thread! Asynchronous tasks must be used (such as Celery):
# myapp/signals.py
from celery import shared_task
@shared_task
def async_send_welcome_email(user_id):
"""异步发送欢迎邮件"""
from django.contrib.auth.models import User
user = User.objects.get(id=user_id)
print(f"异步向 {user.email} 发送邮件")
@receiver(post_save, sender=User)
def trigger_async_email(sender, instance, created, **kwargs):
"""只触发异步任务,不阻塞"""
if created:
async_send_welcome_email.delay(instance.id)
⚠️ Pitfall 3: Avoid signal loop calls
if inpost_saveCalled again in the receiverinstance.save(), will trigger an infinite loop! There are two solutions:
- Add processing flag
@receiver(post_save, sender=Article)
def safe_article_update(sender, instance, created, **kwargs):
if getattr(instance, '_in_signal', False):
return
instance._in_signal = True
try:
# 安全的更新逻辑
if not instance.slug:
instance.slug = f"article-{instance.id}"
instance.save()
finally:
instance._in_signal = False
- Use
update_fieldsorQuerySet.update()
@receiver(post_save, sender=Article)
def safe_article_update(sender, instance, created, **kwargs):
if not instance.slug and created:
# QuerySet.update() 不会触发 post_save
Article.objects.filter(id=instance.id).update(slug=f"article-{instance.id}")
⚠️ Pitfall 4: Temporarily disconnect the signal during large data operations
bulk_createNo single signal will be triggered, but if you must operate row by row, remember to temporarily disconnect the signal to improve performance:
from contextlib import contextmanager
from django.db.models.signals import post_save
@contextmanager
def temp_disconnect_signal(signal, receiver, sender):
"""临时断开信号的上下文管理器"""
signal.disconnect(receiver, sender=sender)
try:
yield
finally:
signal.connect(receiver, sender=sender)
# 使用示例
with temp_disconnect_signal(post_save, send_welcome_email, User):
# 批量导入1000个测试用户,不会触发欢迎邮件
for i in range(1000):
User.objects.create_user(f"test{i}", f"test{i}@example.com", "123")
Summary of this chapter
Django signals are a powerful tool for decoupling application components, but they must be used with caution:
- ✅ Suitable for scenarios: data synchronization, cache invalidation, audit logs, notification push
- ❌ Not suitable for scenarios: core business logic (signals are "optional notifications", not "required dependencies")
- 🚀 Optimization suggestions: use asynchronous tasks, avoid loops, and disconnect signals during large-scale operations
🔗 Recommended related tutorials
🏷️ tag cloud:django信号 事件驱动 观察者模式 解耦 Celery 性能优化