Добавление постраничной разбивки
Зачем пагинация
Когда постов сотни, выводить все на одной странице — медленно и неудобно. Пагинация разбивает список на страницы: /blog/?page=2.
Класс Paginator
from django.core.paginator import Paginator
paginator = Paginator(Post.published.all(), 3) # по 3 объекта на странице
paginator.count # всего объектов
paginator.num_pages # всего страниц
page = paginator.page(2)
page.object_list # объекты на странице
Paginator выполняет COUNT и запрос со срезом LIMIT/OFFSET — загружаются только нужные посты.
Пагинация во view
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
def post_list(request):
post_list = Post.published.all()
paginator = Paginator(post_list, 3)
page_number = request.GET.get("page", 1)
try:
posts = paginator.page(page_number)
except PageNotAnInteger:
posts = paginator.page(1) # ?page=abc
except EmptyPage:
posts = paginator.page(paginator.num_pages) # ?page=999
return render(request, "blog/post/list.html", {"posts": posts})
Обработку ошибок можно сократить методом get_page() — он делает то же самое:
posts = paginator.get_page(page_number)
Объект страницы можно перебирать в цикле как список, поэтому шаблон list.html менять не нужно — {% for post in posts %} продолжает работать.
Шаблон навигации
Навигация понадобится и в других списках, поэтому вынесем её во фрагмент templates/pagination.html:
<div class="pagination">
<span class="step-links">
{% if page.has_previous %}
<a href="?page=1">« первая</a>
<a href="?page={{ page.previous_page_number }}">← назад</a>
{% endif %}
<span class="current">
Страница {{ page.number }} из {{ page.paginator.num_pages }}
</span>
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">далее →</a>
<a href="?page={{ page.paginator.num_pages }}">последняя »</a>
{% endif %}
</span>
</div>
Подключаем в list.html после цикла:
{% include "pagination.html" with page=posts %}
with page=posts — фрагмент работает с переменной page, поэтому его можно подключить к любому списку.
Номера страниц
Чтобы показать ссылки на все страницы:
{% for num in page.paginator.page_range %}
{% if num == page.number %}
<b>{{ num }}</b>
{% else %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
Для сотен страниц удобнее page.paginator.get_elided_page_range(page.number) — он выводит «1 2 … 9 10 11 … 49 50».
Сохранение других параметров
Если в адресе есть фильтры (?tag=django&page=2), простая ссылка ?page=3 их потеряет. Решение — передавать параметры явно или собрать их во view:
params = request.GET.copy()
params.pop("page", None)
context["querystring"] = params.urlencode()
<a href="?{{ querystring }}&page={{ page.next_page_number }}">далее</a>
С Django 5.1 появился тег {% querystring page=page.next_page_number %}, который делает это сам.
Итог
Paginator(queryset, per_page)+get_page(number)— пагинация в две строки.page.has_next,page.next_page_number,page.number,page.paginator.num_pages— для навигации.- Навигацию выносим во фрагмент и подключаем через
include ... with.
Практика решено 0 из 5
Перепишите post_list с пагинацией: по 3 поста на страницу, номер страницы — из GET-параметра page
(по умолчанию 1). Используйте Paginator и метод get_page. В шаблон передайте страницу как posts.
from django.core.paginator import Paginator
def post_list(request):
post_list = Post.published.all()
paginator = Paginator(post_list, 3)
page_number = request.GET.get("page", 1)
posts = paginator.get_page(page_number)
return render(request, "blog/post/list.html", {"posts": posts})
Чем paginator.get_page("abc") отличается от paginator.page("abc")?
Какие атрибуты и методы есть у объекта страницы Page?
Напишите ссылку «Далее», которая показывается, только если есть следующая страница,
и ведёт на ?page=<номер следующей страницы>. Объект страницы — page.
{% if page.has_next %}
<a href="?page={{ page.next_page_number }}">Далее</a>
{% endif %}
Каким тегом подключить фрагмент pagination.html и передать ему переменную page со значением posts? Напишите тег.
{% include "pagination.html" with page=posts %}Войдите, чтобы отмечать пройденные уроки.