Паджинатор
После запуска наш проект Yatube разрастётся, количество записей в лентах увеличится, и если вывести все записи на одной странице — сайтом пользоваться станет невозможно.
Пока что на страницы нашего проекта выводится ограниченное число записей. Даже если на сайте опубликовано несколько тысяч постов — пользователь сможет прочесть только одиннадцать из них.
Код view-функции для главной страницы сейчас выглядит примерно так:
Скопировать кодPYTHON
def index(request):
latest = Post.objects.order_by('-pub_date')[:11]
return render(request, 'index.html', {"posts": latest})
Проблема вывода большого числа постов решается разделением ленты на отдельные страницы с ограниченным числом записей на каждой. Для перехода между страницами добавляются специальные ссылки.
Список ссылок для постраничного перехода вам знаком, это стандартный элемент интерфейсов сатов. Такой компонент есть и во фреймворке Bootstrap:
Такую полосу можно достаточно просто реализовать самому.
- При постраничном делении передать в GET-запросе переменную
page, значением которой будет номер запрошенной страницы, например /leo?page=8 - Проверить, есть ли переменная
page в GET-параметрах - Если нет — отдавать первую страницу, с постами с первого по одиннадцатый.
- Если есть
- Посчитать количество записей в базе
- С помощью
OFFSET и LIMIT получить нужный диапазон записей и отобразить их на странице
Но писать такой код для каждого списка элементов на сайте было бы неправильно, надо автоматизировать эту задачу.
В Django эту проблему решает стандартный модуль Paginator: он «раскладывает» списки по отдельным страницам, выводя на каждую страницу требуемое количество элементов. Для названия этой системы в русском языке используют кальку с английского и называют её «паджинатор».
Скопировать кодPYTHON
>>> from django.core.paginator import Paginator
>>> items = ['Антон Чехов', 'Владимир Набоков', 'Лев Толстой', 'Марина Цветаева']
>>> p = Paginator(items, 2)
>>> p.count
4
>>> 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
>>> 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 = posts.page(2)
>>> post_page.object_list
<QuerySet [<Post: 36>, <Post: 35>]>
Свойства объекта страницы post_page:
- post_page — значение
<Page 2 of 19>, тип django.core.paginator.Page - post_page.has_next() — значение
True, тип bool - post_page.has_previous() — значение
True, тип bool - post_page.has_other_pages() — значение
True, тип bool - post_page.next_page_number() — значение
3, тип int - post_page.previous_page_number() — значение
1, тип int - post_page.start_index() — номер первого элемента на текущей странице от начала списка, если считать с 1, значение
3, тип int - post_page.end_index() — номер последнего элемента на текущей странице от начала списка, если считать с 1, значение
4, тип int
Методы .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)
page_number = request.GET.get('page')
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 }}">« Предыдущая</a></li>
{% else %}
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">« Предыдущая</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 }}">Следующая »</a></li>
{% else %}
<li class="page-item disabled"><a class="page-link" href="#" tabindex="-1" aria-disabled="true">Следующая »</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 %}
Теперь в любой шаблон, где может потребоваться постраничное деление контента, можно добавить фрагмент кода с переключателем страниц.
Задание
Сделайте так, чтобы на страницах групп появился красивый переключатель страниц. По десять записей на странице будет в самый раз.