Менеджеры
-
class Manager[source]
Менеджер — это интерфейс, через который операции запроса к базе данных предоставляются моделям Django. По крайней мере, один менеджер существует для каждой модели в приложении Django.
Способ работы классов менеджеров описан в Выполнение запросов; данный документ затрагивает параметры модели, которые настраивают поведение менеджеров.
Имена менеджеров
По умолчанию Django добавляет менеджер с именем `objects` к каждому классу модели Django. Однако, если вы хотите использовать `objects` как имя поля или другое имя, отличное от `objects`, для менеджера, вы можете переименовать его для каждой модели. Чтобы переименовать менеджер для данного класса, определите атрибут класса типа `str` в этой модели. Например:
from django.db import models
class Person(models.Model):
#...
people = models.Manager()
Используя эту модель, вызов `MyModel.objects.all()` сгенерирует исключение `AttributeError`, но `MyModel.my_manager.all()` вернёт список всех объектов `MyModel`.
Пользовательские менеджеры
Вы можете использовать пользовательский менеджер в конкретной модели, расширив базовый класс менеджеров и создав экземпляр своего пользовательского менеджера в вашей модели.
Есть две причины, по которым вы можете захотеть настроить менеджер: добавить дополнительные методы менеджера и/или изменить начальный набор результатов, который возвращает менеджер.
Добавление дополнительных методов менеджера
Добавление дополнительных методов менеджера — предпочтительный способ добавления функциональности на уровне таблицы в ваши модели. (Для функциональности на уровне строк — т.е. функций, которые действуют на одном экземпляре объекта модели — используйте Методы модели, а не пользовательские методы менеджера.)
Метод пользовательского менеджера может возвращать что угодно. Он не обязательно должен возвращать набор результатов.
Например, этот пользовательский менеджер предоставляет метод `get_all_with_extra_attribute()`, который возвращает список всех объектов `Book`, каждый с дополнительным атрибутом `extra_attribute`, который является результатом агрегирующего запроса:
from django.db import models
class PollManager(models.Manager):
def with_counts(self):
from django.db import connection
with connection.cursor() as 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()
В этом примере вы бы использовали `Book.objects.get_all_with_extra_attribute()` для получения этого списка объектов `Book` с атрибутами `extra_attribute`.
Ещё один момент, который следует отметить в этом примере, заключается в том, что методы менеджера могут получить доступ к `self` для получения класса модели, к которому они прикреплены.
Изменение начального набора результатов менеджера
Базовый менеджер возвращает все объекты в системе. Например, используя эту модель:
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()`. Метод `get_queryset()` должен возвращать набор результатов с требуемыми свойствами.
Например, следующая модель имеет два менеджера — один возвращает все объекты, а другой — только книги, написанные Роальдом Далем:
# 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.roald_dahl_books.all()` вернёт только те, что написаны Роальдом Далем.
Конечно, поскольку менеджер возвращает объект QuerySet, вы можете использовать методы `all()`, `filter()`, `first()` и все другие методы QuerySet. Следовательно, эти выражения являются корректными:
Book.dahl_objects.all() Book.dahl_objects.filter(title='Matilda') Book.dahl_objects.count()
Этот пример также показал ещё одну интересную технику: использование нескольких менеджеров для одной модели. Вы можете прикрепить к модели столько экземпляров менеджеров, сколько вам нужно. Это простой способ определения общих «фильтров» для ваших моделей.
Например:
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()
Этот пример позволяет запросить `Book.objects.all()`, `Book.roald_dahl_books.all()` и `Book.fantasy_books.all()`, получая предсказуемые результаты.
Менеджеры по умолчанию
-
Model._default_manager
Если вы используете пользовательские объекты менеджера, обратите внимание, что первый менеджер, который Django встречает (в порядке их определения в модели), имеет специальный статус. Django интерпретирует первый определённый менеджер в классе как «по умолчанию», и несколько частей Django (включая dumpdata) будут использовать этот менеджер исключительно для данной модели. В результате, стоит быть осторожным в выборе менеджера по умолчанию, чтобы избежать ситуации, когда переопределение менеджера по умолчанию приведёт к невозможности получить доступ к нужным объектам.
Вы можете указать пользовательский менеджер по умолчанию, используя Meta.default_manager_name.
Если вы пишете код, который должен обрабатывать неизвестную модель, например, в стороннем приложении, которое реализует универсальный вид, используйте этот менеджер (или _base_manager), а не полагайтесь на то, что у модели есть менеджер по умолчанию.
Базовые менеджеры
-
Model._base_manager
Использование менеджеров для доступа к связанным объектам
Если обычный базовый класс менеджера (django.db.models.Manager) не подходит для вашей ситуации, вы можете указать Django, какой класс использовать, установив Meta.base_manager_name.
Менеджеры не используются при запросе связанных моделей. Например, если модель `Question` (из руководства) имела поле `ForeignKey` и базовый менеджер, который отфильтровывает экземпляры с `is_deleted=True`, запрос `Question.objects.filter(is_deleted=False).values_list('id')` включит связанные варианты удалённых вопросов.
Не удаляйте результаты в этом типе подкласса менеджера
Этот менеджер используется для доступа к объектам, связанным с другими моделями. В этих ситуациях Django должен видеть все объекты для модели, которую он извлекает, чтобы можно было получить доступ к любому объекту, на который есть ссылка.
Если вы переопределяете метод `get_queryset()` и фильтруете строки, Django вернёт неверные результаты. Не делайте этого. Менеджер, который фильтрует результаты в этом типе, не подходит для использования в качестве базового менеджера.
Вызов пользовательских методов QuerySet из менеджера
Хотя большинство методов стандартного QuerySet доступны напрямую из менеджера, это верно только для дополнительных методов, определённых в пользовательском менеджере, если вы также реализуете их в базовом менеджере:
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()
Этот пример позволяет вызывать как `get_special_objects()` так и `get_all_with_extra_attribute()` напрямую из менеджера `Book.objects`.
Создание менеджера с методами QuerySet
Вместо подхода, описанного выше, требующего дублирования методов в менеджере и QuerySet, можно использовать QuerySet.as_manager() для создания экземпляра менеджера с копией методов пользовательского менеджера:
class Person(models.Model):
...
people = PersonQuerySet.as_manager()
Экземпляр менеджера, созданный с помощью QuerySet.as_manager(), будет практически идентичен менеджеру из предыдущего примера.
Не каждый метод QuerySet подходит для уровня менеджера; например, мы намеренно предотвращаем копирование метода QuerySet.delete() в класс менеджера.
Методы копируются в соответствии со следующими правилами:
- Открытые методы копируются по умолчанию.
- Приватные методы (начинающиеся с нижнего подчеркивания) по умолчанию не копируются.
- Методы с атрибутом `as_manager` равным `True` всегда копируются.
- Методы с атрибутом `as_manager` равным `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 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 автоматически создает менеджер
objects. - По умолчанию менеджер класса выбирается либо с помощью
Meta.default_manager_name, либо это первый объявленный менеджер модели, либо менеджер по умолчанию первой родительской модели.
Некоторые описанные выше особенности наследования не применяются, если вы не установите manager_inheritance_from_future = True в классе Meta модели. В старых версиях и если этот атрибут не установлен, наследование менеджеров зависит от типа наследования модели (Абстрактные базовые классы, Наследование с несколькими таблицами или Прокси-модели), особенно в отношении выбора менеджера по умолчанию.
Эти правила обеспечивают необходимую гибкость, если вы хотите установить набор настраиваемых менеджеров для группы моделей с помощью абстрактного базового класса, но при этом настроить менеджер по умолчанию. Например, предположим, что у вас есть такой базовый класс:
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/1.11/topics/db/managers/