管理器

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 的方法。)

例如,这个自定义的 Manager 添加了一个方法 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)
    # ...

通过这个示例,你可以使用 OpinionPoll.objects.with_counts() 来获取带有额外的 num_responses 属性附加的 OpinionPoll 对象的 QuerySet

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

另一个需要注意的是,Manager 方法可以访问 self.model 来获取它们附加的模型类。

修改管理器的初始 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 被复制。

Back to Top