Менеджеры

class Manager[исходный код]

Manager - это интерфейс, через который для моделей Django предоставляются операции запросов к базе данных. По крайней мере, один Manager существует для каждой модели в приложении Django.

Принцип работы классов Manager задокументирован в Работа с запросами; этот документ специально касается параметров модели, которые настраивают поведение Manager.

Имена менеджеров

По умолчанию Django добавляет Manager с именем objects в каждый класс модели Django. Однако, если вы хотите использовать 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 и создав экземпляр своего настраиваемого Manager в своей модели.

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

Добавление дополнительных методов менеджера

Добавление дополнительных методов в Manager является предпочтительным способом добавления функциональности «уровня таблицы» в ваши модели. (Для функциональности на уровне строки, то есть функций, которые действуют на один экземпляр объекта модели, используйте Методы модели, а не пользовательские методы 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(), чтобы вернуть этот список объектов OpinionPoll с атрибутами num_responses.

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

Изменение начального QuerySet менеджера

Базовый QuerySet Manager’а возвращает все объекты в системе. Например, используя эту модель:

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’а, переопределив метод Manager.get_queryset(). get_queryset() должен вернуть QuerySet с нужными вам свойствами.

Например, в следующей модели есть два Manager - один возвращает все объекты, а другой возвращает только книги Роальда Даля:

# 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() вернет только те, которые написаны Роальдом Далем.

Конечно, поскольку 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, обратите внимание, что первые обнаруженные Django объекты Manager (в том порядке, в котором они определены в модели) имеют особый статус. Django интерпретирует первый менеджер, определенный в классе, как менеджер по умолчанию, и некоторые части Django (включая dumpdata) будут использовать этот менеджер исключительно для этой модели. В результате рекомендуется быть осторожным при выборе диспетчера по умолчанию, чтобы избежать ситуации, когда переопределение get_queryset() приведет к невозможности получить объекты, с которыми вы хотите работать.

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

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

Базовые менеджеры

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

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

Создание менеджера с помощью методов 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. Менеджеры из базовых классов всегда наследуются дочерним классом с использованием обычного порядка разрешения имен 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 по умолчанию. Менеджер объектов по-прежнему доступен, поскольку он унаследован. Он просто не используется по умолчанию.

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

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

Какие бы функции вы ни добавляли в свой настраиваемый Manager, должна быть возможность сделать мелкую копию экземпляра Manager; то есть следующий код должен работать:

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

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

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

Вернуться на верх