Django База [2023]: Фильтрация статей по категориям, вывод категорий в виде дерева MPTT в шаблоне 🌳 #11
В этой статье по Django 4.1 мы рассмотрим вывод статей по категориям, выведем категории в sidebar в виде MPTT дерева, а также добавим ссылки на категории, выведем их в sidebar.
Если вы хотите выразить благодарность автору сайта, статей и курса по Django, вы можете сделать это по ссылке ниже:
В прошлом уроке мы добавили bootstrap, поэтому мы сейчас поработаем над шаблоном, и далее добавим фильтрацию по категориям.
Работа с шаблоном
В папке templates создадим два файла: header.html, sidebar.html.
В main.html добавляем следующую разметку и подключаем header.html и sidebar.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
<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, и вывод наших категорий:
{% 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
{% 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, и добавляем следующий код:
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
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 для модели категорий
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.
Отлично, перейдем в категорию:
Давайте воспользуемся меню в sidebar:
Мы использовали в меню построение ссылки через метод get_absolute_url
. Можно пользоваться методом, либо использовать тег {% url 'articles_by_category' article.category.slug %}
, я предпочитаю создавать методы в модели и использовать их вместо {% url %}
.
На этом все. Мы настроили шаблон, добавили метод, вывели категории в виде дерева, и отсортировали статьи по категориям.