Django 5 для начинающих

Прогресс по курсу:  9/1004

5.4 Работа с наборами запросов QuerySet и менеджерами
3 из 15 шагов пройдено
0 из 56 баллов  получено

Когда вычисляются наборы запросов QuerySet

Создание набора запросов QuerySet не требует каких-либо действий с базой данных до тех пор, пока он не будет вычислен.

Наборы запросов обычно возвращают еще один не вычисленный набор запросов.

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

При вычислении набора запросов он конвертируется в запрос на языке SQL к базе данных.

Наборы запросов QuerySet вычисляются только в следующих ниже случаях:

  • при первом их прокручивании в цикле;

  • при их нарезке, например Post.objects.all()[:3];

  • при их консервации в поток байтов или кешировании;

  • при вызове на них функций repr() или len();

  • при вызове на них функции list() в явной форме;

  • при их проверке в операциях bool(), or, and, if.

 

Создание модельных менеджеров

По умолчанию в каждой модели используется менеджер objects. Этот менеджер извлекает все объекты из базы данных. Однако имеется возможность определять конкретно-прикладные модельные менеджеры.

Давайте создадим конкретно-прикладной менеджер,чтобы извлекать все посты, имеющие статус PUBLISHED.

Есть два способа добавлять или адаптировать модельные менеджеры под конкретно-прикладную задачу:
можно добавлять дополнительные методы менеджера в существующий менеджер,
либо создавать новый менеджер, видоизменив изначальный набор запросов QuerySet, возвращаемый менеджером.

Первый метод предоставляет обозначение набора запросов в виде Post.objects.my_manager(), а второй предоставляет обозначение набора запросов в виде Post.my_manager.all().

Мы выберем второй метод, чтобы реализовать менеджер, который позволит извлекать посты, используя обозначение Post.published.all().

Отредактируйте файл models.py приложения blog, добавив конкретно-прикладной менеджер, как показано ниже:

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:
        ordering = ['-publish']
        indexes = [
            models.Index(fields=['-publish']),
        ]

    def __str__(self):
        return self.title

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

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

Если менеджер в модели не определен, то Django автоматически создает для нее стандартный менеджер objects.

Если в своей модели вы объявляете какие-либо менеджеры, но также хотите сохранить менеджер objects, то вы должны добавить его в свою модель явным образом.

В приведенном выше исходном коде мы добавили в модель Post стандартный менеджер objects и конкретно-прикладной менеджер published.

Метод get_queryset() менеджера возвращает набор запросов QuerySet, который будет исполнен. Мы переопределили этот метод, чтобы сформировать конкретно-прикладной набор запросов QuerySet, фильтрующий посты по их статусу и возвращающий поочередный набор запросов QuerySet, содержащий посты только со статусом PUBLISHED.

Теперь, когда мы определили для модели Post конкретно-прикладной менеджер, давайте его протестируем!
Следующей ниже командой снова запустите сервер разработки из командной оболочки:

python manage.py shell

Теперь можно импортировать модель Post и извлечь все опубликованные посты, заголовки которых начинаются с Как, исполнив следующий ниже набор запросов QuerySet:

from blog.models import Post
Post.published.filter(title__startswith='Как')

Для того чтобы получить результаты этого набора запросов, проверьте, чтобы поле status было равным значению PUBLISHED в объекте Post, поле title которого начинается со слова Как:


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий

Подскажите пожалуйста, я все првильно понял:

  1. объект models.Manager() - это объект как и многие другие, можно проводить аналогию например с файловыми объектами. (и он вызываемый)
  2. objects - это создаваемый по умолчанию экземпляр класса models.Manager() который создает результат работы - объект QuerySet . Но Мы можем созать произвольное количество с произвольными именами экземпляров класса.
  3. метод get_queryset() - это метод прописанный в магическом методе __call__()  - экземпляр объекта models.Manager() , является вызываемым. В конспекте это метод переопределяется для наледника models.Manager , класса PublishedManager .

Подскажите, я правильно резюмировал Конспект?

@Aleksandr_Bogatyrev, По первому и второму вопросу вы правильно поняли, кроме последнего вопроса. Метод get_queryset() - это метод класса BaseManager(), от которого идет наследование всех менеджеров. И в нашем менеджере, мы его переопределяем.

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

Насколько я понял без него можно спокойно обойтись, и может быть он необходим в тех ситуациях, когда нам необходимо часто обращаться по каким-либо полям в таблице и отталкиваясь от них уже формировать вывод на странице, тем самым сократив дальнейший код?

Условно в шаблонах  нам всегда надо будет выводить только опубликованные записи и если бы у нас были отдельные страницы под категории, то в фильтрах необходимо было бы прописывать "лапшу" из необходимых полей, а так мы можем как минимум отказаться от status?

@Нарбеков_Марсель, да, всё правильно понимаете, в первую очередь удобство использования.

Да, у меня почему-то тоже 

AttributeError: type object 'Post' has no attribute 'published'

Хотя у всех постов статус Опубликовано.

Вот мой код

from django.db import models

from django.utils import timezone

from django.contrib.auth.models import User

 

# Create your models here.

class PublishedManager(models.Manager):

    def get_queryset(self) -> models.QuerySet:

        return super().get_queryset()\

            .filter(status=Post.Status.PUBLISHED)


 

class Post(models.Model):

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

    published = PublishedManager() #новый конкретно-прикладной менеджер

   

    class Status(models.TextChoices):

        DRAFT = 'DF', 'Draft'

        PUBLISHED = 'PB', 'Published'

 

    title = models.CharField(max_length=250)

    slug = models.SlugField(max_length=100, unique=True)

    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')

    body = models.TextField()

 

    publish = models.DateTimeField(default=timezone.now) #поле дата публикации

    created = models.DateTimeField(auto_now_add=True) #поле дата создания

    updated = models.DateTimeField(auto_now=True) #поле дата обновления

    status = models.CharField(max_length=2, choices=Status.choices, default = Status.DRAFT)

 

   

 

    class Meta:

        ordering = ['-publish']

        indexes = [

            models.Index(fields=['-publish'])

        ]

 

    def __str__(self) -> str:

        return self.title

@Павел_Кирсанов, загрузите архив проекта по ссылке https://mega.nz/filerequest/rANtUqzWHQ4, посмотрим в чём проблема.

@Павел_Кирсанов, у меня ваш проект работает без ошибок:

@Павел_Кирсанов, попробуйте удалить директорию __pycache__ в директории приложения.

Изменен Дмитрий Селезнев

@Дмитрий_Селезнев, спасибо, а миграции применять нужно при таких изменениях?

@Павел_Кирсанов, при добавлении менеджера миграции не нужны.

Вижу у многих вызывает вопросы ниже приведенный кусок конспекта. У меня тоже была проблема, как выяснилось из-за отсутствия в базе данных записи (поста) со статусом Published, а все потому, что при создании данного поста в админ панели из степа 5.3 там не было явно указано как заполнять поля и что статус при создании надо выставлять Published.

Предлагаю это явно указать в 5.3 так как это совсем не очевидно, соответственно и данная проблема на этом степе будет снята.

Изменен Максим Михеев

@Максим_Михеев, Спасибо, добавил в 5.3 упоминание об этом.

У меня при данном менеджере published = PublishedManager() в shell при выполнении queryset Post.published.filter(title__startswith='Как'), выполняется одна и та же фильтрация и при Post.status='PB' и при Post.status='PB'. Ответ <QuerySet [<Post: Как работает Django?>]>. Получается менеджер published не работает? В чем может быть причина?

@Dmitrii_Novozhilov, Если бы у вас не работал менеджер, то и обратится через Post.published.filter(title__startswith='Как') вы бы не смогли. Вам нужно было бы обращаться как Post.objects.filter(title__startswith='Как')

Можете уточнить, через запрос Post.status='PB' вы пытаетесь изменить статус или что? Просто не совсем понял фразу - "и при Post.status='PB' и при Post.status='PB'"

Попробуйте в админ панели изменить статус у этого поста, и снова выполнить эту фильтрацию. Данный пост не должен будет вывестись.

@Илья_Перминов, Немного некорректно выразился с Post.status='PB',  ниже пример как я обращался и менял статус через shell.

В админ-панели стоит DF, ниже выкопировка из shell.

>>> from blog.models import Post
>>> Post.published.filter(title__startswith='Как')
<QuerySet [<Post: Как работает Django?>]>
>>> post = Post.objects.get(id=1)                  
>>> post.title
'Как работает Django?'
>>> post.status
'DF'

Это код из модуля.

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

class Post(models.Model):
    objects = models.Manager()
    published = PublishedManager()
    class Status(models.TextChoices):
        DRAFT = 'DF', 'Draft'
        PUBLISHED = 'PB', 'Published'
    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_post')
    body = models.TextField()
    publish = models.DateTimeField(default=timezone.now)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    status = models.CharField(max_length=2, choices=Status.choices, default=Status.DRAFT)

@Dmitrii_Novozhilov, У вас ошибка, метод у менеджера должен называться get_queryset

Здравствуйте, у меня пишет type object 'Post' has no attribute 'published', хоть я всё добавил всё определил , вот мой код:
 

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

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 Status(models.TextChoices):
        DRAFT = 'DF', 'Draft'
        PUBLISHED = 'PB', 'Published'

    title = models.CharField(max_length=250)
    slug = models.SlugField(max_length=250)
    author = models.ForeignKey(User,
                               on_delete=models.CASCADE,
                               related_name='blog_posts', null=True,
                               blank=True)
    text = models.TextField()

    publish = models.DateTimeField(default=timezone.now, null=True,
                                   blank=True)
    created = models.DateTimeField(auto_now_add=True, null=True,
                                   blank=True)
    updated = models.DateTimeField(auto_now=True, null=True,
                                   blank=True)
    status = models.CharField(max_length=2,
                              choices=Status.choices,
                              default=Status.DRAFT, null=True,
                              blank=True)

    class Meta:
        ordering = ['-publish']
        indexes = [
            models.Index(fields=['-publish']),
        ]

    def __str__(self):
        return self.title

@Пирогов_Владислав_Дмитриевич, у меня работает ваш код. Можете загрузить ваш проект https://mega.nz/filerequest/rANtUqzWHQ4, разберемся где ошибка. 

@Илья_Перминов, у меня также было, пока я не поменял статус у созданных постов с draft на published