Spec-Zone.ru › Django 1.11

Менеджеры

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 использует экземпляр класса менеджера `objects`, когда обращается к связанным объектам (например, `ForeignKey`), а не менеджер `objects` связанного объекта. Это необходимо, чтобы Django мог получить связанный объект, даже если он в противном случае был бы отфильтрован (и, следовательно, был бы недоступен) менеджером по умолчанию.

Если обычный базовый класс менеджера (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 обрабатывает настраиваемые менеджеры и наследование моделей:

  1. Менеджеры из базовых классов всегда наследуются дочерним классом, используя обычный порядок разрешения имен Python (имена в дочернем классе переопределяют все другие; затем следуют имена в первом родительском классе и так далее).
  2. Если менеджеры не объявлены в модели и/или ее родителях, Django автоматически создает менеджер objects.
  3. По умолчанию менеджер класса выбирается либо с помощью Meta.default_manager_name, либо это первый объявленный менеджер модели, либо менеджер по умолчанию первой родительской модели.
Изменено в Django 1.10:

Некоторые описанные выше особенности наследования не применяются, если вы не установите 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/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API