• 3.1
  • 3.2
  • 6.1
  • Версия документации: 5.0

Менеджеры

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» функционала – то есть функции, которые работают с одним экземпляром модели – используйте методы модели, а не методы менеджера.)

Например, этот пользовательский Менеджер добавляет метод with_counts():

from django.db import models
from django.db.models.functions import Coalesce


class PollManager(models.Manager):
    def with_counts(self):
        return self.annotate(num_responses=Coalesce(models.Count("response"), 0))


class OpinionPoll(models.Model):
    question = models.CharField(max_length=200)
    objects = PollManager()


class Response(models.Model):
    poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
    # ...

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

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

Еще следует отметить, что методы 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().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().get_queryset().filter(role="A")


class EditorManager(models.Manager):
    def get_queryset(self):
        return super().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, помните, что первый Manager, который заметит Django (в том порядке, в котором они определяются в модели) имеет особый статус. Для Django первый Manager будет Manager «по умолчанию», и некоторые компоненты Django (включая dumpdata) будут использовать этот Manager. Поэтому нужно быть осторожным при выборе менеджера по умолчанию, чтобы, переопределив get_queryset(), не попасть в ситуацию, когда вы не можете получить нужный объект.

Вы можете указать собственный менеджер по умолчанию, используя Meta.default_manager_name.

Если вы пишете код, который должен обрабатывать неизвестную модель, например, в стороннем приложении, реализующем общее представление, используйте этот менеджер (или _base_manager), а не предполагайте, что модель имеет менеджер объектов.

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

Model._base_manager

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

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

Поэтому вам не следует переопределять get_queryset() для фильтрации любых строк. Если вы это сделаете, Django вернет неполные результаты.

Вызов собственных методов 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 CustomManager(models.Manager):
    def manager_only_method(self):
        return


class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return


class MyModel(models.Model):
    objects = CustomManager.from_queryset(CustomQuerySet)()

Созданный класс можно сохранить в переменной:

MyManager = CustomManager.from_queryset(CustomQuerySet)


class MyModel(models.Model):
    objects = MyManager()

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

Вот как Django обрабатывает пользовательские менеджеры и наследование модели:

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

  2. Если для модели и/или ее родителей не объявлены менеджеры, Django автоматически создает менеджер объектов.

  3. Менеджером по умолчанию для класса является либо тот, который выбран с помощью Meta.default_manager_name, либо первый менеджер, объявленный в модели, либо менеджер по умолчанию первой родительской модели.

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

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__, или какой-либо другой приватный метод, который контролирует состояние объекта, вы должны убедиться что объект можно скопировать.

Back to Top