管理器¶
一个 Manager
是 Django 模型提供数据库查询操作的接口。每个 Django 应用程序中的每个模型至少存在一个 Manager
。
Manager
类的工作方式在 执行查询 中有详细说明;本文档特别介绍了自定义 Manager
行为的模型选项。
管理器名称¶
默认情况下,Django 会为每个 Django 模型类添加一个名为 objects
的 Manager
。但是,如果您想将 objects
用作字段名,或者如果您想为 Manager
使用除 objects
之外的名称,则可以按模型为单位重命名它。要重命名给定类的 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()
实例附加到模型。这是一种非重复的方式来定义模型的常用“过滤器”。
例如
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()
来过滤掉任何行。如果您这样做,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()
将引发异常。这是因为管理器旨在封装管理对象集合的逻辑。由于您不能拥有抽象对象的集合,因此管理它们没有意义。如果您具有适用于抽象模型的功能,则应将该功能放在抽象模型上的staticmethod
或classmethod
中。
实施注意事项¶
无论您向自定义Manager
添加什么功能,都必须能够对Manager
实例进行浅拷贝;即,以下代码必须工作
>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)
Django 在某些查询期间会对管理器对象进行浅拷贝;如果您的管理器无法被复制,则这些查询将失败。
对于大多数自定义管理器来说,这不会成为问题。如果您只是向您的Manager
添加简单的方法,则您不太可能无意中使您的Manager
实例不可复制。但是,如果您正在重写__getattr__
或Manager
对象的某些其他私有方法来控制对象状态,则应确保您不会影响您的Manager
的可复制性。