Менеджеры
-
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
Не отфильтровывайте результаты в подклассе этого типа менеджера
Этот менеджер используется для доступа к объектам, на которые ссылается другая модель. В таких случаях 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 обрабатывает пользовательские менеджеры и наследование моделей:
- Менеджеры базовых классов всегда наследуются дочерним классом с использованием стандартного порядка разрешения имён Python (имена в дочернем классе имеют приоритет над всеми остальными; затем следуют имена первого родительского класса и так далее).
- Если ни в модели, ни в её родительских классах не объявлены менеджеры, Django автоматически создаёт менеджер
objects. - Менеджер класса по умолчанию — это либо менеджер, выбранный с помощью
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/