Менеджеры¶
-
class
Manager
[исходный код]¶
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
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()
В этом примере вы должны использовать 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().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().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()
Этот пример позволяет вам запрашивать Person.authors.all()
, Person.editors.all()` и ``Person.people.all()
, что дает предсказуемые результаты.
Менеджеры по умолчанию¶
-
Model.
_default_manager
¶
Если вы используете настраиваемые объекты Manager
, обратите внимание, что первые обнаруженные Django объекты Manager
(в том порядке, в котором они определены в модели) имеют особый статус. Django интерпретирует первый менеджер, определенный в классе, как менеджер по умолчанию, и некоторые части Django (включая dumpdata
) будут использовать этот менеджер исключительно для этой модели. В результате рекомендуется быть осторожным при выборе диспетчера по умолчанию, чтобы избежать ситуации, когда переопределение get_queryset()
приведет к невозможности получить объекты, с которыми вы хотите работать.
Вы можете указать собственный менеджер по умолчанию, используя Meta.default_manager_name
.
Если вы пишете код, который должен обрабатывать неизвестную модель, например, в стороннем приложении, реализующем общее представление, используйте этот менеджер (или _base_manager
) вместо того, чтобы предполагать, что модель имеет менеджер объектов.
Базовые менеджеры¶
-
Model.
_base_manager
¶
Не отфильтровывайте результаты в этом типе подкласса менеджера¶
Этот менеджер используется для доступа к объектам, относящимся к какой-либо другой модели. В таких ситуациях Django должен иметь возможность видеть все объекты для модели, которую он извлекает, чтобы можно было получить всё, на что имеется ссылка.
Если вы переопределите метод get_queryset()
и отфильтруете все строки, Django вернет неверные результаты. Не делай этого. Менеджер, который фильтрует результаты в get_queryset()
, не подходит для использования в качестве базового менеджера.
Вызов кастомных методов 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 автоматически создает менеджер объектов.
- Менеджер по умолчанию для класса - это либо тот, который выбран с помощью
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
по умолчанию. Менеджер объектов по-прежнему доступен, поскольку он унаследован. Он просто не используется по умолчанию.
Наконец, для этого примера предположим, что вы хотите добавить дополнительных менеджеров в дочерний класс, но по-прежнему используете значение по умолчанию из 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
быть скопировано.