Spec-Zone.ru › Django 2.0

Менеджеры

class Manager [source]

Интерфейс Manager, через который к моделям Django предоставляются операции запроса к базе данных. Для каждой модели в приложении Django существует хотя бы один Manager.

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

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

По умолчанию Django добавляет менеджер с именем 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 и/или изменение исходного 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 для получения класса модели, к которому они прикреплены.

Изменение исходного набора результатов менеджера

Базовый набор результатов 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() вернёт все книги в базе данных.

Вы можете переопределить базовый набор результатов 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, обратите внимание, что первый Manager менеджер, который встречает Django (в порядке их определения в модели), имеет особый статус. Django интерпретирует первый Manager менеджер, определённый в классе, как «по умолчанию» Manager и несколько частей Django (включая dumpdata) будут использовать этот Manager исключительно для этой модели. Поэтому важно быть внимательным при выборе менеджера по умолчанию, чтобы избежать ситуации, когда переопределение get_queryset() приведёт к невозможности получения нужных вам объектов.

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

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

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

Model._base_manager

Использование менеджеров для доступа к связанным объектам

По умолчанию Django использует экземпляр класса менеджера Model._base_manager, когда обращается к связанным объектам (т.е. choice.question), а не _default_manager связанного объекта. Это необходимо, чтобы Django мог получить связанный объект, даже если бы он в противном случае был отфильтрован (и, следовательно, был недоступен) менеджером по умолчанию.

Если обычный базовый класс менеджера (django.db.models.Manager) не подходит для вашей ситуации, вы можете указать Django, какой класс использовать, задав Meta.base_manager_name.

Базовые менеджеры не используются при запросах к связанным моделям. Например, если модель Question из учебника имела поле deleted и базовый менеджер, который отфильтровывает экземпляры с deleted=True, запрос типа Choice.objects.filter(question__name__startswith='What') будет включать связанные варианты удалённых вопросов.

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

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

Если вы переопределите метод get_queryset() и отфильтруете какие-либо строки, Django вернёт неверные результаты. Не делайте этого. Менеджер, фильтрующий результаты в get_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, так и в Manager, QuerySet.as_manager() может быть использовано для создания экземпляра Manager с копией методов пользовательского менеджера QuerySet:

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

Экземпляр Manager , созданный с помощью QuerySet.as_manager(), будет практически идентичен менеджеру PersonManager из предыдущего примера.

Не каждый метод QuerySet набора результатов имеет смысл на уровне менеджера; например, мы намеренно предотвращаем копирование метода 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 обрабатывает пользовательские менеджеры и наследование моделей:

  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 абстрактной модели.

Вопросы реализации

Какие бы функции вы ни добавили в свой пользовательский Manager, должен быть возможен поверхностный (shallow) копирование экземпляра 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.0/topics/db/managers/

Spec-Zone.ru

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