Паджинатор

После запуска наш проект Yatube разрастётся, количество записей в лентах увеличится, и если вывести все записи на одной странице — сайтом пользоваться станет невозможно.
Пока что на страницы нашего проекта выводится ограниченное число записей. Даже если на сайте опубликовано несколько тысяч постов — пользователь сможет прочесть только одиннадцать из них.
Код view-функции для главной страницы сейчас выглядит примерно так:
Скопировать кодPYTHON
def index(request): latest = Post.objects.order_by('-pub_date')[:11] return render(request, 'index.html', {"posts": latest})
Проблема вывода большого числа постов решается разделением ленты на отдельные страницы с ограниченным числом записей на каждой. Для перехода между страницами добавляются специальные ссылки.
Список ссылок для постраничного перехода вам знаком, это стандартный элемент интерфейсов сатов. Такой компонент есть и во фреймворке Bootstrap:
image
Такую полосу можно достаточно просто реализовать самому.
Но писать такой код для каждого списка элементов на сайте было бы неправильно, надо автоматизировать эту задачу.
В Django эту проблему решает стандартный модуль Paginator: он «раскладывает» списки по отдельным страницам, выводя на каждую страницу требуемое количество элементов. Для названия этой системы в русском языке используют кальку с английского и называют её «паджинатор».
Скопировать кодPYTHON
>>> from django.core.paginator import Paginator # создаём тестовый список >>> items = ['Антон Чехов', 'Владимир Набоков', 'Лев Толстой', 'Марина Цветаева'] # создаём объект Paginator(object_list, per_page), зададим деление по два объекта на страницу >>> p = Paginator(items, 2) # свойство count показывает, сколько объектов в последовательности >>> p.count 4 # свойство num_pages показывает сколько страниц получится из списка # num_pages рассчитывается как len(items)/per_page >>> p.num_pages 2 # получаем объект с элементами для первой страницы >>> page1 = p.page(1) >>> page1 <Page 1 of 2> # получаем элементы для отображения на первой странице >>> page1.object_list ['Антон Чехов', 'Владимир Набоков'] # проверяем, есть ли следующие страницы после текущей: # надо ли отображать кнопку "Следующая страница" >>> page1.has_next() True # проверяем, есть ли страницы перед текущей: # надо ли отображать кнопку "Предыдущая страница" >>> page1.has_previous() False >>> page2 = p.page(2) >>> page2.object_list ['Лев Толстой', 'Марина Цветаева'] >>> page2.has_next() False >>> page2.has_previous() True # чтобы отобразить список с номерами доступных страниц # получим значение свойства page_range: # в нём хранятся данные типа range >>> type(p.page_range) <class 'range'> # выведем в консоль линейку с перечнем страниц >>> for n in p.page_range: ... print(f"<{n}> ", end="") ... <1> <2>
Объект Paginator получает на вход последовательность, разбивает ее на отдельные страницы и позволяет обратиться к ним индивидуально или получить полный список получившихся страниц.
При запросе данных из базы Django ORM тоже возвращает последовательность, и работать с ней можно точно так же, как со списком писателей, который мы только что создали в примере.
Скопировать кодPYTHON
>>> from posts.models import Post >>> posts = Paginator(Post.objects.order_by('-pub_date'), 2) # в переменную post_page передадим объект второй страницы паджинатора >>> post_page = posts.page(2) >>> post_page.object_list <QuerySet [<Post: 36>, <Post: 35>]>
Свойства объекта страницы post_page:
Методы .start_index() и .end_index() нужны для того, чтобы нумеровать элементы списка

Применение паджинатора

Обновим view-функцию главной страницы:
Скопировать кодPYHTON
from django.core.paginator import Paginator from django.shortcuts import render from .models import Post def index(request): post_list = Post.objects.order_by('-pub_date').all() paginator = Paginator(post_list, 10) # показывать по 10 записей на странице. page_number = request.GET.get('page') # переменная в URL с номером запрошенной страницы page = paginator.get_page(page_number) # получить записи с нужным смещением return render( request, 'index.html', {'page': page, 'paginator': paginator} )
Также надо обновить шаблон главной страницы чтобы работать не со списком posts, который использовался раньше, а с новым объектом page:
Скопировать кодHTML
{% extends "base.html" %} {% block title %} Последние обновления {% endblock %} {% block content %} <h1> Последние обновления на сайте<h1> {% for post in page %} ...Тут вывод списка записей... {% endfor %} {% if page.has_other_pages %} ...Тут код со списком страниц для листания... {% endif %} {% endblock %}
Остаётся вывести список для перехода по страницам. Таких страниц у нас множество, и чтобы много раз не повторять один и тот же код — напишем виджет, который будем включать в те шаблоны, где он необходим.
В директории templates создайте файл paginator.html и добавьте в него такой код:
Скопировать кодHTML
<nav aria-label="Переключение страниц"> <ul class="pagination"> {% if items.has_previous %} <li class="page-item"><a class="page-link" href="?page={{ items.previous_page_number }}">&laquo; Предыдущая</a></li> {% else %} <li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">&laquo; Предыдущая</a></li> {% endif %} {% for i in paginator.page_range %} {% if items.number == i %} <li class="page-item active"><span class="page-link">{{ i }} <span class="sr-only">(текущая)</span></span></li> {% else %} <li class="page-item"><a class="page-link" href="?page={{ i }}">{{ i }}</a></li> {% endif %} {% endfor %} {% if items.has_next %} <li class="page-item"><a class="page-link" href="?page={{ items.next_page_number }}">Следующая &raquo;</a></li> {% else %} <li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">Следующая &raquo;</a></li> {% endif %} </ul> </nav>
И обновите код файла index.html:
Скопировать кодHTML
{% extends "base.html" %} {% block title %} Последние обновления {% endblock %} {% block content %} <h1> Последние обновления на сайте<h1> {% for post in page %} ...Тут вывод списка записей... {% endfor %} {% if page.has_other_pages %} {% include "paginator.html" with items=page paginator=paginator %} {% endif %} **** {% endblock %}
Теперь в любой шаблон, где может потребоваться постраничное деление контента, можно добавить фрагмент кода с переключателем страниц.

Задание

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