Менеджеры
-
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.
Настраиваемые менеджеры
Вы можете использовать настраиваемый менеджер в конкретной модели, расширив базовый класс менеджера и инициализировав свой настраиваемый менеджер в вашей модели.
Существует две причины, по которым вы можете захотеть настроить менеджер: добавить дополнительные методы менеджера и/или изменить начальный набор результатов, который возвращает менеджер.
Добавление дополнительных методов менеджера
Добавление дополнительных методов менеджера — это предпочтительный способ добавления функциональности на уровне таблицы в ваши модели. (Для функциональности на уровне строки — т.е. функций, которые действуют на отдельный экземпляр объекта модели — используйте методы модели, а не настраиваемые методы менеджера.)
Метод настраиваемого менеджера может возвращать что угодно. Он не обязан возвращать объект QuerySet.
Например, этот настраиваемый менеджер предлагает метод top_ten_by_value, который возвращает список всех объектов, каждый из которых имеет дополнительный атрибут value, который является результатом агрегирующего запроса:
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()
В этом примере вы используете MyModel.my_manager.top_ten_by_value(), чтобы получить этот список объектов MyModel с атрибутами value.
Еще одна важная деталь этого примера заключается в том, что методы настраиваемых менеджеров могут получить доступ к классу модели, к которому они прикреплены.
Изменение начального набора результатов менеджера
Базовый набор результатов менеджера возвращает все объекты в системе. Например, используя эту модель:
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() должен возвращать объект 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.my_manager.all() вернёт только книги, написанные Роальдом Далем.
Конечно, поскольку метод get_queryset() возвращает объект QuerySet, вы можете использовать методы all(), filter() и все остальные методы 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().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()
Этот пример позволяет запросить Book.objects.all(), Book.special_manager.all() и Book.another_manager.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=True).related_questions.all(), будет включать связанные вопросы, даже если они были удалены.
Не удаляйте результаты в этом типе подклассов менеджеров
Этот менеджер используется для доступа к объектам, связанным с какой-либо другой моделью. В таких случаях 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()
Этот пример позволяет вызывать как MyModel.my_manager.top_ten_by_value(), так и MyModel.my_manager.all() напрямую из менеджера MyModel.my_manager.
Создание менеджера с методами QuerySet
Вместо вышеупомянутого подхода, который требует дублирования методов как в менеджере, так и в QuerySet, QuerySet.as_manager() может использоваться для создания экземпляра менеджера с копией методов настраиваемого менеджера:
class Person(models.Model):
...
people = PersonQuerySet.as_manager()
Экземпляр менеджера, созданный с помощью QuerySet.as_manager(), будет практически идентичен менеджеру из предыдущего примера.
Не каждый метод QuerySet имеет смысл на уровне менеджера; например, мы намеренно предотвращаем копирование метода QuerySet.delete() в класс менеджера.
Методы копируются в соответствии со следующими правилами:
- Публичные методы копируются по умолчанию.
- Приватные методы (начинающиеся с нижнего подчеркивания) по умолчанию не копируются.
- Методы с атрибутом
_copy_on_as_manager, установленным вTrue, всегда копируются. - Методы с атрибутом
_skip_on_as_manager, установленным в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(), которая возвращает подкласс вашего базового менеджера с копией методов настраиваемого 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, либо это первый объявленный менеджер модели, либо менеджер по умолчанию первой родительской модели.
Эти правила обеспечивают необходимую гибкость, если вы хотите установить набор пользовательских менеджеров для группы моделей через абстрактный базовый класс, но при этом настроить менеджер по умолчанию. Например, предположим, что у вас есть такой базовый класс:
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/2.1/topics/db/managers/