管理器¶
Manager 是一种接口,它赋予了 Django 模型操作数据库的能力。Django 应用中每个模型拥有至少一个 Manager。
Manager 类的文档介绍位于 执行查询;本页着重介绍自定义 Manager 行为的模型选项。
管理器名称¶
默认情况下,Django 为每个模型类添加了一个名为 objects 的 Manager。不过,若你想将 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 的基础 QuerySet。 get_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 方法的管理器¶
要替换前面的要求复制 QuerySet 和 Manager 方法的方案, 可以用 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 如何处理自定义管理器和 模型继承:
基类的管理器总是被子类以 Python 的普通名称解析顺序继承(子类上的属性会覆盖所有父类上的同名属性;直接父类会覆盖更上一级的,以此类推)。
如果没有在模型或其父类申明管理器,Django 会自动创建
objects管理器。一个类的默认管理器要么由
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 被复制。