管理器

class Manager

Manager 是一种接口,它赋予了 Django 模型操作数据库的能力。Django 应用中每个模型拥有至少一个 Manager

Manager 类的文档介绍位于 执行查询;本页着重介绍自定义 Manager 行为的模型选项。

管理器名称

默认情况下,Django 为每个模型类添加了一个名为 objectsManager。不过,若你想将 objects 用作字段名,或想使用 objects 以外的 Manager 名字,就要在模型基类中重命名。要为指定类重命名 Manager,在该模型中定义一个类型为 models.Manager 的属性。例如:

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

使用这个实例模型时, Person.objects 会产生一个 AttributeError 异常,而 Person.people.all() 会返回包含所有 Person 对象的列表。

自定义管理器

继承基类 Manager,在模型中实例化自定义 Manager,你就可以在该模型中使用自定义的 Manager

有两种原因可能使你想要自定义 Manager:添加额外的 Manager 方法,修改 Manager 返回的原始 QuerySet

添加额外的管理器方法

添加额外的 Manager 方法一般是为模型添加 “表级” 功能的更好方法。(对于 “行级” 功能 —— 即,只操作单个模型对象 —— 通过 模型方法,而不是自定义 Manager 的方法。)

For example, this custom Manager adds a method with_counts():

from django.db import models
from django.db.models.functions import Coalesce

class PollManager(models.Manager):
    def with_counts(self):
        return self.annotate(
            num_responses=Coalesce(models.Count("response"), 0)
        )

class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    objects = PollManager()

class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    # ...

With this example, you'd use OpinionPoll.objects.with_counts() to get a QuerySet of OpinionPoll objects with the extra num_responses attribute attached.

自定义 Manager 方法能返回任何东西,没有强制它必须返回一个 QuerySet

Another thing to note is that Manager methods can access self.model to get the model class to which they're attached.

修改管理器的初始 QuerySet

Manager 的基础 QuerySet 会返回系统中所有的对象。例如,使用以下模型:

from django.db import models

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

……语句 Book.objects.all() 会返回数据库中所有的书。

你可以通过重写 Manager.get_queryset() 方法来覆盖 Manager 的基础 QuerySetget_queryset() 返回的 QuerySet 应该包含你需要的属性。

例如,以下模型有 两个 Manager —— 一个返回所有对象,另一个仅返回 Roald Dahl 写的书:

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(author='Roald Dahl')

# Then hook it into the Book model explicitly.
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # The default manager.
    dahl_objects = DahlBookManager() # The Dahl-specific manager.

使用这个实例模型时, Book.objects.all() 会返回数据库中所有的书,而 Book.dahl_objects.all() 仅返回 Roald Dahl 写的书。

因为 get_queryset() 返回一个 QuerySet 对象,你可以在上面调用 filter()exclude() 和其它的 QuerySet 方法。所以,以下语句是等效的:

Book.dahl_objects.all()
Book.dahl_objects.filter(title='Matilda')
Book.dahl_objects.count()

本例同时介绍了另一个有趣的技巧:在一个模型中使用多个管理器。你可以为一个模型添加任意多个 Manager()。为模型定义通用 "filters" 的非重复方式。

例如:

class AuthorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role='A')

class EditorManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(role='E')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
    people = models.Manager()
    authors = AuthorManager()
    editors = EditorManager()

本例允许你调用 Person.authors.all()Person.editors.all()Person.people.all(),返回符合期望的结果。

默认管理器

Model._default_manager

若你使用自定义 Manager 对象,注意 Django 遇到的第一个 Manager (按照你在模型中定义的顺序)会拥有一个独特的状态。Django 将类定义中的第一个 Manager 视作 “默认” Manager,Django 的几个组件(包括 dumpdata)在用到该模型时会独立地调用该 Manager。故此,选择默认管理器时要万分小心,避免遇到重写的 get_queryset() 无法获取期望的结果这种情况。

你可以通过 Meta.default_manager_name 指定一个自定义的默认管理器。

若你正在编写的代码必须处理未知模型,例如,在实现了通用视图的第三方应用中使用这个管理器(或 _base_manager),而不是假定该模型有一个名为 objects 的管理器。

基础管理器

Model._base_manager

不要在这类管理器子类中过滤掉任何结果

该管理器用于访问由其它模型关联过来的对象。这些情况下,Django 要能访问待获取模型的全部对象,这样就能检索出其指向的 任何东西

因此,你不应该覆盖 get_queryset() 来过滤任何rows。如果你这么做,Django 会返回不完整的结果。

管理器调用自定义 QuerySet 方法

因为大部分的标准 QuerySet 方法能直接从 Manager 访问,这个实例仅适用于你在自定义 QuerySet 中定义了额外方法,且在 Manager 中实现了它们:

class PersonQuerySet(models.QuerySet):
    def authors(self):
        return self.filter(role='A')

    def editors(self):
        return self.filter(role='E')

class PersonManager(models.Manager):
    def get_queryset(self):
        return PersonQuerySet(self.model, using=self._db)

    def authors(self):
        return self.get_queryset().authors()

    def editors(self):
        return self.get_queryset().editors()

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
    people = PersonManager()

本例允许你从管理器 Person.people 直接调用 authors()editors()

创建带有 QuerySet 方法的管理器

要替换前面的要求复制 QuerySetManager 方法的方案, 可以用 QuerySet.as_manager() 创建一个 Manager 实例,拷贝了自定义 QuerySet 的方法:

class Person(models.Model):
    ...
    people = PersonQuerySet.as_manager()

QuerySet.as_manager() 创建的 Manager 实例实质上等价于前面例子中的 PersonManager

不是每个 QuerySet 方法在 Manager 层都是有意义的;例如,我们故意阻止 QuerySet.delete() 被拷贝进 Manager 类中。

方法拷贝规则如下:

  • 公开方法默认会被拷贝。
  • 私有方法(以下划线打头)默认不会被复制。
  • queryset_only 属性值为 False 的方法总是会被复制。
  • queryset_only 属性值为 True 的方法永远不会被复制。

例如:

class CustomQuerySet(models.QuerySet):
    # Available on both Manager and QuerySet.
    def public_method(self):
        return

    # Available only on QuerySet.
    def _private_method(self):
        return

    # Available only on QuerySet.
    def opted_out_public_method(self):
        return
    opted_out_public_method.queryset_only = True

    # Available on both Manager and QuerySet.
    def _opted_in_private_method(self):
        return
    _opted_in_private_method.queryset_only = False

from_queryset()

classmethod from_queryset(queryset_class)

对于进阶用法,你可能同时要一个自定义 Manager 和一个自定义 QuerySet。你可以通过调用 Manager.from_queryset() 达成目的,这将会返回一个自定义基础 Manager 的子类,带有一份自定义 QuerySet 方法的拷贝:

class CustomManager(models.Manager):
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return

class MyModel(models.Model):
    objects = CustomManager.from_queryset(CustomQuerySet)()

还可以将生成的类存储到变量中:

MyManager = CustomManager.from_queryset(CustomQuerySet)

class MyModel(models.Model):
    objects = MyManager()

自定义管理器和模型继承

下面是 Django 如何处理自定义管理器和 模型继承

  1. 基类的管理器总是被子类以 Python 的普通名称解析顺序继承(子类上的属性会覆盖所有父类上的同名属性;直接父类会覆盖更上一级的,以此类推)。
  2. 如果没有在模型或其父类申明管理器,Django 会自动创建 objects 管理器。
  3. 一个类的默认管理器要么由 Meta.default_manager_name 指定,要么是模型中申明的第一个管理器,或者是直接父模型的默认管理器。

如果您想通过抽象基类在一组模型上安装自定义管理器,但仍能自定义默认管理器,这些规则提供了必要的灵活性。例如,假设有此基类:

class AbstractBase(models.Model):
    # ...
    objects = CustomManager()

    class Meta:
        abstract = True

如果您在子类中直接使用这一点,如果您在基类中没有声明任何管理器,那么 objects 将是默认的管理器:

class ChildA(AbstractBase):
    # ...
    # This class has CustomManager as the default manager.
    pass

如果您想继承 AbstractBase,但提供不同的默认管理器,则可以在子类上提供该默认管理器:

class ChildB(AbstractBase):
    # ...
    # An explicit default manager.
    default_manager = OtherManager()

这里的 default_manager 是默认的。 objects 管理器仍然可用,因为它是继承的,但没有被当做默认管理器。

最后,对于这个示例,假设您想要向子类中添加额外的管理器,但是仍然使用来自 AbstractBase 的默认管理器。您不能直接在子类中添加新的管理器,因为这将覆盖默认管理器,并且您还必须显式地申明来自抽象基类的所有管理器。解决方案是将这个管理器放到另一个基类中,并在默认管理器 之后 将其引入继承层次结构:

class ExtraManager(models.Model):
    extra_manager = OtherManager()

    class Meta:
        abstract = True

class ChildC(AbstractBase, ExtraManager):
    # ...
    # Default manager is CustomManager, but OtherManager is
    # also available via the "extra_manager" attribute.
    pass

请注意,虽然可以在抽象模型上 定义 自定义管理器,但不能使用抽象模型 调用 任何方法。即:

ClassA.objects.do_something()

是合法的,但:

AbstractBase.objects.do_something()

会引发一个异常。这是因为管理器意在封装管理映射对象集合的逻辑。因为您不能拥有抽象对象的集合,所以管理抽象对象是没有意义的。如果您有适用于抽象模型的功能,则应该将该功能放在抽象模型的 静态方法类方法 中。

执行关系

无论您在自定义的 Manager 中添加了什么特性,都必须能够对 Manager 实例进行简单的复制;也就是说,以下代码必须有效:

>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)

Django 在某些查询期间对管理器对象进行浅拷贝;如果您的管理器无法被复制,那么这些查询将失败。

对于大多数的资源管理器来说,这不是问题。若你只是为 Manager 添加简单的方法,一般不会疏忽地把 Manager 变的不可拷贝。但是,若重写了 Manager 对象用于控制对象状态的 __getattr__ 或其它私有方法,你需要确认你的修改不会影响 Manager 被复制。