Spec-Zone.ru › Django 6.0

Менеджеры

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

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

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

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

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

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

Пользовательский метод Manager может возвращать что угодно. Он не обязан возвращать QuerySet.

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

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

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

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

Model._base_manager

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

По умолчанию при доступе к связанным объектам (например, choice.question) Django использует экземпляр класса менеджера Model._base_manager, а не _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, для создания экземпляра Manager с копией методов пользовательского QuerySet можно использовать QuerySet.as_manager():

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

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

Spec-Zone.ru

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