Программы
Добавляем постраничную пагинацию на Django сайт

Добавляем постраничную пагинацию на Django сайт

На сайтах часто встречаются многостраничные объекты: список товаров, список заметок и т.д. Поэтому важно уметь добавить навигацию по страницам на Django-проекте.

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

Итак, представим себе, что мы имеем модель Post, имеющей примерно следующий вид:

from django.db import models


class Post(models.Model):
    name = models.CharField(max_length=100, verbose_name='Название')
    slug = models.CharField(max_length=64, verbose_name='URL', validators=[validate_slug], unique=True)
    published = models.DateTimeField(default=None, null=True, blank=True)
    text = models.TextField(verbose_name='Текст')

Тогда контроллер для вывода списка заметок будет выглядеть примерно следующим образом:

from django.views.generic import ListView
from post.models import Post


class PostListView(ListView):
    model = Post

Шаблон же для страницы с листингом заметок будет выглядеть следующим образом (в общем виде):

<div class="post-list">
{% for object in object_list %}
    <div class="post">
        <h3>{{ object.name }}</h3>
        <p>
            {{ object.text|truncatewords_html:10 }}
        </p>
    </div>
{% endfor %}
</div>

Итак, мы описали в общем виде наш MVC. Чтобы добавить к нашему листингу пагинацию, добавим атрибут paginate_by для класса контроллера:

from django.views.generic import ListView
from post.models import Post


class PostListView(ListView):
    model = Post
    paginate_by = 20  # Вот эта строчка

Теперь в контексте нашего шаблона появляется объект page_obj - объект для страницы с пагинацией. Теперь давайте его используем для вывода ссылок для навигации между страницами:

<div class="post-list">
{% for object in object_list %}
    <div class="post">
        <h3>{{ object.name }}</h3>
        <p>
            {{ object.text|truncatewords_html:10 }}
        </p>
    </div>
{% endfor %}

    <div class="example1-pagination">
        {% if page_obj.has_previous %}
            <a class="example1-pagination_link" href="?page=1">&laquo; первая</a>
            <a class="example1-pagination_link" href="?page={{ page_obj.previous_page_number }}">предыдущая</a>
        {% endif %}

        <span class="example1-pagination_link example1-pagination_link__active">
            Страница {{ page_obj.number }} из {{ page_obj.paginator.num_pages }}.
        </span>

        {% if page_obj.has_next %}
            <a class="example1-pagination_link" href="?page={{ page_obj.next_page_number }}">следующая</a>
            <a class="example1-pagination_link" href="?page={{ page_obj.paginator.num_pages }}">последняя &raquo;</a>
        {% endif %}
    </div>
</div>

Таким образом, мы получаем верстку пагинации примерно следующего формата:

Естественно, можно и иначе вывести пагинацию. Так, к примеру, можно показывать первые три страницы, по три страницы вокруг текущей и три последние страницы. Такой подход мне кажется более удобным для использования:

<div class="post-list">
{% for object in object_list %}
    <div class="post">
        <h3>{{ object.name }}</h3>
        <p>
            {{ object.text|truncatewords_html:10 }}
        </p>
    </div>
{% endfor %}

    <div class="example2-pagination">
        {% for num in page_obj.paginator.page_range %}
            {% if num < page_obj.number and num < 4 %}
                <a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
            {% elif num < page_obj.number and num > page_obj.number|add:-4 %}
                <a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
            {% elif num == page_obj.number %}
                <span class="example2-pagination_link example2-pagination_link__active">{{ num }}</span>
            {% elif num > page_obj.number and num < page_obj.number|add:4 %}
                <a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
            {% elif num > page_obj.number and num > page_obj.paginator.num_pages|add:-3 %}
                <a class="example2-pagination_link" href="?page={{ num }}">{{ num }}</a>
            {% endif %}
        {% endfor %}
    </div>
</div>

Тогда получим примерно следующий вид:

1 2 3 4 5 6 7 48 49 50

Если же вы используете не Class Based View, а обычные view в виде функций, то подготовить объект для пагинации придётся нам самим:

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

from post.models import Post


def post_list_view(request):
    paginator = Paginator(Post.objects.all(), per_page=20)
    page = paginator.page(request.GET.get('page', 1))
    return render(request, 'post/post_list.html', {
        'object_list': page.object_list,
        'page_obj': page,
    })

Подготовив в нашем view таким образом контекст шаблона, нам не нужно беспокоиться об изменениях самих шаблонов, ведь мы передали в них ровно то, что передал бы и его брат-ClassBasedView, что мы рассмотрели выше.

Изображение Python 3.11. Что нового?