Менеджеры
-
class Manager
Интерфейс Manager, посредством которого выполняются операции запросов к базе данных для моделей Django. По крайней мере, один Manager существует для каждой модели в приложении Django.
Способы работы с классами Manager описаны в Выполнение запросов; в данном документе подробно рассматриваются параметры моделей, которые настраивают поведение Manager.
Имена менеджеров
По умолчанию Django добавляет Manager с именем objects к каждому классу модели Django. Однако, если вы хотите использовать objects в качестве имени поля или хотите использовать имя, отличное от objects для Manager, вы можете переименовать его на уровне конкретной модели. Для переименования 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)
# ...
В этом примере вы будете использовать OpinionPoll.objects.with_counts() для получения QuerySet объектов OpinionPoll с дополнительным атрибутом num_responses.
Пользовательский метод Manager может возвращать любое значение. Он не обязан возвращать QuerySet.
Еще один важный момент заключается в том, что методы Manager имеют доступ к self.model для получения класса модели, к которому они прикреплены.
Изменение исходного набора результатов менеджера
Базовый набор результатов 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() вернёт все книги в базе данных.
Вы можете переопределить базовый набор результатов Manager менеджера, переопределив метод Manager.get_queryset() . get_queryset() должен возвращать набор результатов 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.dahl_objects.all() вернёт только книги, написанные Роальдом Далем.
Так как get_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.db.models.Manager) не подходит для ваших обстоятельств, вы можете указать Django, какой класс использовать, установив Meta.base_manager_name.
Базовые менеджеры не используются при запросе связанных моделей или при доступе к отношению один-ко-многим или многие-ко-многим. Например, если модель Question из туториала имела поле deleted и базовый менеджер, который отфильтровывал экземпляры с deleted=True, запрос Choice.objects.filter(question__name__startswith='What') будет включать связанные варианты удалённых вопросов.
Не фильтруйте результаты в этом типе подкласса менеджера
Этот менеджер используется для доступа к объектам, связанным с другими моделями. В этих ситуациях Django должен видеть все объекты модели, так как нужно извлечь любой объект, на который есть ссылка.
Поэтому вы не должны переопределять get_queryset() для фильтрации строк. В противном случае Django вернёт неполные результаты.
Вызов пользовательских методов набора результатов из менеджера
Хотя большинство методов стандартного набора результатов 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 , так и в 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.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 некопируемыми. Однако, если вы перезаписываете __getattr__ или какой-либо другой закрытый метод вашего объекта Manager, который контролирует состояние объекта, вы должны убедиться, что не повлияете на способность вашего Manager копироваться.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/3.2/topics/db/managers/