Django 5 для начинающих

Прогресс по курсу:  9/1004

6.2 Добавление постраничной разбивки
2 из 3 шагов пройдено
0 из 5 баллов  получено

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

Когда вы начнете добавлять контент в свой блог, вы сможете легко хранить десятки и даже сотни постов в своей базе данных.
Возможно, вы захотите разделить список постов на несколько страниц, не отображая все записи на одной странице, и вставить навигационные ссылки на разные страницы.

Эта функциональность называется постраничной разбивкой, и ее можно найти почти в каждом веб-приложении, которое показывает длинные списки элементов.

Например, Яндекс использует постраничную разбивку с целью распределения результатов поиска по нескольким страницам.
На рисунке ниже показаны постранично разбитые ссылки Яндекса на страницы результатов поиска:

В Django есть встроенный класс постраничной разбивки, который позволяет легко управлять постранично разбитыми данными, при этом имеется возможность определять число объектов, которое необходимое возвращать в расчете на страницу, и извлекать записи, соответствующие запрошенной пользователем странице.

Класс Paginator первым элементом может принимать список, кортеж, QuerySet или другой объект, который можно оценить с помощью метода count() или __len__(). Для правильной разбивки на страницы, данные должны быть упорядочены, например, с помощью метода order_by() или с порядком по умолчанию в модели.

 

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

Отредактируйте файл views.py приложения blog, импортировав класс Django Paginator и видоизменив представление post_list, как показано ниже:

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


def post_list(request):
    post_list = Post.published.all()
    # Постраничная разбивка с 3 постами на страницу
    paginator = Paginator(post_list, 3)
    page_number = request.GET.get('page', 1)
    posts = paginator.page(page_number)
    return render(request,
                  'blog/post/list.html',
                  {'posts': posts})

#........

Давайте рассмотрим новый исходный код, который был добавлен в представление.

  1. Мы создаем экземпляр класса Paginator с числом объектов, возвращаемых в расчете на страницу. Мы будем отображать по три поста на страницу.

  2. Мы извлекаем HTTP GET-параметр page и сохраняем его в переменной page_number. Этот параметр содержит запрошенный номер страницы. Если параметра page нет в GET-параметрах запроса, то мы используем стандартное значение 1, чтобы загрузить первую страницу результатов.

  3. Мы получаем объекты для желаемой страницы, вызывая метод page() класса Paginator. Этот метод возвращает объект Page, который хранится в переменной posts.

  4. Мы передаем номер страницы и объект posts в шаблон.

 

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

Далее необходимо создать навигацию по страницам, чтобы пользователи имели возможность просматривать разные страницы.

Мы создадим шаблон отображения постранично разбитых ссылок и сделаем его типовым, чтобы иметь возможность переиспользовать шаблон для постраничной разбивки любого объекта на веб-сайте.

Внутри каталога templates создайте новый файл и назовите его pagination.html.

Добавьте в файл следующий ниже исходный код HTML:

<div class="pagination">
    <span class="step-links">
        {% if page.has_previous %} # если есть предыдущая страница то показываем ее кнопку
            <a href="?page={{ page.previous_page_number }}">Previous</a>  # возвращает номер предыдущей страницы
        {% endif %}
        <span class="current">
           Page {{ page.number }} of {{ page.paginator.num_pages }}.
        </span>
        {% if page.has_next %} # если есть следующая страница то показываем ее кнопку
            <a href="?page={{ page.next_page_number }}">Next</a> # возвращает номер следуюущей страницы
        {% endif %}
    </span>
</div>

Это типовой шаблон постраничной разбивки. В котором используются следующие атрибуты:

  • page.has_previous: проверяет, если есть предыдущая страница возвращает True, иначе False
  • page.has_next: проверяет, если есть следующая страница возвращает True, иначе False
  • page.previous_page_number: возвращает номер предыдущей страницы, вызывает ошибку InvalidPage, если следующая страница не существует.
  • page.next_page_number: возвращает номер следующей страницы, вызывает ошибку InvalidPage, если следующая страница не существует.
  • page.number: возвращает номер текущей страницы.

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

Давайте вернемся к шаблону blog/post/list.html и разместим шаблон pagination.html
в нижней части блока {% content %}, как показано ниже:

{% extends "blog/base.html" %}

{% block title %}My Blog{% endblock %}

{% block content %}
    <h1>My Blog</h1>
    {% for post in posts %}
        <h2>
            <a href="{{ post.get_absolute_url }}">
                {{ post.title }}
            </a>
        </h2>
        <p class="date">
            Published {{ post.publish }} by {{ post.author }}
        </p>
        {{ post.body|truncatewords:30|linebreaks }}
    {% endfor %}
    {% include "pagination.html" with page=posts %}
{% endblock %}

Шаблонный тег {% include %} загружает данный шаблон и прорисовывает его с использованием текущего контекста шаблона.

Ключевое слово with используется для того, чтобы передавать дополнительные контекстные переменные в шаблон.

Для прорисовки в шаблоне постраничной разбивки используется переменная page, при этом объект Page, который мы передаем из представления в шаблон, называется posts.

Мы используем выражение with page=posts, чтобы передавать переменную, ожидаемую шаблоном постраничной разбивки.

Описанному методу можно следовать для применения шаблона постраничной разбивки для любого типа объекта.

Давайте запустим сервер и откроем http://127.0.0.1:8000/admin/blog/post/ и создадим 4 разных поста. Проверьте, чтобы у всех этих постов был установлен статус Published. Напоминаем, что удобно брать шаблонный текст из Яндекс-Рефератов.

Теперь пройдите по URL-адресу http://127.0.0.1:8000/blog/ в своем браузере. Вы должны увидеть первые три поста в обратном хронологическом порядке, а затем навигационные ссылки в нижней части списка постов, как показано ниже:

Если кликнуть по Next(Далее), то можно увидеть последний пост.

URL-адрес второй страницы содержит GET-параметр ?page=2. Указанный параметр используется представлением для загрузки запрошенной страницы результатов с использованием постраничного разбивщика:

Отлично! Ссылки на постраничную разбивку работают так, как и ожидалось.


  • Комментариев
Будьте вежливы и соблюдайте наши принципы сообщества. Пожалуйста, не оставляйте решения и подсказки в комментариях, для этого есть отдельный форум.
Оставить комментарий
Мне кажется объект Page, лучше назвать page_obj, а то очень вводит в заблуждение потом в шаблонах переменная posts, которая на самом деле объект Page

Я бы добавил пояснения к шаблону: 

page.has_next() -  возвращает True если есть следующая страница, иначе возвращает False

page.has_previous() - возвращает True, если есть предыдущая страница, иначе возвращает False

page.next_page_number() - Возвращает номер следующей страницы, вызывает InvalidPage, если следующая страница не существует.

page.previous_page_number() - Возвращает номер предыдущей страницы, вызывает InvalidPage, если предыдущая страница не существует.

 

<div class="pagination">
    <span class="step-links">
        {% if page.has_previous %} # если есть предыдущая страница то показываем кнопку назад
            <a href="?page={{ page.previous_page_number }}">Previous</a>  # возвращает номер предыдущей страницы
        {% endif %}
        <span class="current">
           Page {{ page.number }} of {{ page.paginator.num_pages }}.
        </span>
        {% if page.has_next %} # если есть следующая страница то показываем кнопку вперед
            <a href="?page={{ page.next_page_number }}">Next</a> # возвращает номер следуюущей страницы
        {% endif %}
    </span>
</div>

@Александр_Павлов, Спасибо, добавил в лекцию.

Хммм, почему-то не работает ничего...
Просто все посты сплошняком идут, а внизу "Page of ."
В чём может быть проблема?

@Александр_Ёлшин, в представлении post_list нет ошибок?

@Дмитрий_Селезнев, действительно там была ошибка, поверх старой функции новую написал)

Друзья мне нужна помощь, вроде все правильно но ругаться на post_lists = Post.publish.all()

@Николай_Глазков, посты в базе есть и они имеют статус published 

@Николай_Глазков
чтобы решить вопрос пришло в функции представления сделать вот так
 

def post_list(request):

post_list = Post.objects.filter(status=Post.Status.PUBLISHED)

# Постраничная разбивка с 3 постами на страницу

paginator = Paginator(post_list, 3)

page_number = request.GET.get('page', 1)

posts = paginator.page(page_number)

return render(request,

'blog/post/list.html',

{'posts': posts})

@Николай_Глазков, менеджер в модель добавляли тут: https://stepik.org/lesson/972304/step/3?

  1. Мы получаем объекты для желаемой страницы, вызывая метод page() класса Paginator. Этот метод возвращает объект Page, который хранится в переменной posts.


    почему тут написано метод объекта, но по факту мы вызываем класс Page?

@Vlad_Kirnats, и что все эти переменные к которым мы обращаемся 

{% if page.has_previous %} <a href="?page={{ page.previous_page_number }} {{ page.number }} of {{ page.paginator.num_pages }}. {% if page.has_next %} <a href="?page={{ page.next_page_number }}"

@Vlad_Kirnats, мы не вызываем класс Page. Paginator возвращает нам разделенный QuerySet на объекты Page. Которые мы уже и выводим на каждой странице. 

page.previous_page_number,  page.previous_page_numberpage.number и другие переменные, это появившиеся переменные в результате работы Paginator.  Ссылка на документацию.

Спасибочки большое, как всегда быстро ответили