管理器

class Manager[source]

Manager是Django的模型进行数据库查询操作的接口。 Django 应用的每个模型都拥有至少一个Manager

Manager类的工作方式的文档在执行查询这一节中;这页文档具体涉及用于自定义管理器行为的模型选项。

管理器的名字

默认情况下,Django 为每个模型类添加一个名为objectsManager 然而,如果你想将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方法是为你的类增加“表级”功能的首选方式。 (如果要添加行级功能 —— 比如只对某个模型的实例起作用 ——应 使用Model methods ,而不是Manager方法。)

自定义的Manager方法可以返回你想要的任何形式的数据。 它不必一定要返回一个QuerySet

例如,下面这个自定义Manager提供一个with_counts()方法,它返回所有OpinionPoll对象的列表,列表的每项都有一额外num_responses属性,该属性保存一个聚合查询的结果:

from django.db import models

class PollManager(models.Manager):
    def with_counts(self):
        from django.db import connection
        with connection.cursor() as 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, on_delete=models.CASCADE)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

在该例中,你可以使用OpinionPoll.objects.with_counts()返回带有num_responses属性的OpinionPoll对象列表。

该例中需要注意的一点是:Manager方法可以通过self.model来得到它所属的模型类。

修改管理器的初始QuerySet

管理器自带的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 的对象:

# 首先,定义管理器的子类。
class DahlBookManager(models.Manager):
    def get_queryset(self):
        return super(DahlBookManager, self).get_queryset().filter(author='Roald Dahl')

# 然后将它显式地放入Book模型。
class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

    objects = models.Manager() # 默认的管理器。
    dahl_objects = DahlBookManager() # 用于Dahl的管理器。

在这个简单的例子中,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(),都会得到预期的结果。

默认管理器

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将返回不正确的结果。 不要这么做! 过滤get_queryset()中的结果的管理员不适合用作基础管理器。

从管理器调用自定义的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方法创建一个管理器

上面的方法要求同一个方法在ManagerQuerySet上都创建,QuerySet.as_manager()可以用来创建一个带有自定义的QuerySet的方法副本的Manager实例:

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):
    # 在Manager和QuerySet上都可以访问。
    def public_method(self):
        return

    # 只能在QuerySet上访问。
    def _private_method(self):
        return

    # 只能在QuerySet上访问。
    def opted_out_public_method(self):
        return
    opted_out_public_method.queryset_only = True

    # 在Manager和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 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如何处理自定义管理器和模型继承的

  1. 基类的管理器始终由子类继承,使用Python的正常名称解析顺序(子类上的名称覆盖父类上其它相同的名称;然后在第一个父类上查找名称,等等)。
  2. 如果没有在模型和/或其父类上声明管理器,Django会自动创建objects管理器。
  3. 类上的默认管理器来自于Meta.default_manager_name、在模型上声明的第一个管理器或第一个父模型的默认管理器。
在Django 1.10中更改:

上面描述的某些继承行为只有你在模型的Meta类上设置manager_inheritance_from_future = True才会应用。 在旧版本中,如果你没有设置该属性,则管理器继承根据模型继承的类型而异(抽象基类多表继承代理模型),特别是关于选择默认管理器。

如果你想在一组模型上安装一系列自定义管理器,上面提到的这些规则就已经为你的实现提供了必要的灵活性。你可以继承一个抽象基类,但仍要自定义默认的管理器。 例如,假设你的基类是这样的:

class AbstractBase(models.Model):
    # ...
    objects = CustomManager()

    class Meta:
        abstract = True

如果你在子类中没有定义管理器,直接使用上面的代码,默认管理器就是从基类中继承的 objects

class ChildA(AbstractBase):
    # ...
    # 这个类以CustomManager作为默认的管理器。
    pass

如果你想从 AbstractBase继承,却又想提供另一个默认管理器,那么你可以在子类中定义默认管理器:

class ChildB(AbstractBase):
    # ...
    # 一个显式的默认管理器。
    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()

会抛出一个异常。 这是因为,管理器被设计用来封装对象集合管理的逻辑。 由于抽象的对象中并没有一个集合,管理它们是毫无意义的。 如果你写了应用在抽象模型上的功能,你应该把功能放到抽象模型的classmethod,或者staticmethod中。

实现上的注意事项

无论你添加到自定义管理器中的功能是什么,它必须可以生成Manager实例的一个浅拷贝;即以下代码必须工作:

>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)

Django在某些查询期间生成管理器对象的浅拷贝;如果你的管理器无法复制,则这些查询将失败。

这对于大多数自定义管理器不是什么大问题。 如果你只是添加一些简单的方法到你的Manager中,不太可能会把你的Manager实例变为不可复制的。 但是,如果你正在覆盖__getattr__或者Manager中控制对象状态的其它私有方法,你应该确保不会影响到Manager的复制。