Менеджеры
-
class Manager[source]
Интерфейс 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 может возвращать любой тип данных. Он не обязан возвращать QuerySet.
Например, этот пользовательский Manager предлагает метод with_counts(), который возвращает список всех OpinionPoll объектов, каждый из которых имеет дополнительный атрибут num_responses, являющийся результатом агрегатного запроса:
from django.db import models
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
cursor = connection.cursor()
cursor.execute("""
SELECT p.id, p.question, p.poll_date, COUNT(*)
FROM polls_opinionpoll p, polls_response r
WHERE p.id = r.poll_id
GROUP BY p.id, p.question, p.poll_date
ORDER BY p.poll_date DESC""")
result_list = []
for row in cursor.fetchall():
p = self.model(id=row[0], question=row[1], poll_date=row[2])
p.num_responses = row[3]
result_list.append(p)
return result_list
class OpinionPoll(models.Model):
question = models.CharField(max_length=200)
poll_date = models.DateField()
objects = PollManager()
class Response(models.Model):
poll = models.ForeignKey(OpinionPoll, on_delete=models.CASCADE)
person_name = models.CharField(max_length=50)
response = models.TextField()
В этом примере вы используете OpinionPoll.objects.with_counts() для получения списка OpinionPoll объектов с атрибутами num_responses.
Еще одна важная деталь в этом примере заключается в том, что методы 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(DahlBookManager, self).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(AuthorManager, self).get_queryset().filter(role='A')
class EditorManager(models.Manager):
def get_queryset(self):
return super(EditorManager, self).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(), обеспечивая предсказуемые результаты.
Менеджеры по умолчанию
Если вы используете пользовательские объекты Manager , обратите внимание, что первый Manager Django встречает (в порядке их определения в модели) имеет особый статус. Django интерпретирует первый Manager определенный в классе как «по умолчанию» Manager, и несколько частей Django (включая dumpdata) будут использовать этот Manager исключительно для данной модели. В результате, стоит быть внимательным при выборе менеджера по умолчанию, чтобы избежать ситуации, когда переопределение get_queryset() приводит к невозможности получения нужных объектов.
Использование менеджеров для доступа к связанным объектам
Если обычный менеджер класса («простого» менеджера) (django.db.models.Manager) не подходит для вашей ситуации, вы можете принудительно заставить Django использовать тот же класс, что и менеджер по умолчанию для вашей модели, установив атрибут use_for_related_fields в классе менеджера. Это подробно описано ниже ниже.
Вызов пользовательских методов 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, можно использовать QuerySet.as_manager() для создания экземпляра Manager с копией методов пользовательского менеджера QuerySet:
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 BaseManager(models.Manager):
def manager_only_method(self):
return
class CustomQuerySet(models.QuerySet):
def manager_and_queryset_method(self):
return
class MyModel(models.Model):
objects = BaseManager.from_queryset(CustomQuerySet)()
Вы также можете сохранить сгенерированный класс в переменную:
CustomManager = BaseManager.from_queryset(CustomQuerySet)
class MyModel(models.Model):
objects = CustomManager()
Пользовательские менеджеры и наследование моделей
Наследование классов и менеджеры моделей не идеально сочетаются друг с другом. Менеджеры часто специфичны для классов, в которых они определены, и их наследование в подклассах не всегда целесообразно. Также, поскольку первый объявленный менеджер является менеджером по умолчанию, важно позволить контролировать этот аспект. Вот как Django обрабатывает пользовательские менеджеры и наследование моделей:
- Менеджеры, определенные в базовых классах, не наследуются подклассами. Если вы хотите повторно использовать менеджер из базового класса, явно переопределите его в подклассе. Такие менеджеры, скорее всего, довольно специфичны для класса, в котором они определены, поэтому их наследование часто приводит к непредвиденным результатам (особенно в отношении менеджера по умолчанию). Поэтому они не передаются подклассам.
- Менеджеры из абстрактных базовых классов всегда наследуются подклассом, используя стандартный порядок разрешения имен Python (имена в подклассе переопределяют все остальные; затем идут имена в первом родительском классе и так далее). Абстрактные базовые классы предназначены для захвата информации и поведения, общих для их подклассов. Определение общих менеджеров является подходящей частью этой общей информации.
- Менеджер по умолчанию для класса — это либо первый объявленный менеджер в классе, если он существует, либо менеджер по умолчанию первого абстрактного базового класса в иерархии предков, если он существует. Если менеджер по умолчанию не объявлен явно, используется менеджер по умолчанию Django.
Эти правила обеспечивают необходимую гибкость, если вы хотите установить набор пользовательских менеджеров на группу моделей через абстрактный базовый класс, но при этом настроить менеджер по умолчанию. Например, предположим, что у вас есть этот базовый класс:
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 создаёт для вас класс менеджера: менеджеры по умолчанию и «простой» менеджер, используемый для доступа к связанным объектам. Существуют и другие места в реализации Django, где требуются временные простые менеджеры. Автоматически созданные менеджеры обычно будут экземплярами класса django.db.models.Manager.
В этом разделе мы будем использовать термин «автоматический менеджер», чтобы обозначать менеджер, который Django создаёт для вас — либо как менеджер по умолчанию для модели без менеджеров, либо для временного использования при доступе к связанным объектам.
Иногда этот класс по умолчанию не подходит. Менеджер по умолчанию может не иметь всех методов, необходимых для работы с вашими данными. Пользовательский класс менеджера позволит вам создавать пользовательские объекты QuerySet, чтобы получить необходимую вам информацию.
Django предоставляет способ для разработчиков пользовательских менеджеров указать, что их класс менеджера должен использоваться для автоматических менеджеров всякий раз, когда он является менеджером по умолчанию для модели. Это делается путём установки атрибута use_for_related_fields в классе менеджера:
class MyManager(models.Manager):
use_for_related_fields = True
# ...
Если этот атрибут установлен для менеджера по умолчанию модели (в этих ситуациях рассматривается только менеджер по умолчанию), Django будет использовать этот класс всякий раз, когда ему нужно автоматически создать менеджер для класса. В противном случае он будет использовать django.db.models.Manager.
Историческая справка
Учитывая назначение, название этого атрибута (use_for_related_fields) может показаться немного странным. Изначально атрибут контролировал только тип менеджера, используемого для доступа к связанным полям, отсюда и название. Поскольку стало ясно, что концепция более широко полезна, название не было изменено. В основном это для того, чтобы существующий код продолжал работать в будущих версиях Django.
Написание корректных менеджеров для использования в экземплярах автоматических менеджеров
Функция use_for_related_fields предназначена в первую очередь для менеджеров, которым необходимо вернуть пользовательский подкласс QuerySet. Предоставляя эту функциональность в своём менеджере, следует помнить об нескольких вещах.
Не удаляйте результаты в этом типе подкласса менеджера
Одна из причин использования автоматического менеджера заключается в доступе к объектам, связанным с какой-либо другой моделью. В этих ситуациях Django должен видеть все объекты для модели, которую он извлекает, чтобы любой объект, на который есть ссылка, мог быть извлечён.
Если вы переопределяете метод get_queryset() и фильтруете какие-либо строки, Django вернёт некорректные результаты. Не делайте этого. Менеджер, который фильтрует результаты в get_queryset(), не подходит для использования в качестве автоматического менеджера.
Установите use_for_related_fields при определении класса
Атрибут use_for_related_fields должен быть установлен на классе менеджера, а не на экземпляре класса. Предыдущий пример демонстрирует правильный способ установки, в то время как следующий не сработает:
# BAD: Incorrect code
class MyManager(models.Manager):
# ...
pass
# Sets the attribute on an instance of MyManager. Django will
# ignore this setting.
mgr = MyManager()
mgr.use_for_related_fields = True
class MyModel(models.Model):
# ...
objects = mgr
# End of incorrect code.
Также не следует изменять атрибут в объекте класса после его использования в модели, так как значение атрибута обрабатывается при создании класса модели и не перечитывается впоследствии. Установите атрибут в классе менеджера при его первоначальном определении, как в первом примере этого раздела, и всё будет работать гладко.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.9/topics/db/managers/