bigywater.tech
Django · 37

Model Manager 与自定义 QuerySet

软删除最常见的写法是覆盖 Manager 的 get_queryset。它挡得住多少?为什么 book.author 还是能拿到「已删除」的作者?这篇用一个带软删除的小模型逐项验证。

as_manager()
自定义 QuerySet 的方法可以出现在 Manager 上,也可以链在 filter 后面
第一个定义的
模型的默认 Manager 是第一个定义的 Manager
关系不走 objects
book.author 与 JOIN 都不受自定义 Manager 的过滤(已软删除的对象仍然能取到)

自定义 QuerySet

实测 · 本机执行输出
Book.objects.cheap()             → ['b1', 'gb']
Book.objects.filter(...).cheap() → ['b1', 'gb']
Book.objects.by("ann").cheap()   → ['b1']
类型: ManagerFromBookQuerySet / BookQuerySet

把可复用的过滤写成 QuerySet 的方法,再 as_manager(),既能 Book.objects.cheap(),也能 Book.objects.filter(...).cheap()。直接写 Manager 方法则没法链在 filter 后面。

软删除:覆盖 get_queryset

实测 · 本机执行输出
Author.objects.all()      → ['ann']
Author.all_objects.all()  → ['ann', 'ghost']
Author._default_manager 是 ActiveManager (模型里第一个定义的 Manager)
Author._base_manager 是 Manager
Author.objects.get(name='ghost') → DoesNotExist

把「排除已删除」放进默认 Manager,业务代码里 Author.objects 就天然看不到它们。同时保留一个不过滤的 Manager 供管理用。

过滤挡不住的地方

实测 · 本机执行输出
gb.author → ghost  deleted = True (关联对象访问使用 _base_manager,不受 objects 过滤)
Book.objects.filter(author__name="ghost") → ['gb'] (JOIN 也不受影响)
ann.books.all() → ['b1', 'b2']  related manager 的类: RelatedManager

外键的正向访问(book.author)使用 _base_manager,不受你自定义的默认 Manager 影响——这是有意的(否则外键会「悬空」)。JOIN、filter(author__…) 也一样。如果业务要求「已删除的作者不应出现」,要在这些地方显式处理。

顺序

实测 · 本机执行输出
Author._meta.managers 顺序: ['objects', 'all_objects']
Book._meta.managers 顺序  : ['objects']

第一个定义的 Manager 是默认 Manager(_default_manager),admin、序列化等许多内部功能用它。把「过滤后的」放第一位要想清楚这些内部用途(本文没有验证 admin 的行为)。

分步演示:Manager 与 QuerySet

Manager 与软删除
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite
    • 没有验证:Meta.default_manager_name / base_manager_name 的效果、admin 与序列化对默认 Manager 的使用、反向关系管理器(author.books)是否继承自定义 Manager、迁移里的 managers 选项(use_in_migrations)
    京ICP备2026067502号-1京公网安备11010502063487号