Spec-Zone.ru › Django 5.2

Менеджеры

class Manager [source]

Интерфейс, посредством которого выполняются операции запроса к базе данных для моделей Django. Для каждой модели в приложении Django существует хотя бы один менеджер.

Работа классов менеджеров описана в Выполнении запросов. Этот документ подробно рассматривает параметры модели, которые настраивают поведение менеджеров.

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

По умолчанию Django добавляет менеджер с именем `objects` к каждому классу модели Django. Однако, если вы хотите использовать `objects` как имя поля или использовать другое имя для менеджера, вы можете переименовать его на уровне каждой модели. Для переименования менеджера для данного класса определите атрибут класса типа `str` в модели. Например:

from django.db import models


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

Используя эту модель, вызов `MyModel.objects` сгенерирует исключение `AttributeError`, но `MyModel.all_objects` предоставит список всех объектов `MyModel`.

Пользовательские менеджеры

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

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

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

Добавление дополнительных методов Manager — это предпочтительный способ добавления функциональности «уровня таблицы» к вашим моделям. (Для функциональности «уровня строки» — то есть функций, действующих на один экземпляр объекта модели — используйте Методы модели, а не пользовательские методы Manager.)

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

Изменение начального набора результатов менеджера

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

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

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

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

Model._base_manager

Использование менеджеров для доступа к связанным объектам

По умолчанию Django использует экземпляр класса менеджера Model._base_manager при обращении к связанным объектам (т. е. choice.question), а не менеджер _default_manager связанного объекта. Это потому, что Django необходимо получить связанный объект, даже если он был бы иначе отфильтрован (и, следовательно, недоступен) по умолчанию менеджером.

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

Базовые менеджеры не используются при запросе связанных моделей или при доступе к связи один-ко-многим или многие-ко-многим. Например, если модель Question из учебника имела поле deleted и базовый менеджер, фильтрующий экземпляры с deleted=True, запрос вроде Choice.objects.filter(question__name__startswith='What') включал бы связанные элементы удалённых вопросов.

Не удаляйте результаты в этом типе подкласса менеджера

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

Поэтому вы не должны переопределять get_queryset() для фильтрации строк. В противном случае Django вернёт неполные результаты.

Вызов пользовательских методов 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 имеет смысл на уровне менеджера; например, мы намеренно предотвращаем копирование метода 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 автоматически создаёт менеджер objects.
  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 в абстрактной модели.

Вопросы реализации

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

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

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

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

© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.2/topics/db/managers/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API