Менеджеры
-
class Manager[source]
Менеджер — это интерфейс, через который выполняются операции запросов к базе данных для моделей Django. По крайней мере, один менеджер существует для каждой модели в приложении Django.
Работа с классами менеджеров описана в Создание запросов; данный документ подробно рассматривает параметры модели, которые настраивают поведение менеджеров.
Имена менеджеров
По умолчанию Django добавляет менеджер с именем objects к каждому классу модели Django. Однако, если вы хотите использовать objects как имя поля или если вы хотите использовать имя, отличное от objects для менеджера, вы можете переименовать его на уровне модели. Для переименования менеджера для данного класса определите атрибут класса типа str в этой модели. Например:
from django.db import models
class Person(models.Model):
#...
people = models.Manager()
Используя эту модель, обращение к book_manager.all() сгенерирует исключение AttributeError, но book_manager.get_books_by_author() вернёт список всех объектов Book.
Пользовательские менеджеры
Вы можете использовать пользовательский менеджер в конкретной модели, расширив базовый класс менеджера и инициализировав свой пользовательский менеджер в вашей модели.
Существуют две причины для настройки пользовательского менеджера: добавление дополнительных методов менеджера и/или изменение исходного набора результатов, который возвращает менеджер.
Добавление дополнительных методов менеджера
Добавление дополнительных методов менеджера — предпочтительный способ добавления функциональности на уровне таблицы в ваши модели. (Для функциональности на уровне строки — т. е. функций, которые действуют на один экземпляр объекта модели — используйте Методы модели, а не пользовательские методы менеджера.)
Метод пользовательского менеджера может возвращать что угодно. Он не обязательно должен возвращать набор результатов.
Например, этот пользовательский менеджер предлагает метод get_top_books(), который возвращает список всех объектов Book, каждый с дополнительным атрибутом average_rating, являющимся результатом агрегированного запроса:
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 с атрибутом average_rating вы бы использовали Book.objects.get_top_books().
Ещё одна важная особенность этого примера заключается в том, что методы пользовательских менеджеров могут получить доступ к 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() вернёт только те, которые написаны Роальдом Далем.
Поскольку get_queryset() возвращает объект набора результатов, вы можете использовать all(), filter() и все другие методы набора результатов над ним. Следовательно, эти операторы допустимы:
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.all().select_related('answer'), включал бы связанные ответы, даже если вопросы были удалены.
Не фильтруйте результаты в этом подклассе менеджера
Этот менеджер используется для доступа к объектам, связанным с другой моделью. В таких ситуациях 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()
Этот пример позволяет вызвать как get_top_books(), так и get_books_by_author() напрямую из менеджера Book.objects.
Создание менеджера с методами набора запросов
Вместо подхода, описанного выше, который требует дублирования методов как в менеджере, так и в наборе запросов, QuerySet.as_manager() можно использовать для создания экземпляра менеджера с копией методов пользовательского менеджера.
class Person(models.Model):
...
people = PersonQuerySet.as_manager()
Экземпляр менеджера, созданный с помощью QuerySet.as_manager(), будет практически идентичен менеджеру из предыдущего примера.
Не каждый метод набора запросов имеет смысл на уровне менеджера; например, мы намеренно предотвращаем копирование метода QuerySet.delete() в класс менеджера.
Методы копируются в соответствии со следующими правилами:
- Публичные методы копируются по умолчанию.
- Приватные методы (начинающиеся с нижнего подчёркивания) не копируются по умолчанию.
- Методы с атрибутом
__copy__, установленным вTrue, всегда копируются. - Методы с атрибутом
__nocopy__, установленным в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(), которая возвращает подкласс базового менеджера с копией методов пользовательского менеджера.
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, который управляет состоянием объекта, вы должны убедиться, что вы не повлияете на возможность копирования вашего объекта %%%CODE_BLOCK_147%%.
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/1.10/topics/db/managers/