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

⏱ 25 мин · 🧩 заданий: 5

Меню категорий

Дерево категорий должно быть в боковой панели на каждой странице. Сделаем тег-включение.

modules/blog/templatetags/blog_tags.py (не забудьте __init__.py в папке templatetags):

from django import template

from modules.blog.models import Category

register = template.Library()


@register.inclusion_tag("includes/categories_menu.html")
def popular_categories():
    return {"categories": Category.objects.all()}

Тег recursetree

django-mptt даёт тег, который рекурсивно обходит дерево:

templates/includes/categories_menu.html:

{% load mptt_tags %}
<div class="card mb-4">
  <div class="card-header">Категории</div>
  <ul class="list-group list-group-flush">
    {% recursetree categories %}
      <li class="list-group-item">
        <a href="{{ node.get_absolute_url }}">{{ node.title }}</a>
        {% if not node.is_leaf_node %}
          <ul class="list-unstyled ms-3">{{ children }}</ul>
        {% endif %}
      </li>
    {% endrecursetree %}
  </ul>
</div>
  • node — текущая категория;
  • {{ children }} — рекурсивно отрисованные дочерние узлы;
  • дерево строится из одного запроса благодаря полям lft/rght/level.

Альтернатива — тег {% full_tree_for_model blog.Category as categories %} и фильтр tree_info.

Пагинация

В ListView — один атрибут:

class ArticleListView(ListView):
    ...
    paginate_by = 4

Фрагмент templates/includes/pagination.html:

{% if is_paginated %}
  <nav>
    <ul class="pagination">
      {% if page_obj.has_previous %}
        <li class="page-item"><a class="page-link" href="?page=1">Первая</a></li>
        <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">Предыдущая</a></li>
      {% endif %}

      {% for num in page_obj.paginator.page_range %}
        {% if num == page_obj.number %}
          <li class="page-item active"><span class="page-link">{{ num }}</span></li>
        {% elif num > page_obj.number|add:"-3" and num < page_obj.number|add:"3" %}
          <li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
        {% endif %}
      {% endfor %}

      {% if page_obj.has_next %}
        <li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}">Следующая</a></li>
        <li class="page-item"><a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Последняя</a></li>
      {% endif %}
    </ul>
  </nav>
{% endif %}
  • is_paginated — есть ли больше одной страницы;
  • выводим только ближайшие номера (±2 от текущей) — с фильтром add.

В articles_list.html после цикла: {% include "includes/pagination.html" %}.

Bootstrap 5

Скачайте Bootstrap и положите в templates/src/ (это STATICFILES_DIRS) или подключите с CDN. templates/main.html:

{% load static blog_tags %}
<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>{{ title }}</title>
  <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
  <link rel="stylesheet" href="{% static 'custom/css/style.css' %}">
</head>
<body>
  {% include "header.html" %}
  <div class="container my-4">
    <div class="row">
      <div class="col-md-8">
        {% block content %}{% endblock %}
      </div>
      <div class="col-md-4">
        {% popular_categories %}
      </div>
    </div>
  </div>
  {% include "footer.html" %}
  <script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
</body>
</html>

templates/header.html — navbar с логотипом, ссылкой на главную и (позже) кнопками входа и профиля.

Итог

  • Меню категорий — тег-включение + recursetree из mptt_tags.
  • Пагинация в ListView — paginate_by, в шаблоне — page_obj и is_paginated.
  • Общие блоки (шапка, подвал, пагинация, меню) — во фрагментах include.
  • Bootstrap подключаем как статику из STATICFILES_DIRS или с CDN.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Выведите дерево категорий тегом recursetree (django-mptt): для каждого узла <li> со ссылкой <a href="{{ node.get_absolute_url }}">{{ node.title }}</a>, а если узел не лист — вложенный <ul> с {{ children }}.

Задание 2 Написать код

Напишите тег-включение popular_categories (шаблон includes/categories_menu.html), который возвращает контекст {"categories": Category.objects.all()}. Объект библиотеки — register.

Задание 3 Один вариант ответа

Почему recursetree выполняет всего один запрос к базе для всего дерева?

Задание 4 Написать код

Напишите ссылку «Предыдущая» для пагинации ListView (Bootstrap): если page_obj.has_previous, выведите <li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}">Предыдущая</a></li>.

Задание 5 Один вариант ответа

Где в шаблоне ListView с paginate_by проверить, нужна ли пагинация вообще?

Войдите, чтобы отмечать пройденные уроки.