管理器是Django 的模型进行数据库查询操作的接口。Django 应用的每个模型都拥有至少一个管理器。
管理器 类的工作方式在执行查询文档中阐述;而这篇文档针对自定义管理器行为时需要用到的模型选项。
默认情况下,Django 为每个模型类添加一个名为objects的管理器。然而,如果你想将objects用于字段名称,或者你想使用其它名称而不是objects访问管理器,你可以在每个模型类中重命名它们。在模型中定义一个值为models.Manager()的属性,来重命名管理器。例如:
from django.db import models
class Person(models.Model):
#...
people = models.Manager()
使用例子中的模型, Person.objects会抛出AttributeError异常,而Person.people.all()会返回一个包含所有Person对象的列表。
你可以在模型中使用自定义的管理器,方法是继承Manager 基类并实例化你的自定义管理器。
你有两个原因可能会自己定义管理器:向管理器类中添加额外的方法,或者修改管理器返回的原始查询集。
添加额外管理器方法是为你的类增加“表级”功能的首选方式。 (如果要添加行级功能 —— 比如只对某个模型的实例起作用 ——应 使用模型方法 ,而不是管理器方法。)
自定义的管理器 方法可以返回你想要的任何数据,而不需要返回一个查询集。
例如,下面这个自定义管理器提供一个with_counts() 方法,它返回所有OpinionPoll 对象的列表,列表的每项都有一额外num_responses 属性,该属性保存一个聚合查询的结果(注:对应的应是SQL查询语句中的COUNT(*)生成的项):
from django.db import models
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
SELECT p.id, p.question, p.poll_date, COUNT(*)
FROM polls_opinionpoll p, polls_response r
WHERE p.id = r.poll_id
GROUP BY p.id, p.question, p.poll_date
ORDER BY p.poll_date DESC""")
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], question=row[1], poll_date=row[2])
p.num_responses = row[3]
result_list.append(p)
return result_list
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
poll_date = models.DateField()
objects = PollManager()
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll)
person_name = models.CharField(max_length=50)
response = models.TextField()
在该例中,你可以使用OpinionPoll.objects.with_counts() 返回带有num_responses 属性的OpinionPoll 对象列表。
该例中需要注意的一点是:管理器方法可以通过self.model 来得到它所属的模型类。
管理器自带的 查询集返回系统中所有的对象。例如,使用下面这个模型:
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
... Book.objects.all() 语句将返回数据库中所有的 Book 对象。
你可以通过重写Manager.get_queryset() 方法来覆盖管理器自带的Queryset。get_queryset() 会根据你所需要的属性返回查询集。
例如,下面的模型有两个管理器,一个返回所有的对象,另一个则只返回作者是Roald Dahl 的对象:
# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super(DahlBookManager, self).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(AuthorManager, self).get_queryset().filter(role='A')
class EditorManager(models.Manager):
def get_queryset(self):
return super(EditorManager, self).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(),都会得到预期的结果。
虽然大多数标准查询集的方法可以从管理器中直接访问到,但是如果你需要将一些被定义到一个自定义查询集中的额外方法也在管理器上实现,下面所展示的这个例子是唯一可行办法:
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.as_manager() 可以用来创建一个带有自定义查询集方法副本的管理器实例:
class Person(models.Model):
...
people = PersonQuerySet.as_manager()
通过QuerySet.as_manager()创建的管理器 实例,实际上等价于上面例子中的PersonManager。
并不是每个查询集的方法都在管理器层面上有意义。比如 QuerySet.delete(),我们有意防止它复制到管理器 中。
方法按照以下规则进行复制:
例如:
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
在进一步的使用中,你可能想创建一个自定义管理器和一个自定义查询集。你可以调用Manager.from_queryset(),它会返回管理器的一个子类,带有自定义查询集所有方法的副本:
class BaseManager(models.Manager):
def manager_only_method(self):
return
class CustomQuerySet(models.QuerySet):
def manager_and_queryset_method(self):
return
class MyModel(models.Model):
objects = BaseManager.from_queryset(CustomQueryset)()
你也可以在一个变量中储存生成的类:
CustomManager = BaseManager.from_queryset(CustomQueryset)
class MyModel(models.Model):
objects = CustomManager()
类继承和模型管理器两者之间配合得并不是很好。 管理器一般只对其定义所在的类起作用,在子类中对其继承绝对不是一个好主意。 而且,因为第一个管理器为默认的管理器,所以对默认的管理器 进行控制是非常必要的。下面是Django 如何处理自定义管理器和模型继承:
如果你想在一组模型上安装一系列自定义管理器,上面提到的这些规则就已经为你的实现提供了必要的灵活性。你可以继承一个抽象基类,但仍要自定义默认的管理器。例如,假设你的基类是这样的:
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()
会抛出一个异常。这是因为,管理器被设计用来封装对象集合管理的逻辑。由于抽象的对象中并没有一个集合,管理它们是毫无意义的。如果你写了应用在抽象模型上的功能,你应该把功能放到抽象模型的静态方法,或者类的方法中。
无论你向自定义的管理器添加了什么功能,都必须可以得到管理器实例的一个浅表副本;例如,下面的代码必须正常运行:
>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)
Django 在一些查询中会创建管理器的浅表副本;如果你的管理器不能被复制,查询就会失败。
这对于大多数自定义管理器不是什么大问题。如果你只是添加一些简单的方法到你的管理器中,不太可能会把你的管理器实例变为不可复制的。但是,如果你覆盖了__getattr__,或者其它管理器中控制对象状态的私有方法,你应该确保不会影响到管理器的复制。
这篇文档已经提到了Django创建管理器类的一些位置:默认管理器和用于访问关联对象的“朴素” 管理器。在 Django 的实现中也有很多地方用到了临时的朴素管理器。 正常情况下,django.db.models.Manager 类的实例会自动创建管理器。
在整个这一节中,我们将那种由 Django 为你创建的管理器称之为 "自动管理器",既有因为没有管理器而被 Django 自动添加的默认管理器, 也包括在访问关联模型时使用的临时管理器。
有时,默认管理器也并非是自动管理器。 一个例子就是 Django 自带的django.contrib.gis 应用,所有 gis模型都必须使用一个特殊的管理器类(GeoManager),因为它们需要运行特殊的查询集(GeoQuerySet)与数据库进行交互。这表明无论自动管理器是否被创建,那些要使用特殊的管理器的模型仍要使用这个特殊的管理器类。
Django 为自定义管理器的开发者提供了一种方式:无论开发的管理器类是不是默认的管理器,它都应该可以用做自动管理器。 可以通过在管理器类中设置 use_for_related_fields属性来做到这点:
class MyManager(models.Manager):
use_for_related_fields = True
# ...
如果在模型中的默认 管理器(在这些情况中仅考虑默认管理器)中设置了这个属性,无论它何时需要为这个类自动地创建管理器,Django 都会自动使用它。否则 Django 就会使用 django.db.models.Manager.
历史回顾
从它使用目的来看,这个属性的名称(use_for_related_fields)好象有点古怪。原本,这个属性仅仅是用来控制访问关联字段的管理器的类型,这就是它名字的由来。 后来它的作用更加拓宽了,但是名称一直未变。 因为要保证现在的代码在 Django 以后的版本中仍可以正常工作(continue to work),这就是它名称不变的原因。
在上面的django.contrib.gis 已经提到了, use_for_related_fields这个特性是在需要返回一个自定义查询集子类的管理器中使用的。要在你的管理器中提供这个功能,要注意以下几点。
一个原因是自动管理器是用来访问关联模型 的对象。 在这种情况下,Django 必须要能看到相关模型的所有对象,所以才能根据关联关系得到任何数据 。
如果你重写了 get_queryset() 方法并且过滤掉了一些行数据,Django 将返回不正确的结果。不要这么做! 在 get_queryset()方法中过滤掉数据,会使得它所在的管理器不适于用做自动管理器。
2015年5月13日