管理器
class Manager
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
的方法。)
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
的基础 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 访问关联对象(即 choice.question
)时使用 Model._base_manager
管理器类的实例,而不是关联对象的 _default_manager
。这是因为 Django 要检索那些可能被默认管理器筛选掉(所以无法访问)的关联对象。
若基本管理器类 (django.db.models.Manager) 无法满足需求,你可以通过设置 Meta.base_manager_name 告诉 Django 使用哪个类。
在关联模型上执行查询时不会使用基础管理器,或者当访问一对多或多对多关系( accessing a one-to-many or many-to-many relationship )。例如,若 来自教程 的模型 Question
有个 deleted
字段,还有一个基础管理器,用于过滤掉 deleted=True
的实例。由 Choice.objects.filter(question__name__startswith='What')
返回的查询结果集会包含关联至已删除的问题的选项。
不要在这类管理器子类中过滤掉任何结果
该管理器用于访问由其它模型关联过来的对象。这些情况下,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
被复制。