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

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


В этой статье по 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.

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

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

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

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

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

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

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

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