Manager + QuerySet 是唯一可靠组合,因为 Django 链式调用全程基于 QuerySet 实例,仅重写模型 delete() 方法无法拦截批量删除,必须通过自定义 Manager 绑定重写了 delete() 和 bulk_delete() 的 QuerySet 子类,并确保 get_queryset() 返回该子类实例,才能覆盖所有删除入口。

delete() 方法根本拦不住批量删除,MyModel.objects.filter().delete() 会跳过它,直奔数据库执行物理 DELETE。真正能覆盖所有入口的,只有自定义 Manager 配合 QuerySet。
为什么 Manager + QuerySet 是唯一可靠组合
Django 的链式调用(比如 MyModel.objects.filter(x=1).exclude(y=2).delete())全程走的是 QuerySet 实例,不是 Manager。只改 Manager.get_queryset() 只能影响起点,后续 .filter() 等操作仍可能回到原始 QuerySet——除非你让自定义 QuerySet 成为默认行为。
-
Manager负责提供查询起点(如objects),并绑定自定义QuerySet类 -
QuerySet必须重写delete()和bulk_delete()(Django 4.2+ 可用bulk_update()模拟) -
get_queryset()返回的必须是你的子类QuerySet实例,否则链式调用中途就“脱钩”了
常见错误:只重写 delete() 方法
很多人在模型里加:
def delete(self, *args, **kwargs):
self.is_deleted = True
self.save()
这只能拦截 obj.delete() 单实例调用。但以下全部失效:
-
MyModel.objects.filter(id__in=[1,2,3]).delete()→ 物理删 -
MyModel.objects.all().delete()→ 物理删 - Admin 后台勾选多条后点“删除” → 物理删(Admin 默认调用 QuerySet.delete)
DRF 和 Admin 对软删除的不同需求
API 层(如 DRF)默认不应返回已软删数据,而 Admin 需要看到全部记录(含 is_deleted=True 的),并支持恢复或彻底清理。这意味着:
立即学习“Python免费学习笔记(深入)”;
-
objectsManager 必须过滤is_deleted=False - 必须额外暴露一个
all_objects = models.Manager()或类似with_deleted()方法供 Admin 使用 - DRF 的
DestroyModelMixin默认调用instance.delete(),需确保该调用最终落到自定义QuerySet.delete()上(靠正确配置 Manager 绑定)
外键级联是隐形雷区
即使软删逻辑写对了,如果关联模型用了 on_delete=models.CASCADE,物理删除仍会发生——因为 Django 的级联逻辑不查 is_deleted 字段,只看外键约束。
- 必须把所有相关外键的
on_delete改成models.SET_NULL或models.PROTECT - 对应字段得允许
null=True(SET_NULL前提) - 如果用
PROTECT,则需在软删前手动处理依赖,否则delete()会抛ProtectedError
QuerySet 行为。漏掉任意一环,数据就可能被真实擦除。


















