管理器

class Manager

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

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

管理器名称

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

  1. from django.db import models
  2. class Person(models.Model):
  3. #...
  4. people = models.Manager()

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

自定义管理器

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

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

添加额外的管理器方法

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

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

例如,这个自定义 Manager 提供了一个方法 with_counts(),它会返回包含所有 OpinionPoll 对象的列表。每个对象都有一个额外属性 num_response,这是一次聚合查询的结果:

  1. from django.db import models
  2. class PollManager(models.Manager):
  3. def with_counts(self):
  4. from django.db import connection
  5. with connection.cursor() as cursor:
  6. cursor.execute("""
  7. SELECT p.id, p.question, p.poll_date, COUNT(*)
  8. FROM polls_opinionpoll p, polls_response r
  9. WHERE p.id = r.poll_id
  10. GROUP BY p.id, p.question, p.poll_date
  11. ORDER BY p.poll_date DESC""")
  12. result_list = []
  13. for row in cursor.fetchall():
  14. p = self.model(id=row[0], question=row[1], poll_date=row[2])
  15. p.num_responses = row[3]
  16. result_list.append(p)
  17. return result_list
  18. class OpinionPoll(models.Model):
  19. question = models.CharField(max_length=200)
  20. poll_date = models.DateField()
  21. objects = PollManager()
  22. class Response(models.Model):
  23. poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
  24. person_name = models.CharField(max_length=50)
  25. response = models.TextField()

通过本例,你可以利用 OpinionPoll.objects.with_counts() 返回一个包含 OpinionPoll 对象的列表,每个对象都有 num_responses 属性。

本例的另一个注意事项是 Manager 方法能通过 self.model 获取所依附的模型类。

修改管理器的初始 QuerySet

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

  1. from django.db import models
  2. class Book(models.Model):
  3. title = models.CharField(max_length=100)
  4. author = models.CharField(max_length=50)

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

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

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

  1. # First, define the Manager subclass.
  2. class DahlBookManager(models.Manager):
  3. def get_queryset(self):
  4. return super().get_queryset().filter(author='Roald Dahl')
  5. # Then hook it into the Book model explicitly.
  6. class Book(models.Model):
  7. title = models.CharField(max_length=100)
  8. author = models.CharField(max_length=50)
  9. objects = models.Manager() # The default manager.
  10. dahl_objects = DahlBookManager() # The Dahl-specific manager.

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

Because get_queryset() returns a QuerySet object, you can use filter(), exclude() and all the other QuerySet methods on it. So these statements are all legal:

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

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

例如:

  1. class AuthorManager(models.Manager):
  2. def get_queryset(self):
  3. return super().get_queryset().filter(role='A')
  4. class EditorManager(models.Manager):
  5. def get_queryset(self):
  6. return super().get_queryset().filter(role='E')
  7. class Person(models.Model):
  8. first_name = models.CharField(max_length=50)
  9. last_name = models.CharField(max_length=50)
  10. role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
  11. people = models.Manager()
  12. authors = AuthorManager()
  13. 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 要能访问待获取模型的全部对象,这样就能检索出其指向的 任何东西

Therefore, you should not override get_queryset() to filter out any rows. If you do so, Django will return incomplete results.

管理器调用自定义 QuerySet 方法

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

  1. class PersonQuerySet(models.QuerySet):
  2. def authors(self):
  3. return self.filter(role='A')
  4. def editors(self):
  5. return self.filter(role='E')
  6. class PersonManager(models.Manager):
  7. def get_queryset(self):
  8. return PersonQuerySet(self.model, using=self._db)
  9. def authors(self):
  10. return self.get_queryset().authors()
  11. def editors(self):
  12. return self.get_queryset().editors()
  13. class Person(models.Model):
  14. first_name = models.CharField(max_length=50)
  15. last_name = models.CharField(max_length=50)
  16. role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
  17. people = PersonManager()

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

创建带有 QuerySet 方法的管理器

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

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

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

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

方法拷贝规则如下:

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

例如:

  1. class CustomQuerySet(models.QuerySet):
  2. # Available on both Manager and QuerySet.
  3. def public_method(self):
  4. return
  5. # Available only on QuerySet.
  6. def _private_method(self):
  7. return
  8. # Available only on QuerySet.
  9. def opted_out_public_method(self):
  10. return
  11. opted_out_public_method.queryset_only = True
  12. # Available on both Manager and QuerySet.
  13. def _opted_in_private_method(self):
  14. return
  15. _opted_in_private_method.queryset_only = False

from_queryset()

classmethod from_queryset(queryset_class)

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

  1. class CustomManager(models.Manager):
  2. def manager_only_method(self):
  3. return
  4. class CustomQuerySet(models.QuerySet):
  5. def manager_and_queryset_method(self):
  6. return
  7. class MyModel(models.Model):
  8. objects = CustomManager.from_queryset(CustomQuerySet)()

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

  1. MyManager = CustomManager.from_queryset(CustomQuerySet)
  2. class MyModel(models.Model):
  3. objects = MyManager()

自定义管理器和模型继承

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

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

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

  1. class AbstractBase(models.Model):
  2. # ...
  3. objects = CustomManager()
  4. class Meta:
  5. abstract = True

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

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

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

  1. class ChildB(AbstractBase):
  2. # ...
  3. # An explicit default manager.
  4. default_manager = OtherManager()

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

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

  1. class ExtraManager(models.Model):
  2. extra_manager = OtherManager()
  3. class Meta:
  4. abstract = True
  5. class ChildC(AbstractBase, ExtraManager):
  6. # ...
  7. # Default manager is CustomManager, but OtherManager is
  8. # also available via the "extra_manager" attribute.
  9. pass

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

  1. ClassA.objects.do_something()

是合法的,但:

  1. AbstractBase.objects.do_something()

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

执行关系

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

  1. >>> import copy
  2. >>> manager = MyManager()
  3. >>> my_copy = copy.copy(manager)

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

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