# dj_signals **Repository Path**: duans/dj_signals ## Basic Information - **Project Name**: dj_signals - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-18 - **Last Updated**: 2026-03-18 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Django-signals信号 Django 模型信号(Model Signals)是一种在模型生命周期特定时刻自动触发回调函数的机制,允许你在模型实例被保存、删除、初始化等操作前后执行自定义逻辑。这是实现“观察者模式”的一种方式,非常适合用于解耦业务逻辑。 ## 一、常用模型信号类型 Django 提供了以下几种常用的模型信号: | 信号名 | 触发时机 | | ---------------- | ---------------------------- | | `pre_init` | 模型实例初始化前 | | `post_init` | 模型实例初始化后 | | `pre_save` | 调用 `save()` 方法前 | | `post_save` | 调用 `save()` 方法后 | | `pre_delete` | 调用 `delete()` 方法前 | | `post_delete` | 调用 `delete()` 方法后 | | `m2m_changed` | 多对多关系变更时 | | `class_prepared` | 模型类准备完成时(较少使用) | ## 二、基本使用方法 ### 1. 导入信号和接收器装饰器 ```python from django.db.models.signals import post_save, pre_delete from django.dispatch import receiver from myapp.models import MyModel ``` ### 2. 定义信号接收函数(使用 `@receiver` 装饰器) ```python # 定义信号函数 # sender: 模型 # instance: 模型实例对象 # created: 布尔值, 如果为True表示新增操作 # kwargs: 其他参数 @receiver(post_save, sender=MyModel) def my_handler(sender, instance, created, **kwargs): if created: print(f"新对象创建:{instance}") else: print(f"对象更新:{instance}") ``` > 注意:`post_save` 会传递一个 `created` 参数,用于判断是新建还是更新。 ### 3. 注册信号(可选) 如果使用 `@receiver` 装饰器并放在正确的位置(如 `apps.py` 或已导入的模块中),通常无需手动注册。但也可以手动连接: ```python # 手动让信号含函数和模型建立关联 # post_save.connect(信号函数, sender=模型) post_save.connect(my_handler, sender=MyModel) ``` ## 三、最佳实践:将信号放在哪里? ### 推荐做法:在 `apps.py` 中显式导入信号 1. 在你的应用目录下创建 `signals.py` 文件: ```python # myapp/signals.py from django.db.models.signals import post_save from django.dispatch import receiver from .models import MyModel @receiver(post_save, sender=MyModel) def handle_my_model_save(sender, instance, created, **kwargs): if created: # 执行创建后的逻辑,例如发送邮件、记录日志等 pass ``` 1. 在 `myapp/apps.py` 中导入信号: ```python # myapp/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. 确保 `settings.py` 中使用的是这个 AppConfig: ```python # settings.py INSTALLED_APPS = [ ... 'myapp.apps.MyAppConfig', # 而不是 'myapp' ] ``` > ⚠️ 如果只写 `'myapp'`,Django 会自动使用默认的 AppConfig,可能不会调用 `ready()` 方法,导致信号未注册。 ## 四、各信号详解与示例 ### 1. `post_save` - 参数: - `sender`: 模型类 - `instance`: 模型实例 - `created`: 布尔值,是否为新建 - `raw`: 是否从 fixture 加载 - `using`: 使用的数据库别名 - `update_fields`: 更新的字段集合(如果是部分更新) ```python @receiver(post_save, sender=User) def create_profile(sender, instance, created, **kwargs): if created: Profile.objects.create(user=instance) ``` ### 2. `pre_delete` / `post_delete` ```python @receiver(pre_delete, sender=Document) def backup_document(sender, instance, **kwargs): # 删除前备份文件 shutil.copy(instance.file.path, "/backup/") ``` ### 3. `m2m_changed` 当多对多字段(如 `ManyToManyField`)发生变化时触发。 ```python @receiver(m2m_changed, sender=Group.members.through) def members_changed(sender, instance, action, reverse, model, pk_set, **kwargs): if action == "post_add": print(f"成员 {pk_set} 被添加到组 {instance}") elif action == "post_remove": print(f"成员 {pk_set} 被从组 {instance} 移除") ``` action 可能值:`"pre_add"`, `"post_add"`, `"pre_remove"`, `"post_remove"`, `"pre_clear"`, `"post_clear"` ## 五、应用场景 ### 场景一:自动创建关联数据(最经典场景) **场景描述**:当用户注册(创建 `User` 对象)时,系统需要自动为该用户创建一个对应的 `Profile`(个人资料)记录。 **为什么用信号**:`User` 模型通常来自 Django 内置的 `contrib.auth` 应用,你无法直接修改其源码或在项目中直接重写它的 `save()` 方法。信号让你可以在“外部”监听这个事件。 ### 场景二:数据变更审计日志(Audit Logging) **场景描述**:你需要记录关键数据(如订单金额、用户权限)的修改历史,包括“谁”在“什么时候”把“什么字段”从“旧值”改成了“新值”。 **为什么用信号**:审计逻辑属于“横切关注点”,不应该污染业务模型代码。使用 `pre_save` 信号可以对比数据库中的旧值和即将保存的新值。 ### 场景三:清理关联文件或资源 **场景描述**:当删除一个包含文件上传字段(如头像、附件)的模型实例时,需要同时删除服务器磁盘上对应的物理文件,防止产生孤儿文件占用空间。 **为什么用信号**:Django 默认不会在删除模型时自动删除文件。在模型的 `delete()` 方法中处理虽然可行,但如果多个地方调用删除逻辑,容易遗漏。使用 `pre_delete` 信号可以确保只要删除发生,清理逻辑必执行。 ### 场景四:发送通知或触发异步任务 **场景描述**:当文章发布、订单支付成功或评论被回复时,需要发送邮件、短信或将任务推送到消息队列(如 Celery)。 **为什么用信号**:业务逻辑(如“保存订单”)应该保持纯粹和快速。通知逻辑是副作用,应该分离出来。如果通知失败,不应影响主业务的保存操作。 ### 场景五:第三方应用集成或遗留系统兼容 **场景描述**:你正在使用一个第三方的 Django App(如 `django-shop` 或 CMS),需要在它们的数据保存时执行你自己的自定义逻辑,但你不能也不应该去修改第三方库的源代码。 **为什么用信号**:这是信号存在的最大意义之一。它提供了一种标准的“插件式”机制,让你能够介入第三方应用的生命周期。 ## 六、注意事项与常见陷阱 1. **事务问题**: - 信号在数据库事务内执行。如果 `save()` 在事务中失败回滚,信号中的操作也会回滚(前提是操作也在同一个事务中)。 - 若信号中执行了非数据库操作(如发邮件、调用 API),即使事务回滚,这些操作也无法撤销。 2. **避免循环调用**: - 在 `post_save` 中再次调用 `instance.save()` 可能导致无限递归。可使用 `update_fields` 或标志位避免。 3. **测试时信号可能不触发**: - 使用 `bulk_create()`、`bulk_update()`、`QuerySet.update()` 等批量操作 **不会触发** `pre_save`/`post_save`。 - `QuerySet.delete()` 也不会触发 `pre_delete`/`post_delete`。 4. **性能影响**: - 信号是同步执行的,复杂逻辑会影响请求响应时间。可考虑使用异步任务(如 Celery)。