Добавление постраничной разбивки

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

Зачем пагинация

Когда постов сотни, выводить все на одной странице — медленно и неудобно. Пагинация разбивает список на страницы: /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

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

Перепишите post_list с пагинацией: по 3 поста на страницу, номер страницы — из GET-параметра page (по умолчанию 1). Используйте Paginator и метод get_page. В шаблон передайте страницу как posts.

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

Чем paginator.get_page("abc") отличается от paginator.page("abc")?

Задание 3 Несколько вариантов

Какие атрибуты и методы есть у объекта страницы Page?

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

Напишите ссылку «Далее», которая показывается, только если есть следующая страница, и ведёт на ?page=<номер следующей страницы>. Объект страницы — page.

Задание 5 Ввод ответа

Каким тегом подключить фрагмент pagination.html и передать ему переменную page со значением posts? Напишите тег.

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