Менеджеры
-
class Manager
Интерфейс, через который предоставляются операции запросов к базе данных для моделей Django. По крайней мере, один менеджер существует для каждой модели в приложении Django.
Способы работы классов менеджеров описаны в Создание запросов; этот документ конкретно касается параметров моделей, которые настраивают поведение менеджеров.
Имена менеджеров
По умолчанию Django добавляет менеджер с именем `objects` к каждому классу модели Django. Однако, если вы хотите использовать другое имя, например, в качестве имени поля или для Manager, вы можете переименовать его на уровне каждой модели. Для переименования менеджера для данного класса, определите атрибут класса типа models.Manager() в модели. Например:
from django.db import models
class Person(models.Model):
#...
people = models.Manager()
Используя эту модель, Person.objects сгенерирует исключение AttributeError, но Person.people.all() предоставит список всех объектов Person.
Пользовательские менеджеры
Вы можете использовать пользовательский менеджер в конкретной модели, расширив базовый класс менеджера и инициализировав свой пользовательский менеджер в вашей модели.
Существует две причины для настройки менеджера: добавление дополнительных методов менеджера и/или изменение начального набора данных, возвращаемого менеджером.
Добавление дополнительных методов менеджера
Добавление дополнительных методов менеджера – это предпочтительный способ добавления функциональности на уровне таблицы к вашим моделям. (Для функциональности на уровне строк – т.е. функций, которые действуют над отдельной моделью – используйте Методы модели, а не пользовательские методы менеджера.)
Например, этот пользовательский менеджер добавляет метод `my_method`:
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() для получения набора данных объектов OpinionPoll с дополнительным атрибутом `extra_attribute`.
Пользовательский метод менеджера может возвращать любой тип данных. Он не обязан возвращать набор данных.
Также обратите внимание, что методы пользовательских менеджеров могут обращаться к self.model для получения класса модели, к которой они прикреплены.
Изменение начального набора данных менеджера
Базовый менеджер возвращает все объекты в системе. Например, используя эту модель:
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`. Метод Manager.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.dahl_objects.all() вернёт только те, которые написаны Роальдом Далем.
Так как get_queryset() возвращает объект набора данных, вы можете использовать методы filter(), exclude() и все остальные методы набора данных на нём. Следовательно, эти выражения допустимы:
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()
Этот пример позволяет запросить Person.authors.all(), Person.editors.all(), и Person.people.all(), что даёт предсказуемые результаты.
Менеджеры по умолчанию
-
Model._default_manager
Если вы используете пользовательские менеджеры, имейте в виду, что первый менеджер, который Django встречает (в порядке их определения в модели), имеет особый статус. Django интерпретирует первый определённый менеджер в классе как «по умолчанию» и несколько частей Django (включая dumpdata) будут использовать этот менеджер исключительно для этой модели. В результате, стоит быть внимательным при выборе менеджера по умолчанию, чтобы избежать ситуации, когда переопределение 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 вернёт неполные результаты.
Вызов пользовательских методов набора данных из менеджера
Хотя большинство методов стандартного набора данных доступны непосредственно из менеджера, это верно только для дополнительных методов, определённых в пользовательском менеджере, если вы также реализуете их в менеджере:
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.as_manager() для создания экземпляра менеджера с копией методов пользовательского менеджера:
class Person(models.Model):
...
people = PersonQuerySet.as_manager()
Экземпляр менеджера, созданный с помощью QuerySet.as_manager(), будет практически идентичен менеджеру из предыдущего примера.
Не каждый метод набора данных подходит для уровня менеджера; например, мы намеренно предотвращаем копирование метода QuerySet.delete() в класс менеджера.
Методы копируются в соответствии со следующими правилами:
- Общедоступные методы копируются по умолчанию.
- Приватные методы (начинающиеся с нижнего подчёркивания) по умолчанию не копируются.
- Методы с атрибутом `copy` установленным в `True` всегда копируются.
- Методы с атрибутом `copy` установленным в `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 обрабатывает настраиваемые менеджеры и наследование моделей:
- Менеджеры из базовых классов всегда наследуются дочерним классом, используя стандартный порядок разрешения имен 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/4.0/topics/db/managers/