Работа с наборами запросов QuerySet и менеджерами

⏱ 20 мин · 🧩 заданий: 5

Запросы к блогу в shell

Добавьте через админку несколько постов (часть — черновики, часть — опубликованные) и откройте консоль:

python manage.py shell
>>> from django.contrib.auth.models import User
>>> from blog.models import Post

>>> user = User.objects.get(username="admin")
>>> post = Post.objects.create(title="Ещё один пост", slug="eshhe-odin-post", body="Текст", author=user)

>>> Post.objects.all()
>>> Post.objects.filter(publish__year=2026)
>>> Post.objects.filter(publish__year=2026, author__username="admin")
>>> Post.objects.filter(status=Post.Status.PUBLISHED).exclude(title__startswith="Ещё")
>>> Post.objects.order_by("title")
>>> Post.objects.get(id=1).delete()

Всё это вы уже умеете из модуля про ORM — теперь применяем к реальной модели.

Проблема: везде нужен фильтр по статусу

На сайте читателям показываются только опубликованные посты. Значит, в каждом view придётся писать:

Post.objects.filter(status=Post.Status.PUBLISHED)

Забыли в одном месте — читатели видят черновики. Решение — собственный менеджер.

Менеджеры

Менеджер — объект, через который модель обращается к базе. objects — менеджер по умолчанию. Можно создать свой, переопределив get_queryset():

class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status=Post.Status.PUBLISHED)


class Post(models.Model):
    # ... поля ...

    objects = models.Manager()        # менеджер по умолчанию
    published = PublishedManager()    # наш менеджер

    class Meta:
        ...

Теперь:

Post.published.all()
Post.published.filter(title__startswith="Как")
Post.published.count()

Порядок и objects

Если в модели объявлен хотя бы один свой менеджер, Django не создаёт objects автоматически — объявите его явно. Первый объявленный менеджер становится менеджером по умолчанию (его использует, например, админка), поэтому objects ставим первым.

Методы менеджера и QuerySet

Кроме фильтрации по умолчанию, в менеджер можно добавить свои методы:

class PostQuerySet(models.QuerySet):
    def published(self):
        return self.filter(status=Post.Status.PUBLISHED)

    def by_author(self, username):
        return self.filter(author__username=username)


class Post(models.Model):
    ...
    objects = PostQuerySet.as_manager()
Post.objects.published().by_author("admin")   # методы можно соединять в цепочку!

Это ещё одно проявление принципа «толстые модели»: логика выборки живёт рядом с моделью, а не размазана по view.

Ещё раз о ленивости

posts = Post.published.all()                # запроса нет
posts = posts.filter(publish__year=2026)    # запроса нет
for post in posts:                          # SELECT ... — один раз
    print(post.title)
for post in posts:                          # повторно не запрашивает: результаты закэшированы
    print(post.slug)

QuerySet выполняется при: итерации, срезе с шагом, len(), list(), bool(), repr() (в shell), pickle. Результат кэшируется в объекте QuerySet.

Итог

  • Собственный менеджер инкапсулирует частые фильтры: Post.published.all().
  • Переопределяйте get_queryset(), вызывая super().
  • При объявлении своего менеджера явно добавьте objects = models.Manager() первым.
  • QuerySet.as_manager() позволяет создавать цепочки своих методов.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Напишите менеджер PublishedManager, который переопределяет get_queryset() и возвращает только посты со статусом Post.Status.PUBLISHED.

Задание 2 Написать код

Подключите к модели Post два менеджера: стандартный objects и published (экземпляр PublishedManager). Стандартный должен идти первым.

Задание 3 Один вариант ответа

Почему после добавления published = PublishedManager() нужно явно объявить objects = models.Manager()?

Задание 4 Написать код

Получите опубликованные посты автора с именем пользователя admin, в заголовке которых есть слово Django (без учёта регистра). Результат — в posts.

Задание 5 Один вариант ответа

Когда выполнится SQL-запрос в коде qs = Post.published.all(); qs = qs.filter(title__startswith="A"); print(qs.count())?

Войдите, чтобы отмечать пройденные уроки.