Managers --Django1.7

管理器(Managers)

管理器是一个对于Django模型提供的针对数据库查询的接口。在每个Django应用中,至少有一个管理器存在。

管理器工作的方式记录在Making queries;中,该文档特别记录了关于通过模型选项定制Manager的行为。

Manager names

默认的,Django会为每个Django模型类添加一个Manager ,命名为objects。然而,如果你想要把objects当作一个字段名,或者你想用其他名字给管理器命名。你可以在per-model basis里重命名它。为了再给定的模型中重命名manager,先在该类中定义一个models.Manegr()类的类属性。

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

 

如同示例中,Person.objects将会引起一个AttributeError类型的错误。但是Person,people.all()将会产生一个所有Person对象的列表。

定制 Managers

你可以在一个特殊的类里通过扩展基础Manager类,并实例化你的定制Manager。两种情况可能会使用定制的Manager:为了添加额外的Manager方法,或者为了修改初始QuerySet和Manager的返回结果。

添加额外的Manager方法

添加额外的Manager方法是一个较好的给你的模型添加“table-level”功能。(对于“row-level”功能,即,在单一模型实例上操作的功能--这类功能使用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
        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()来返回一个OpinionPoll对象列表,每一个都附带一个汇总查询结果的num_responses属性。

另一个需要注意的就是这个例子中的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()语句将返回所有数据库中的books。

你可以Manager.get_queryset()通过重写Manager的基本查询集。Manager.get_queryset()应该返回一个给定属性的查询集。

例如,下列模型有两个Manager,一个返回所有实例,另一个只返回books by 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.

当然,因为get_queryset()返回 QuerySet 对象,你可以使用filter(), exclude()和其他QuerySet方法。

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

这个例子也指出了另一个有趣的技巧,在同一个模型中使用多个Manager。你将可以获得任意多的的Manager对象。这可以在你的模型中很简单的定义常见的“filter”。

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()

 

默认 managers

如果你想使用定制的Mnaager对象,注意第一个Django Manager 的情况(按在模型中定义的顺序排列),有一种特殊的状态。Django把模型类中第一个定义的Manager看作默认Manager,而且在Django的几个部分中(including dumpdata)将未来那个模型专门的使用该Manager。结果,为了避免需要重写get_queryset()得到一个你希望得到的无法检索的对象,小心的选择你的默认Manager是一个好主意。

Changed in Django 1.6:

The get_queryset method 过去命名为 get_query_set.

使用managers连接关联的对象

默认的,当关联连接的实例时,Django使用一个“plain”manager 类的实例(例:choice.poll),而不是默认的manager。这是因为Django需要有能力检索关联的对象,而使用默认manager它就会被过滤掉(导致无法访问)。

如果普通的“plain”manager类(django.db.models.Manager)在你的应用的情景下不适用。你可以强行使Django对你的模型使用和默认manager相同的类,通过在manger类中设定use_for_related_fields属性。相关文档在下面。

从Manager调用自定义 QuerySet方法

当大多数标准QuerySet的方法可以直接通过Manager访问时,只有当你同时在Manager和QuerySet中执行某些方法时,你才需要在自定义QuerySet中额外定义该方法。

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()

 

该例子允许你同时直接从manager Person.people调用authors()editors()

使用QuerySet 的方法创建Manager

New in Django 1.7.
上述情况下,需要QuerySet 和 the Manager相同的方法。QuerySet.as_manager() 可以用于创建一个作为自定义QuerySet方法复制品的Manager实例。
class Person(models.Model):
    ...
    people = PersonQuerySet.as_manager()

这个Manager实例通过QuerySet.as_manager()创建,它和之前例子的PersonManager几乎相同。

并不是每一个QuerySet方法都在Manager中有意义。我们在QuerySet方法复制品的Manager中使用QuerySet.delete()来刻意阻止那些实例。

方法的拷贝遵循一下规则:

  • Public方法默认会被拷贝。
  • 私有方法(以下划线开头)默认不会被拷贝。
  • 含有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(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()

自定义managers和类继承

 类继承和manager模型不会完美的符合每个人的要求。Mananger一般是指特别的类,它总是自己定义的类并在子类中继承它们,但这并不是一个很好的注意。而且,因为第一个定义的manager会默认为默认manager,允许这件事可以由用户控制很重要。所以,下面是Django怎么处理自定义manager和模型继承的。

  1. Manager定义自非抽象基类不是继承自子类。如果你想要重用一个来自非抽象基类的manager,明确的重声明它的子类。这种managers可能 to be fairly specific to the class they are defined on,因此继承他们可能经常会引起不可预期的结果(特别是当使用默认manager时)。因此,他们不能传递到子类。
  2. 源自抽象基类的Manager总是继承自其子类,使用Python正常的命名解析顺序(子类的名字会覆盖其他,接下来是第一个父类,and so on)。抽象基类被设计成为了捕获其子类常见的信息和行为。定义常见的manager是一般性信息的合理部分。
  3. 类中的默认manager是类中第一个定义的manager,或者是父层次(in the parent hierarchy)中的第一个抽象基类的默认manager。如果默认manager并没有被明确指明,Django的常用默认manager会被使用。

上述规则提供了必要的灵活性,如果你想要通过一个抽象基类,在一组模型中安装一系列的自定义manager,但是仍然自定义默认manager。例如:

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

    class Meta:
        abstract = True

如果直接在一个子类中使用它,而不在基类中声明manager,objects将会是默认manager。

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

如果你想要从AbstractBase中继承,但是却需要不同的默认manager,你可以在子类中提供默认manager。

class ChildB(AbstractBase):
    # ...
    # An explicit default manager.
    default_manager = OtherManager()

 

 在这里,default_manager是默认的名字。因为objects是继承来的,objects manager仍然是可以使用的。只是objects不再被作为默认manager。

例如,假如你想给你的子类添加一个额外的manager,但是还是使用从AbstractBase的默认manager。你不能直接在子类中添加一个新的manager,因为那会覆盖默认manager。你必须明确包括所有从抽象基类继承的manager。解决方式就是把额外的manager放到另一个基类中,并且把它引入默认值之后的继承层次。

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

 

注意当你可以在抽象模型中定义一个自定义manager时,你不可以调用任意抽象模型的方法,下列是合法的:
ClassA.objects.do_something()

但是:

AbstractBase.objects.do_something()

将会产生一个错误。这是因为manager期望为了管理objects的集合而封装逻辑。所以,你不可以有一个抽象对象的集合,这在管理他们上是没有意义的。如果你适用于抽象模型的功能,你应该把这些功能放在抽象模型的staticmethod、classmethod

Implementation concerns

无论,你向默认Manager添加了什么特性,必须保证可以执行Manager实例的浅拷贝。即下列代码必须可用:

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

 Django在某些查询时执行manager实例的浅拷贝,所以Manager不可拷贝,那些查询就会失败。

对大部分的自定义manager不是问题。如果只是在manager中添加了简单的方法,不太可能会使你manager的实例不可拷贝。然而,如果你重写了__getattr__ 或其他的一些你控制manager对象状态的私有方法你应该确认没有影响到Mnager的可拷贝性。

控制自动 Manager 类型

 本段文档已经在若干处Django创建manager类:default managers and the “plain” manager used to access related objects,中提到了。There are other places in the implementation of Django where temporary plain managers are needed. Those automatically created managers will normally be instances of the django.db.models.Manager class.
.......

为使用自动Manager实例写正确的Manager

As already suggested by the django.contrib.gis example, above, the use_for_related_fields feature is primarily for managers that need to return a custom QuerySet subclass. In providing this functionality in your manager, there are a couple of things to remember.

Do not filter away any results in this type of manager subclass

One reason an automatic manager is used is to access objects that are related to from some other model. In those situations, Django has to be able to see all the objects for the model it is fetching, so that anything which is referred to can be retrieved.

....

Set use_for_related_fields when you define the class

The use_for_related_fields attribute must be set on the manager class, not on an instance of the class. The earlier example shows the correct way to set it, whereas the following will not work:

# BAD: Incorrect code
class MyManager(models.Manager):
    # ...
    pass

# Sets the attribute on an instance of MyManager. Django will
# ignore this setting.
mgr = MyManager()
mgr.use_for_related_fields = True

class MyModel(models.Model):
    # ...
    objects = mgr

# End of incorrect code.
You also shouldn’t change the attribute on the class object after it has been used in a model, since the attribute’s value is processed when the model class is created and not subsequently reread. Set the attribute on the manager class when it is first defined, as in the initial example of this section and everything will work smoothly.

 

 
posted @ 2014-12-16 17:15  落叶落叶  阅读(454)  评论(0编辑  收藏  举报