Менеджеры

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 с результатом агрегации:

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, on_delete=models.CASCADE)
    person_name = models.CharField(max_length=50)
    response = models.TextField()

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

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

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

Базовый 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() вернет все книги из базы данных.

Вы можете изменить базовый QuerySet, переопределив метод Manager.get_queryset(). Метод get_queryset() должен возвращать 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() для получения соответствующего результата.

Менеджеры по умолчанию

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

Вызов собственных методов QuerySet из Manager

Т.к. большинство методом стандартного QuerySet доступны из Manager, следующий подход необходимо использовать только для собственных методов переопределенного 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()

Этот пример позволяет вызывать authors() и editors() непосредственно из менеджера Person.people.

Создание Manager с методами QuerySet

Вместо подхода, описанного выше, который требует дублирования методов в QuerySet и Manager, QuerySet.as_manager() позволяет создать экземпляр Manager со всеми методами QuerySet:

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

Экземпляр Manager, созданный QuerySet.as_manager(), предоставляет интерфейс аналогичный PersonManager из примера выше.

Не каждый метод QuerySet следует добавлять в Manager. Например, метод QuerySet.delete() намерено не добавляется в класс Manager.

Методы копируются в соответствии со следующими правилами:

  • Публичные методы копируются по умолчанию.

  • Приватные методы (которые начинаются с подчеркивания) не копируются по умолчанию.

  • Методы с атрибутом 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

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. Менеджеры из не абстрактной базовой модели не наследуются дочерними. Если вы хотите унаследовать менеджер из не абстрактной родительской модели, добавьте его в дочернюю модель. Такие менеджеры обычно тесно связаны с моделью, поэтому их наследование может привести к проблемам (по крайней мере менеджер по умолчанию). Таким образом, они не передаются на дочерние классы.

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

вызовет исключение. Менеджеры инкапсулируют логику работы с коллекцией объектов. Так как мы не можем создать коллекцию абстрактных объектов, нет смысла использовать менеджер для них. Если вам нужен функционал связанный с абстрактной моделью, добавьте его в staticmethod или classmethod.

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

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

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

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

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

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

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

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

В некоторых случаях класс по умолчанию может вам не подходить, и не содержать всех необходимых методов для работы с вашими данными. Класс собственного менеджера позволит вам создать свой QuerySet, который предоставляет необходимую вам информацию.

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

class MyManager(models.Manager):
    use_for_related_fields = True
    # ...

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

Историческая справка

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

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

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

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

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

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