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

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

10.5 Вывод дерева категорий, пагинация, добавление Bootstrap 5
3 из 3 шагов пройдено

Представление для отображения записей по категориям

Переходим в файл views.py в нашем приложении blog, и добавим следующий код:

from django.views.generic import ListView, DetailView
from .models import Post, Category


class PostFromCategory(ListView):
    template_name = 'blog/post_list.html'
    context_object_name = 'posts'
    category = None

    def get_queryset(self):
        self.category = Category.objects.get(slug=self.kwargs['slug'])
        queryset = Post.objects.filter(category__slug=self.category.slug)
        if not queryset:
            sub_cat = Category.objects.filter(parent=self.category)
            queryset = Post.objects.filter(category__in=sub_cat)
        return queryset

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['title'] = f'Записи из категории: {self.category.title}'
        return context


Давайте еще раз разберем работу, мы наследуемся от ListView класса, это представление будет обрабатывать наш список объектов:

  • model - название нашей модели, Post.
  • template_name - По умолчанию ListView ищет шаблон с префиксом имени модели и суффиксом _list.html, если не установлено иное. Это можно переопределить, установив атрибут template_name. Мы будем использовать тот же шаблон, что и в классе PostListView.
  • context_object_name - Переопределим имя Queryset по умолчанию object_list, установив атрибут context_object_name. Это помогает иметь более удобное для работы имя. Мы будем использовать posts, так как в шаблоне post_list.html мы уже используем ее.
  • category - переменная, по которой мы будем работать
  • def get_queryset - метод обработки запросов, здесь мы получаем категорию по определенному slug, а после мы фильтруем запросы статей по категории и возвращаем QuerySet. Это работает только для дочерних категорий, если данный объект пустой(при переходе в родительскую категорию), то мы получаем все дочерние категории, и выводим все записи из них.
  • get_context_data - в этом методе передаем <title></title> категории в наш шаблон.


Следующим шагом, нам необходимо добавить в urls.py маршруты вывода записей из категорий.

from django.urls import path
from .views import PostListView, PostDetailView, PostFromCategory

urlpatterns = [
    path('', PostListView.as_view(), name='home'),
    path('post/<str:slug>/', PostDetailView.as_view(), name='post_detail'),
    path('category/<str:slug>/', PostFromCategory.as_view(), name="post_by_category"),
]


Теперь нам нужно ещё добавить метод в модель категорий Category:

 class Category(MPTTModel):

# поля модели

    def get_absolute_url(self):
        """
        Получаем прямую ссылку на категорию
        """
        return reverse('post_by_category', kwargs={'slug': self.slug})

    def __str__(self):
        """
        Возвращение заголовка статьи
        """
        return self.title

Данный метод позволяет получать прямую ссылку на категорию, без вызова {% url '' %}.

Давайте откроем шаблон post_list.html и добавим туда ссылки на категории:

{% extends 'main.html' %}

{% block content %}
    {% for post in posts %}
        <div class="card mb-3">
            <div class="row">
                <div class="col-4">
                    <img src="{{ post.thumbnail.url }}" class="card-img-top" alt="{{ post.title }}">
                </div>
                <div class="col-8">
                    <div class="card-body">
                        <h5 class="card-title">
                            <a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
                        </h5>
                        <p class="card-text">{{ post.description }}</p>
                        <small>Добавил {{ post.author.username }}, {{ post.create }},</small>
                        в категорию: <a href="{{ post.category.get_absolute_url }}">{{ post.category.title }}</a>
                    </div>
                </div>
            </div>
        </div>
    {% endfor %}
{% endblock %}

Мы использовали в модели построение ссылки через метод get_absolute_url.

Можно пользоваться методом, либо получать ссылку через <a href="{% url 'post_by_category' post.category.slug %}">{{ post.category.title }}</a>

Запустим сервер и перейдем на главную страницу блога:


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


Мы успешно сделали вывод записей по категориям, осталось добавить пагинацию к их выводу.


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

Не могу разобраться. Почему во вью PostFromCategory мы не переопределяем метод  get_queryset путем расширения родительского метода (как делалось ранее для get_context_data), а полностью перезаписываем его.
Т.е. не начинаем метод  get_queryset со строки типа:
queryset = super().get_queryset(*args, **kwargs) ?

@Евгений_Куликов, Потому что в super().get_queryset() будут находится все записи нашего блога, в том числе и не опубликованные. И нам проще сразу написать используя наш менеджер моделей:

queryset = Post.custom.filter(category__slug=self.category.slug)

Нежели вот так:

queryset = super().get_queryset().filter(status='published')
queryset = queryset.filter(category__slug=self.category.slug)

@Илья_Перминов, Посмотрев проект дальше, увидел что потом появляется кастомный менеджер PostManager() для получения опубликованных записей. Но в целом получается, что можно идти разными путями. Главное уметь понимать, какое решение будет лучше.

@Евгений_Куликов, ага, кстати соглашусь, что на данном этапе проще использовать:

queryset = super().get_queryset().filter(category__slug=self.category.slug)

Но в дальнейшем, после добавления менеджера модели, эта конструкция уже будет громоздкой.

Насколько я понимаю, если в модели переопределен метод get_queryset(), то указывать модель в атрибуте бессмысленно. Если его закомментировать, все работает точно так же

@ilya_kutaev, да, верно, исправил, спасибо.