直接重写QuerySet比只改Manager更可靠,因为filter()、get()、all()等方法底层均调用QuerySet,Manager层过滤无法覆盖Model.objects.all().filter(...)等链式调用,必须让自定义QuerySet成为默认行为。

为什么直接重写 QuerySet 比只改 Manager 更可靠
因为 Django 的 filter()、get()、all() 等方法底层都走 QuerySet,只在 Manager 层加 filter(is_deleted=False) 无法覆盖 Model.objects.all().filter(...) 这类链式调用——后者会绕过 Manager 的 get_queryset(),直接用原始 QuerySet。所以必须让自定义的 QuerySet 成为默认行为。
实操建议:
- 定义一个继承
QuerySet的类,重写__init__或直接在as_manager()绑定前统一过滤is_deleted=False - 在
Manager中调用as_manager()时传入该QuerySet子类 - 保留一个
with_deleted方法(返回未过滤的QuerySet),方便后台或恢复操作
SoftDeleteQuerySet 必须处理 delete() 和 bulk_delete()
逻辑删除的核心不是“查不到”,而是“删不动真数据”。Django 默认的 QuerySet.delete() 是物理删除,必须拦截并转为字段更新。
常见错误现象:MyModel.objects.filter(id=1).delete() 仍会执行 SQL DELETE FROM ...,导致数据丢失。
立即学习“Python免费学习笔记(深入)”;
实操建议:
- 在自定义
QuerySet中重写delete():改为update(is_deleted=True),并返回伪造的django.db.models.DeletionSummary兼容接口 - 对
bulk_delete()同理,但注意它不走delete(),需单独重写(Django 4.2+ 支持bulk_update,可用它模拟) - 如果模型有外键级联(
on_delete=models.CASCADE),逻辑删除后关联对象仍可能被物理删掉——必须把外键的on_delete改成SET_NULL或PROTECT,并确保字段允许为空
is_deleted 字段类型和索引怎么选才不影响查询性能
用 BooleanField 最直观,但要注意数据库层面的存储和索引效率。PostgreSQL 对 boolean 索引支持好,MySQL 的 TINYINT(1) 实际是整数,且默认不走索引(除非显式加索引)。
使用场景:高频列表页(如订单列表)几乎每次查询都带 is_deleted=False,没索引会导致全表扫描。
实操建议:
- 字段声明用
is_deleted = models.BooleanField(default=False, db_index=True),显式加索引 - 避免用
NullBooleanField(已弃用),也不要用CharField存 "yes"/"no" —— 类型不一致会让 ORM 推导出错,且无法利用布尔索引优化 - 如果业务需要区分“已删除”和“从未激活”,可扩展为
status = models.CharField(choices=[('active', 'Active'), ('deleted', 'Deleted'), ('draft', 'Draft')]),但必须同步调整QuerySet的默认过滤条件
Admin 后台怎么自动屏蔽已删除记录又保留恢复入口
Django Admin 默认用 ModelAdmin.get_queryset() 获取数据,不改它就会把已删除记录也列出来,既干扰运营,又暴露逻辑删除细节。
容易踩的坑:get_queryset() 里直接 return super().get_queryset().filter(is_deleted=False) 会导致“恢复”按钮失效——因为恢复操作需要先查到那条记录,而它已被过滤掉了。
实操建议:
- 在
ModelAdmin中根据请求路径判断是否进入列表页:if 'changelist' in request.resolver_match.view_name:,再加过滤 - 给 Admin 添加一个自定义 action,比如
restore_selected,专门用于批量恢复,并在 action 内部用queryset.with_deleted()获取原始数据 - 在 list_display 中加一列显示
is_deleted状态(仅限超级用户),避免误操作;用has_delete_permission控制普通用户看不到删除按钮


















