Менеджеры
-
class Manager[source]
A 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)
person_name = models.CharField(max_length=50)
response = models.TextField()
С помощью этого примера вы можете использовать OpinionPoll.objects.with_counts() для возврата этого списка объектов OpinionPoll с атрибутами num_responses.
Еще одна важная деталь в этом примере заключается в том, что методы Manager имеют доступ к self.model для получения класса модели, к которой они прикреплены.
Модификация начальных наборов запросов менеджера
Базовый набор запросов Manager QuerySet возвращает все объекты в системе. Например, используя эту модель:
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 QuerySet , переопределив метод 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 методов из Manager
Хотя большинство методов стандартного 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.
Создание Manager с 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 создаёт для вас — либо как менеджер по умолчанию для модели без менеджеров, либо временно при обращении к связанным объектам.
Иногда этот класс по умолчанию не подходит. Один пример — в приложении django.contrib.gis, которое поставляется вместе с Django. Все gis модели должны использовать специальный класс менеджера (GeoManager), потому что им нужен специальный набор запросов (GeoQuerySet), используемый для взаимодействия с базой данных. Оказывается, что модели, которые требуют специального менеджера, такого как этот, должны использовать тот же класс менеджера везде, где создаётся автоматический менеджер.
Django предоставляет способ для разработчиков настраиваемых менеджеров указать, что их класс менеджера должен использоваться для автоматических менеджеров, когда он является менеджером по умолчанию для модели. Это делается путём установки атрибута use_for_related_fields в классе менеджера:
class MyManager(models.Manager):
use_for_related_fields = True
# ...
Если этот атрибут установлен в менеджере по умолчанию для модели (в этих ситуациях рассматривается только менеджер по умолчанию), Django будет использовать этот класс всякий раз, когда ему нужно автоматически создать менеджер для класса. В противном случае он будет использовать django.db.models.Manager.
Историческая справка
Учитывая назначение, имя этого атрибута (use_for_related_fields) может показаться немного странным. Изначально этот атрибут контролировал только тип менеджера, используемого для доступа к связанным полям, отсюда и название. По мере того, как стало ясно, что концепция более полезна, название не было изменено. В основном для того, чтобы существующий код продолжал работать в будущих версиях Django.
Написание корректных менеджеров для использования в экземплярах автоматических менеджеров
Как уже было показано в примере с django.contrib.gis, выше, функция 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.8/topics/db/managers/