Spec-Zone.ru › Django 5.0

Менеджеры

class Manager

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

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

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

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

from django.db import models


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

Используя эту модель, вызов Book.objects.all() вызовет исключение AttributeError, но Book.my_books.all() вернёт список всех объектов 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_author.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_author.all() и Book.by_genre.all(), давая предсказуемые результаты.

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

Model._default_manager

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

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

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

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

Model._base_manager

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

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

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

Базовые менеджеры не используются при запросе по связанным моделям или при доступе к отношению «один ко многим» или «многие ко многим». Например, если у модели Question из учебника было поле ForeignKey и базовый менеджер, который отфильтровывает экземпляры с is_deleted=True, запрос Question.objects.all().order_by('pub_date') будет включать связанные варианты даже если вопросы были удалены.

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

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

Этот пример позволяет вызвать как by_author, так и by_genre напрямую из менеджера 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)

Для расширенного использования вам может понадобиться как настраиваемый менеджер, так и настраиваемый набор данных. Это можно сделать, вызвав Manager.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/5.0/topics/db/managers/

Spec-Zone.ru

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