Spec-Zone.ru › Django 3.0

Менеджеры

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 менеджера

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

Вы можете переопределить базовый Manager менеджер QuerySet, переопределив метод 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 из менеджера

Хотя большинство методов стандартного 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 имеет смысл на уровне менеджера; например, мы намеренно предотвращаем копирование метода 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 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 на абстрактной модели.

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

Какие бы функции вы ни добавили в свой настраиваемый 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/3.0/topics/db/managers/

Spec-Zone.ru

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