Django База [2023]: Фильтрация статей по категориям, вывод категорий в виде дерева MPTT в шаблоне 🌳 #11
Django

Django База [2023]: Фильтрация статей по категориям, вывод категорий в виде дерева MPTT в шаблоне 🌳 #11

Razilator

В этой статье по Django 4.1 мы рассмотрим вывод статей по категориям, выведем категории в sidebar в виде MPTT дерева, а также добавим ссылки на категории, выведем их в sidebar.

В прошлом уроке мы добавили bootstrap, поэтому мы сейчас поработаем над шаблоном, и далее добавим фильтрацию по категориям.

Работа с шаблоном

В папке templates создадим два файла: header.html, sidebar.html.

В main.html добавляем следующую разметку и подключаем header.html и sidebar.html:

templates/main.html
<!DOCTYPE html>
<html lang="ru">
<head>
    {% load static %}
    <meta charset="UTF-8">
    <title>{{ title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- INCLUDE CSS -->
    <link href="{% static 'bootstrap/css/bootstrap.min.css' %}" type="text/css" rel="stylesheet">
</head>
<body>
    <div class="container">
        {% include 'header.html' %}
        <div class="row">
            <div class="col-8">
            {% block content %}

            {% endblock %}
            </div>
            <div class="col-4">
                {% include 'sidebar.html' %}
            </div>
        </div>
    </div>
<script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>

Пояснение:

  • С помощью тегов {% include %} мы подключаем шаблоны, при этом добавив разметку из фреймворка bootstrap.

Теперь добавим следующий код в header.html

templates/header.html
<header>
    <div class="px-3 py-2 text-bg-dark">
      <div class="container">
        <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
          <a href="/" class="d-flex align-items-center my-2 my-lg-0 me-lg-auto text-white text-decoration-none">
            <svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap"><use xlink:href="#bootstrap"></use></svg>
          </a>

          <ul class="nav col-12 col-lg-auto my-2 justify-content-center my-md-0 text-small">
            <li>
              <a href="#" class="nav-link text-secondary">
                <svg class="bi d-block mx-auto mb-1" width="24" height="24"><use xlink:href="#home"></use></svg>
                Home
              </a>
            </li>
            <li>
              <a href="#" class="nav-link text-white">
                <svg class="bi d-block mx-auto mb-1" width="24" height="24"><use xlink:href="#speedometer2"></use></svg>
                Dashboard
              </a>
            </li>
            <li>
              <a href="#" class="nav-link text-white">
                <svg class="bi d-block mx-auto mb-1" width="24" height="24"><use xlink:href="#table"></use></svg>
                Orders
              </a>
            </li>
            <li>
              <a href="#" class="nav-link text-white">
                <svg class="bi d-block mx-auto mb-1" width="24" height="24"><use xlink:href="#grid"></use></svg>
                Products
              </a>
            </li>
            <li>
              <a href="#" class="nav-link text-white">
                <svg class="bi d-block mx-auto mb-1" width="24" height="24"><use xlink:href="#people-circle"></use></svg>
                Customers
              </a>
            </li>
          </ul>
        </div>
      </div>
    </div>
    <div class="px-3 py-2 border-bottom mb-3">
      <div class="container d-flex flex-wrap justify-content-center">
        <form class="col-12 col-lg-auto mb-2 mb-lg-0 me-lg-auto" role="search">
          <input type="search" class="form-control" placeholder="Search..." aria-label="Search">
        </form>

        <div class="text-end">
          <button type="button" class="btn btn-light text-dark me-2">Login</button>
          <button type="button" class="btn btn-primary">Sign-up</button>
        </div>
      </div>
    </div>
  </header>

И давайте добавим немного разметки в sidebar.html, и вывод наших категорий:

templates/sidebar.html
{% load mptt_tags %}

<div class="card">
    <div class="card-body">
      <h5 class="card-title">Категории</h5>
      {% full_tree_for_model blog.Category as categories %}
      <p class="card-text">
        <ul>
            {% recursetree categories %}
                <li>
                 <a href="{{ node.get_absolute_url }}">{{ node.title }}</a>
                </li>
            
                {% if not node.is_leaf_node %}<ul>{% endif %}
                    {{children}}
                {% if not node.is_leaf_node %}</ul>{% endif %}
            {% endrecursetree %}
        </ul>
      </p>
    </div>
  </div>

Пояснение:

  • Загружаем mptt теги в шаблон.
  • Подгружаем дерево модели Category.
  • Выводим все категории рекурсиво с вложенностью (children)

Отлично, предлагаю заодно сделать изменение в articles_list.html

templates/blog/articles_list.html
{% extends 'main.html' %}

{% block content %}
    {% for article in articles %}
    <div class="card mb-3">
        <div class="row">
            <div class="col-4">
                <img src="{{ article.thumbnail.url }}" class="card-img-top" alt="{{ article.title }}">
            </div>
            <div class="col-8">
                <div class="card-body">
                    <h5 class="card-title">{{ article.title }}</h5>
                    <div class="card-subtitle">{{ article.author.username }}</div>
                    <p class="card-text">{{ article.short_description }}</p>
                    <a href="{% url 'articles_by_category' article.category.slug %}" class="btn btn-primary">{{ article.category.title }}</a>
                  </div>
                </div>
            </div>
      </div>
    {% endfor %}
{% endblock %}

Пояснение:

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

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

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

blog/views.py
class ArticleByCategoryListView(ListView):
    model = Article
    template_name = 'blog/articles_list.html'
    context_object_name = 'articles'
    category = None

    def get_queryset(self):
        self.category = Category.objects.get(slug=self.kwargs['slug'])
        queryset = Article.objects.all().filter(category__slug=self.category.slug)
        return queryset

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

Пояснение:

  • template_name - наш шаблон, тот же что и у других представлений
  • category - переменная, по которой мы будем работать
  • context_object_name - словарь для перебирания в шаблоне
  • def get_queryset - метод обработки qs, здесь мы получаем категорию по определенному slug, а после мы фильтруем qs статей по категории и возвращаем qs.
  • def get_context_data - в этом методе передаем <title></title> категории

Добавление представления в urls.py

blog/urls.py
from django.urls import path

from .views import ArticleListView, ArticleDetailView, ArticleByCategoryListView

urlpatterns = [
    path('', ArticleListView.as_view(), name='home'),
    path('articles/<str:slug>/', ArticleDetailView.as_view(), name='articles_detail'),
    path('category/<str:slug>/', ArticleByCategoryListView.as_view(), name="articles_by_category"),
]

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

Добавление метода get_absolute_url для модели категорий

blog/models.py
class Category(MPTTModel):
    """
    Модель категорий с вложенностью
    """
    
    title = models.CharField(max_length=255, verbose_name='Название категории')
    
    # Поля модели... 
    
    # Методы модели...
    
    def get_absolute_url(self):
        return reverse('articles_by_category', kwargs={'slug': self.slug})

Отлично, теперь давайте проверим как все это работает, запустив наш сервер Django.

При наведении на категорию, мы видим ссылку на категорию
При наведении на категорию, мы видим ссылку на категорию

Отлично, перейдем в категорию:

Перешли в категорию, отработал title, а также мы получили 2 статьи из этой категории
Перешли в категорию, отработал title, а также мы получили 2 статьи из этой категории

Давайте воспользуемся меню в sidebar:

В меню мы вывели категории с помощью mptt тегов
В меню мы вывели категории с помощью mptt тегов

Мы использовали в меню построение ссылки через метод get_absolute_url. Можно пользоваться методом, либо использовать тег {% url 'articles_by_category' article.category.slug %}, я предпочитаю создавать методы в модели и использовать их вместо {% url %}.

Результат: одна статья из категории веб-фреймворки
Результат: одна статья из категории веб-фреймворки

На этом все. Мы настроили шаблон, добавили метод, вывели категории в виде дерева, и отсортировали статьи по категориям.

;