Модуль Paginator

Сейчас на главную страницу проекта выводится ограниченное число записей. Даже если на сайте опубликовано несколько тысяч постов — пользователь сможет прочесть только десять из них: во view-функции главной страницы стоит ограничение на вывод постов.

Чтобы получить доступ к большому списку материалов сайта (будь то посты в блоге или товары в интернет-магазине) — список разбивают постранично, выводя на каждую страницу определённое количество записей. Для перехода между страницами добавляют специальные ссылки.

Список ссылок для постраничного перехода — это стандартный элемент интерфейса:

Написать постраничное разбиение материалов можно самостоятельно:

  • при постраничном делении передать в GET-запросе переменную page, значением которой будет номер запрошенной страницы, например /group/cats?page=8 (восьмая страница с постами сообщества, посвящённого котикам);
  • во view-функции проверить, есть ли переменная page в GET-параметрах;
  • если в параметрах нет переменной page — отдавать первую страницу, с постами с первого по десятый;
  • если переменная page есть:
    • посчитать количество записей в базе,
    • через OFFSET и LIMIT получить нужный диапазон записей и отобразить их на странице. Для /group/cats?page=8 нужно будет получить записи с 71-ой по 80-ую включительно, если разбиение будет по десять постов на страницу.

Но писать такой код для каждого списка элементов было бы нерационально, и в Django решили эту задачу: стандартный модуль Paginator раскладывает списки объектов по отдельным страницам и создаёт множество дополнительных инструментов для управления такими страницами. Для названия этой системы в русском языке используют кальку с английского: «паджинатор».

Объект класса Paginator получает на вход список объектов, разбивает его на отдельные страницы и позволяет обратиться к ним индивидуально или получить полный список получившихся страниц.

Задача разработчика — создать объект Paginator(), передать в него список объектов и число элементов, которое требуется выводить на одну страницу. Всё остальное модуль сделает сам, остаётся только обращаться к свойствам и методам созданного объекта:

>>> from django.core.paginator import Paginator
# Создаём тестовый список объектов
>>> items = ['Антон Чехов', 'Владимир Набоков', 'Лев Толстой', 'Марина Цветаева']

# Cоздаём объект Paginator(object_list, per_page),
# он принимает на вход список объектов 
# и число объектов, которое должно отображаться на одной странице.
# Передаём список items и задаём постраничное деление: по два объекта на страницу
>>> p = Paginator(items, 2)

# Свойство count показывает, сколько объектов в последовательности
>>> p.count
4

# Свойство num_pages показывает сколько страниц получится из списка
# num_pages рассчитывается как len(items)/per_page
>>> p.num_pages
2

# Получаем объект с элементами для первой страницы
>>> page1 = p.get_page(1)
>>> page1
<Page 1 of 2>

# Получаем элементы для отображения на первой странице
>>> page1.object_list
['Антон Чехов', 'Владимир Набоков']

# Проверяем, есть ли страницы после текущей
# и надо ли отображать кнопку "Следующая страница"
>>> page1.has_next()
True
# Проверяем, есть ли страницы перед текущей
# и надо ли отображать кнопку "Предыдущая страница"
>>> page1.has_previous()
False

>>> page2 = p.get_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> 

При запросе данных из базы тоже возвращается список объектов, и работать с ним можно точно так же, как со списком писателей из приведённого примера.

>>> from posts.models import Post
>>> posts = Paginator(Post.objects.order_by('-pub_date'), 2)
# В переменную post_page передадим объект второй страницы паджинатора
>>> post_page = posts.get_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() будут полезны, если нужно нумеровать элементы, выведенные на страницу.

Паджинатор в Yatube

Обновите view-функцию главной страницы: пусть она отдаёт по десять постов на страницу. Они будут передаваться в переменной page.

Для перехода по страницам паджинатора к обычному URL будет добавляться GET-параметр page с указанием нужной страницы: page=n, где n — это номер страницы. Например, URL второй страницы паджинатора для главной страницы проекта будет таким: http://127.0.0.1:8000/?page=2

from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Post


def index(request):
    post_list = Post.objects.all().order_by('-pub_date')
    # Если порядок сортировки определен в классе Meta модели,
    # запрос будет выглядить так:
    # post_list = Post.objects.all()
    # Показывать по 10 записей на странице.
    paginator = Paginator(post_list, 10) 

    # Из URL извлекаем номер запрошенной страницы - это значение параметра page
    page_number = request.GET.get('page')

    # Получаем набор записей для страницы с запрошенным номером
    page_obj = paginator.get_page(page_number)
    # Отдаем в словаре контекста
    context = {
        'page_obj': page_obj,
    }
    return render(request, 'posts/index.html', context) 

Настройка HTML-шаблонов

Измените шаблон index.html: список постов будет передаваться в элементе с ключом page.

{% extends "base.html" %}
{% block title %}Последние обновления на сайте{% endblock %}
{% block header %}Последние обновления на сайте{% endblock %}
{% block content %}

  {% for post in page_obj %}
    ...Тут вывод списка записей...
  {% endfor %}

{% endblock %} 

Помимо списка объектов заданной страницы переменная page_obj содержит объект paginator, в котором доступны все методы свойства объекта Paginator. В результате к этому объекту можно обратиться, запросив его методы, свойства или атрибуты, это пригодится при работе с шаблоном:

  • page_obj.paginator.per_page — сколько записей на страницу? Десять.
  • page_obj.paginator.num_pages — сколько страниц получилось? Четыре (если вы ничего не добавили к постам Льва Толстого).

Остальные свойства и методы можно найти в официальной документации модуля Paginator.

Теперь нужно написать HTML-код для навигации по страницам. Паджинатор понадобится на нескольких страницах проекта, и чтобы не дублировать код навигации в разных шаблонах — напишите виджет в отдельном файле, и его можно будет включать через include туда, где он необходим.

Логика проекта предполагает, что паджинатор будет применяться только в приложении posts, так что файл с паджинатором можно разместить вместе с шаблонами приложения posts.

Если использование паджинации предполагается в нескольких приложения — лучше вынести этот файл в папку /includes на уровень шаблонов проекта.

В директории templates/posts/includes создайте файл paginator.html и добавьте в него такой код:

{# templates/posts/includes/paginator.html #}

{# Отрисовываем навигацию паджинатора только если
все посты не помещаются на первую страницу #}
{% if page_obj.has_other_pages %}
<nav aria-label="Page navigation" class="my-5">
  <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 i in page_obj.paginator.page_range %}
        {% if page_obj.number == i %}
          <li class="page-item active">
            <span class="page-link">{{ i }}</span>
          </li>
        {% else %}
          <li class="page-item">
            <a class="page-link" href="?page={{ i }}">{{ i }}</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 %} 

Осталось встроить виджет паджинатора в код шаблона главной страницы:

{% extends "base.html" %}
{% block title %}Последние обновления на сайте{% endblock %}
{% block content %}

  {% for post in page_obj %}
    ...Тут вывод списка записей...
  {% endfor %}

  {% include 'posts/includes/paginator.html' %}

{% endblock %} 

Точно так же паджинатор можно добавить в любой другой шаблон, где может потребоваться постраничное разделение контента. Разумеется, view-функции тоже надо будет изменить.





Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: