Менеджеры

класс Manager

Обработчик (объект Manager ) - это интерфейс, через который операции запросов к базе данных становятся доступными для моделей Django. В Manager приложении Django есть как минимум один для каждой модели.

Работа классов Manager описана в разделе Создание запросов ; в этом документе конкретно обсуждаются параметры шаблона, которые настраивают поведение менеджера.

Имена менеджеров

По умолчанию Django добавляет именованный обработчик objects к каждому классу модели Django. Однако, если вы хотите использовать objects в качестве имени поля или хотите присвоить обработчику другое имя objects , вы можете переименовать его на уровне модели. Чтобы переименовать обработчик для данного класса, определите атрибут класса типа models.Manager() в модели. Например :

from django.db import models

class Person(models.Model):
    #...
    people = models.Manager()

Использование этого шаблона примера Person.objects вызовет исключение AttributeError , но Person.people.all() фактически перечислит все объекты Person .

Пользовательские менеджеры ¶

Вы можете использовать собственный обработчик в конкретной модели, расширив Manager базовый класс и создав свой собственный экземпляр Manager в своей модели.

Настроить обработчик нужно по двум причинам: добавить к нему дополнительные методы или изменить QuerySet исходный объект , возвращаемый обработчиком.

Добавление дополнительных методов-обработчиков

Добавление дополнительных методов-обработчиков является предпочтительным способом добавления к моделям функциональности на уровне таблицы (для функциональности на уровне строк, то есть функций, которые действуют на один экземпляр объекта модели используйте методы модели , а не методы настраиваемого обработчика).

Пользовательский метод обработчика может возвращать все, что вы хотите, ему не обязательно возвращать объект QuerySet .

Например, этот метод настраиваемого обработчика предлагает метод, 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 .

Еще одна вещь, на которую следует обратить внимание в этом примере, заключается в том, что методы-обработчики имеют доступ self.model для получения класса модели, к которому они привязаны.

Смена QuerySet инициалов менеджера

QuerySet База менеджера возвращает все объекты в системе. Например, используя эту модель:

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.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.dahl_objects.all() возвращаются только те, которые были написаны Роальдом Далем.

Потому что get_queryset() возвращает QuerySet объект, вы можете использовать filter() , exclude() и все другие 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()

Этот пример позволяет выполнять запросы Person.authors.all() , Person.editors.all() и Person.people.all() зная , чего ожидать в плане результатов.

Обработчики по умолчанию

Model._default_manager

Если вы используете Manager настраиваемые объекты , имейте в виду, что первый Manager обнаруженный Django (в том порядке, в котором они были определены в модели) получает особый статус. Django интерпретирует первый обработчик, определенный в классе, как обработчик по умолчанию, и некоторые части Django (включая dumpdata ) используют этот обработчик исключительно для рассматриваемой модели. В результате желательно тщательно выбирать обработчик по умолчанию, чтобы избежать ситуации, когда перегрузка get_queryset() приводит к невозможности получить объекты, с которыми вам нужно работать.

Вы можете определить собственный обработчик по умолчанию, используя Meta.default_manager_name .

Например, если вы пишете код, который должен обрабатывать неизвестную модель в стороннем приложении, которое реализует универсальное представление, используйте этот обработчик (или _base_manager ), а не предполагайте, что у модели есть обработчик objects .

Базовые менеджеры ¶

Model._base_manager

Никогда не фильтруйте результаты в этом типе подкласса обработчика

Этот менеджер используется для доступа к связанным объектам из другой модели. В этих ситуациях Django должен иметь возможность видеть все объекты модели, которые он извлекает, чтобы можно было получить все , на что есть ссылки.

Следовательно, не следует переопределять get_queryset() фильтрацию каких-либо строк. Если вы это сделаете, Django вернет неполные результаты.

Вызов кастомного метода QuerySet из менеджера

Хотя большинство методов QuerySet стандарта доступны напрямую из a 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 .

Создание объекта Manager методамиQuerySet

Вместо вышеуказанного подхода, который требует дублирования методов для обоих объектов QuerySet и Manager , QuerySet.as_manager() можно использовать для создания экземпляра Manager с копией методов QuerySet настраиваемого объекта :

class Person(models.Model):
    ...
    people = PersonQuerySet.as_manager()

Экземпляр Manager created by 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()

classmethodfrom_queryset ( queryset_class )

Для более продвинутого использования может быть желательно иметь как Manager пользовательский, так и QuerySet пользовательский. Вы можете сделать это по телефону , Manager.from_queryset() который возвращает подкласс от Manager основания с методами копирования QuerySet пользовательских:

class CustomManager(models.Manager):
    def manager_only_method(self):
        return

class CustomQuerySet(models.QuerySet):
    def manager_and_queryset_method(self):
        return

class MyModel(models.Model):
    objects = CustomManager.from_queryset(CustomQuerySet)()

Вы также можете сохранить сгенерированный класс в переменной:

MyManager = CustomManager.from_queryset(CustomQuerySet)

class MyModel(models.Model):
    objects = MyManager()

Пользовательские обработчики и наследование модели

Вот как Django обрабатывает пользовательские обработчики в контексте наследования модели :

  1. Обработчики базовых классов всегда наследуются дочерними классами с помощью обычных средств Python для определения порядка разрешения имен (имена дочерних классов имеют приоритет над именами своих родителей).
  2. Если в модели или ее родительских элементах обработчик не объявлен, Django автоматически создает обработчик objects .
  3. Менеджер класса по умолчанию - это либо тот, который выбран 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 абстрактную модель.

Детали реализации

Какие бы функциональные возможности ни были добавлены в настраиваемый менеджер, всегда должна быть возможность сделать легкую («неглубокую») копию одного из его экземпляров; то есть следующий код должен работать:

>>> import copy
>>> manager = MyManager()
>>> my_copy = copy.copy(manager)

Django делает легкие копии объектов-обработчиков во время определенных запросов; если ваш обработчик не может быть скопирован, эти запросы не будут выполнены.

Для большинства кастомных менеджеров это не проблема. Если вы просто добавляете простые методы в свой класс Manager , вы вряд ли предотвратите непреднамеренное копирование обработчиком. Однако, если вы переопределяете __getattr__ или другие частные методы объекта, Manager которые управляют состоянием объекта, убедитесь, что вы не изменяете возможность копирования вашего обработчика.

Copyright ©2020 All rights reserved