Менеджеры

class Manager

Менеджер(Manager) - это интерфейс, через который создаются запросы к моделям Django. Каждая модель имеет хотя бы один менеджер.

Как работает Manager описано в разделе Выполнение запросов; этот раздел описывает как изменить поведение менеджера модели.

Имя менеджера

По-умолчанию Django добавляет Manager с именем objects для каждого класса модели. Однако, если вы хотите использовать objects, как имя поля, или хотите использовать название отличное от objects для Manager, вы можете переименовать его для модели. Что бы переименовать Manager добавьте в класс атрибут, значение которого экземпляр models.Manager(). Например:

from django.db import models

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

Обращение к Person.objects вызовет исключение AttributeError, в то время как Person.people.all() вернет список всех объектов Person.

Собственные менеджеры

Вы можете использовать собственный менеджер создав его через наследование от основного класса Manager и добавив его в модель.

Есть две причины почему вам может понадобиться изменить Manager: добавить дополнительные методы, и/или изменить базовый QuerySet, который возвращает Manager.

Добавление методов в менеджер

Добавление дополнительных методов в Manager - лучший способ добавить “table-level” функционал в вашу модель. (Для “row-level” функционала – то есть функции, которые работают с одним экземпляром модели – используйте методы модели, а не методы менеджера.)

Методы Manager могут возвращать что угодно. И это не обязательно должен быть QuerySet.

Например, этот Manager имеет метод with_counts(), который возвращает список всех объектов OpinionPoll, каждый из которых содержит дополнительный атрибут num_responses с результатом агрегации:

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 1, 2, 3
            ORDER BY 3 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(Poll)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

Вы можете использовать OpinionPoll.objects.with_counts() что бы получить список объектов OpinionPoll с атрибутом num_responses.

Стоит отметить, что методы Manager могут обращаться к self.model что бы получить класс модели, которая использует этот менеджер.

Изменение базового QuerySets менеджера

Базовый QuerySet менеджера возвращает все объекты модели. Возьмем для примера эту модель:

class Book(models.Model):
    title = models.CharField(max_length=100)
    author = models.CharField(max_length=50)

...``Book.objects.all()`` вернет все книги из базы данных.

Вы можете изменить базовый QuerySet переопределив метод Manager.get_query_set(). Метод get_query_set() возвращает QuerySet с необходимыми вам свойствами.

Например, следующая модель содержит два менеджера – один возвращает все книги, второй - только книги Roald Dahl:

# First, define the Manager subclass.
class DahlBookManager(models.Manager):
    def get_query_set(self):
        return super(DahlBookManager, self).get_query_set().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_query_set() возвращает экземпляр QuerySet, вы можете использовать filter(), exclude() и остальные методы QuerySet. Например:

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

Этот пример так же показывает как мы можем использовать несколько менеджеров в одной модели. Вы можете добавить столько экземпляров Manager(), сколько вы пожелаете. Это позволяет легко добавить набор “стандартных” фильтров для вашей модели.

Например:

class MaleManager(models.Manager):
    def get_query_set(self):
        return super(MaleManager, self).get_query_set().filter(sex='M')

class FemaleManager(models.Manager):
    def get_query_set(self):
        return super(FemaleManager, self).get_query_set().filter(sex='F')

class Person(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    sex = models.CharField(max_length=1, choices=(('M', 'Male'), ('F', 'Female')))
    people = models.Manager()
    men = MaleManager()
    women = FemaleManager()

Этот пример позволяет выполнить Person.men.all(), Person.women.all(), и ``Person.people.all()``для получения соответствующего результата.

При использовании собственного объекта Manager,помните что первый Manager, который заметит Django (в том порядке, в котором они определяются в модели) имеет особый статус. Для Django первый Manager будет Manager “по-умолчанию”, и некоторые компоненты Django (включая dumpdata) будут использовать этот Manager. По-этому нужно быть осторожным при выборе менеджера по-умолчанию, что бы переопределив get_query_set() не попасть в ситуацию, когда вы не можете получить нужный объект.

Собственные менеджеры и наследование моделей

Наследование моделей и менеджеры взаимодействуют не совсем просто. Обычно менеджеры предназначены для работы с моделью, к которой принадлежат, по этому наследовать их в дочерних моделях - не всегда хорошая идея. К тому же первый менеджер в модели будет менеджером по-умолчанию, и очень важно что бы это так и работало. Вот как Django решает эти проблемы при наследовании моделей:

  1. Менеджеры из не абстрактной базовой модели не наследуются дочерними. Если вы хотите унаследовать менеджер из не абстрактной родительской модели, добавьте его в дочернюю модель. Такие менеджеры обычно тесно связаны с моделью, по этому их наследование может привести к проблемам (по крайней мере менеджер по-умолчанию). Таким образом, они не передаются на дочерние классы.
  2. Менеджеры абстрактного класса наследуются дочерним классом, используя правила именования Python (имена дочернего класса переопределяют имена родительского).Абстрактные классы созданы для работы с общими данными дочерних классов. Определение общих менеджеров - важная часть абстрактных моделей.
  3. Менеджером по-умолчанию становится первый менеджер определенный в модели. Если он отсутствует, используется менеджер по-умолчанию первого абстрактного класса среди родительских моделей, если он существует. Если таким образом не удалось определить менеджер по-умолчанию, используется стандартный менеджер Django.

Эти правила позволяют достаточно гибко добавить собственные менеджеры в модель через абстрактные модели, в то же время имея возможность переопределить менеджер по-умолчанию. Например, у нас есть такой базовый класс:

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

    class Meta:
        abstract = True

Унаследовав от него модель, objects станет менеджером по-умолчанию, если мы не определим другой менеджер в дочерней модели

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

Если вы хотите унаследоваться от 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.

Проблемы реализации

Всегда должно работать копирование экземпляра менеджера, то есть должен работать такой код:

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

Django выполняет копирование экземпляра менеджера при определенных запросах. Если ваш менеджер нельзя скопировать, эти запросы перестанут работать.

Для большинства менеджеров это не будет проблемой. Если вы просто добавить метод в Manager, вряд ли он станет не копируемый. Однако, если вы измените __getattr__ или какой-либо другой приватный метод, который контролирует состояние объекта, вы должны убедиться что объект можно скопировать.

Тип автоматически создаваемого менеджера

Этот раздел уже упоминался несколько раз там, где говорилось что Django создает менеджер для тебя: default managers и “стандартный” менеджер используемые для access related objects. Есть и другие ситуация, когда Django создает временные менеджеры. Обычно это экземпляры класса django.db.models.Manager.

В этом разделе мы будем использовать термит “автоматически созданный менеджер” подразумевая менеджер, который Django создает для вас – будь то менеджер по-умолчанию для модели без определенных менеджеров, или временный менеджер для доступа к связанным объектам.

Для некоторых случаев обычный класс не подойдет. Один из примеров это приложение django.contrib.gis которое включено в Django. Все gis модели должны использовать специальный менеджер (GeoManager), т.к. они должны использовать особый QuerySet класс (GeoQuerySet) для работы с базой данных. Получается что они должны использовать специальный менеджер каждый раз, когда Django должен автоматически создать его.

Django позволяет указать, что созданный вам менеджер должен использоваться как “автоматически созданный менеджер”, каждый раз когда он добавлен в модель как менеджер по-умолчанию. Это можно сделать используя атрибут use_for_related_fields:

class MyManager(models.Manager):
    use_for_related_fields = True

    ...

Если этот атрибут используется для менеджера по-умолчанию (это работает только для менеджера по-умолчанию), Django будет использовать его каждый раз при автоматическом создании менеджера, иначе будет использован django.db.models.Manager.

Historical Note

С учетом целей, для которых он используется, имя этого атрибута (use_for_related_fields) может звучать немного странно. Изначально этот атрибут использовался для определения менеджера для доступа к связанным объектам, вот откуда это название. В то время, когда стало понятно на сколько это широко можно использовать эту концепцию, имя не изменилось. Это было сделано, что бы существующий код продолжал работать в следующих версиях Django.

Каким должен быть класс автоматически создаваемого менеджера

Как уже упоминалось в примере о django.contrib.gis, use_for_related_fields используется для возвращения специального подкласса QuerySet. Реализуя подобный функционал необходимо помнить о нескольких правилах.

Не фильтруйте результаты запроса

Одно из назначений автоматически создаваемого менеджера - доступ к связанным объектам. В этом случае Django должен иметь возможность получить все связанные объекты.

Если вы переопределите метод get_query_set() и отфильтруете часть результата, Django вернет неверный результат. Не делайте этого. Менеджер, который фильтрует результат запроса в get_query_set() не подходит на роль автоматически создаваемого менеджера.