软删除应通过标记字段(如is_deleted)实现,而非重写delete()方法,因其无法拦截queryset批量删除;推荐使用django-model-utils的softdeletablemodel,或自定义manager与queryset配对实现,并需统一处理关联、信号及admin等各访问路径。

软删除不是靠删数据,而是靠加标记——Django 里最稳妥的做法是用 django-model-utils 的 SoftDeletableModel,或者自己加 is_deleted 字段 + 自定义 Manager。
为什么不能直接重写 delete() 方法?
Django 的 delete() 是实例方法,但 ORM 查询(比如 MyModel.objects.filter(...).delete())会绕过它,直接执行 SQL DELETE,导致软删除失效。
- 重写
delete()只对单个对象生效:obj.delete()可以拦截,但MyModel.objects.filter(id=1).delete()不会调用它 - QuerySet 的
delete()默认走数据库级删除,不触发模型层逻辑 - 除非你同时重写 QuerySet 和 Manager,否则无法覆盖所有删除入口
用 SoftDeletableModel 最省事
django-model-utils 提供了开箱即用的软删除支持,核心是自动替换默认 Manager,并提供 all_objects 让你能查到已“删”的记录。
- 安装:
pip install django-model-utils - 模型继承:
from model_utils.models import SoftDeletableModel,然后class MyModel(SoftDeletableModel): - 默认查询(
MyModel.objects.all())自动过滤掉is_removed=True的记录 - 查全部(含软删):用
MyModel.all_objects.all() - 字段名固定为
is_removed,类型是BooleanField,不可改
手动实现时,Manager 和 QuerySet 必须配对写
如果不想引入第三方包,就得自己控制查询入口。关键点不是只改模型,而是让所有查询都走自定义 Manager。
- 定义字段:
is_deleted = models.BooleanField(default=False) - 写自定义 QuerySet:
class SoftDeleteQuerySet(models.QuerySet): def delete(self): return self.update(is_deleted=True) - 写 Manager:
class SoftDeleteManager(models.Manager): def get_queryset(self): return SoftDeleteQuerySet(self.model, using=self._db).filter(is_deleted=False) - 在模型中声明:
objects = SoftDeleteManager(),再额外加一个all_objects = models.Manager()用于查全部 - 必须重写 QuerySet 的
delete(),否则批量操作仍会物理删除
关联关系和信号容易漏处理
软删除后,外键约束、on_delete 行为、post_delete 信号都不会自动适配,得手动干预。
-
ForeignKey的on_delete=models.CASCADE在软删除下无效——子表不会自动软删,需在父模型的delete()中显式处理 -
post_delete信号只在物理删除时触发,软删除要自己发信号,比如soft_delete - 带
related_name的反向查询(如user.posts.all())会受 Manager 影响,但user.posts.all_objects.all()才能看到软删内容 - Admin 后台默认不显示软删记录,需重写
get_queryset()或加筛选开关
真正麻烦的不是加个字段,而是让所有数据访问路径——QuerySet、Manager、Admin、序列化、信号、关联逻辑——都一致地尊重这个“删除”状态。漏掉一环,就可能查到不该见的数据,或误删真实记录。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











