Spec-Zone.ru › Django 4.2

Менеджеры

class Manager

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

Способ работы классов менеджеров описан в Выполнение запросов; этот документ подробно рассматривает параметры моделей, которые настраивают поведение менеджеров.

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

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

from django.db import models


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

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

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

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

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

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

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

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

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

С этим примером, вы бы использовали `Book.by_author('Roald Dahl')` для получения списка объектов `Book` с дополнительным атрибутом `by_author`.

Метод пользовательского менеджера может возвращать что угодно. Он не обязательно должен возвращать набор результатов.

Ещё один момент, который следует отметить, это то, что методы пользовательских менеджеров могут получить доступ к `self` для получения класса модели, к которому они прикреплены.

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

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

from django.db import models


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

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

Вы можете переопределить базовый набор результатов менеджера, переопределив метод `get_queryset()`. Метод `get_queryset()` должен возвращать набор результатов с требуемыми свойствами.

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

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

Поскольку `get_queryset()` возвращает объект набора результатов, вы можете использовать `all()`, `filter()`, и все другие методы набора результатов над ним. Следовательно, эти выражения допустимы:

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

Этот пример также показал ещё одну интересную технику: использование нескольких менеджеров для одной модели. Вы можете прикрепить любое количество экземпляров менеджеров к модели, сколько нужно. Это способ определения общих «фильтров» для ваших моделей без повторения кода.

Например:

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

Этот пример позволяет вам запросить `Book.objects.all()`, `Book.by_roald_dahl.all()`, и `Book.by_author.all()`, получая предсказуемые результаты.

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

Model._default_manager

Если вы используете пользовательские объекты менеджера, обратите внимание, что первый менеджер, с которым сталкивается Django (в порядке их определения в модели), имеет особый статус. Django интерпретирует первый менеджер, определённый в классе, как «по умолчанию», и несколько частей Django (включая dumpdata) будут использовать этот менеджер исключительно для этой модели. В результате, стоит быть внимательным в выборе менеджера по умолчанию, чтобы избежать ситуации, когда переопределение менеджера по умолчанию может привести к невозможности получения интересующих объектов.

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

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

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

Model._base_manager

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

По умолчанию Django использует экземпляр класса менеджера `objects` при доступе к связанным объектам (т.е. при запросе к связанным моделям), а не менеджер по умолчанию связанного объекта. Это потому, что Django должен уметь получить связанный объект, даже если он бы иначе был отфильтрован (и, следовательно, был недоступен) менеджером по умолчанию.

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

Базовые менеджеры не используются при запросе к связанным моделям или при доступе к связи один-ко-многим или многие-ко-многим. Например, если модель `Question` из туториала имела поле `is_published` и базовый менеджер, который отфильтровывал экземпляры с `is_published=False`, запрос `Question.objects.all().published_questions.all()` включил бы связанные с ними ответы.

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

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

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

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

Хотя большинство методов стандартного набора результатов доступны напрямую из менеджера, это верно только для дополнительных методов, определённых в пользовательском менеджере, если вы также реализуете их в наборе результатов:

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

Этот пример позволяет вам вызвать как `filter_by_year` и `filter_by_author` напрямую из менеджера `Book.objects`.

Создание менеджера с методами набора результатов

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

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

Экземпляр менеджера, созданный с помощью QuerySet.as_manager(), будет практически идентичен экземпляру менеджера из предыдущего примера.

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

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

  • Общедоступные методы копируются по умолчанию.
  • Приватные методы (начинающиеся с нижнего подчёркивания) не копируются по умолчанию.
  • Методы с атрибутом `as_manager` установленным в `True` всегда копируются.
  • Методы с атрибутом `as_manager` установленным в `False` никогда не копируются.

Например:

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)

Для расширенного использования вам может потребоваться как пользовательский менеджер, так и пользовательский набор результатов. Вы можете сделать это, вызвав `from_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/4.2/topics/db/managers/

Spec-Zone.ru

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