В предыдущем уроке мы познакомились с классом Paginator и реализовали постраничный вывод данных вручную, используя функции-представления. Такой подход полезен для понимания механизма работы пагинации, однако в реальных проектах Django чаще используются классы представлений.
Одно из ключевых преимуществ ListView заключается в том, что пагинация уже встроена в этот класс. Нам не нужно вручную создавать объект Paginator, извлекать номер страницы или передавать дополнительные переменные в шаблон — Django делает это за нас.
В этом уроке мы:
- подключим пагинацию к
ListViewбуквально одной строкой; - разберём, какие переменные Django автоматически передаёт в шаблон;
- научимся управлять отображением номеров страниц;
- добавим кнопки «вперёд» и «назад»;
- рассмотрим типичные ошибки и способы их избежать.
Все примеры будут построены на проекте cinemahub и основной модели Movie.
ListView поддерживает пагинацию через атрибут класса:
paginate_by = Nгде N — количество объектов, отображаемых на одной странице.
Как только этот атрибут добавлен, Django автоматически:
- разбивает queryset на страницы;
- извлекает номер страницы из
GET-параметраpage; - передаёт в шаблон специальные объекты для работы с пагинацией.
Создадим представление для главной страницы фильмов:
from django.views.generic import ListView
from .models import Movie
class MovieListView(ListView):
model = Movie
template_name = 'movies/movie_list.html'
context_object_name = 'movies'
paginate_by = 5Разберём, что здесь происходит:
model— указывает, с какой моделью мы работаем;template_name— шаблон для отображения списка;context_object_name— имя переменной в шаблоне;paginate_by— активирует пагинацию.
Важно:
Если убрать paginate_by, список снова станет «бесконечным».
При использовании ListView с пагинацией в шаблоне доступны:
page_obj— объект текущей страницы;paginator— объект пагинатора;is_paginated— булево значение (есть ли пагинация).
Эти переменные появляются без дополнительного кода.
<h1>Фильмы</h1>
<ul>
{% for movie in movies %}
<li>
{{ movie.title }} ({{ movie.year }})
</li>
{% endfor %}
</ul>На этом этапе:
- отображается только 5 фильмов;
- остальные становятся доступны через
?page=2,?page=3и т.д.; - навигации пока нет.
{% if is_paginated %}
<nav>
<ul>
{% for page in paginator.page_range %}
<li>
<a href="?page={{ page }}">{{ page }}</a>
</li>
{% endfor %}
</ul>
</nav>
{% endif %}Теперь под списком фильмов появляются номера страниц.
-
Запускаем сервер.
-
Открываем страницу списка фильмов.
-
Видим не более 5 записей.
-
Внизу отображаются номера страниц.
-
При переходе:
?page=1— первая страница;?page=999— последняя;?page=abc— Django корректно покажет первую страницу.
Пользователю важно понимать, на какой странице он находится. Для этого изменим шаблон.
{% if is_paginated %}
<nav class="pagination">
<ul>
{% for page in paginator.page_range %}
{% if page_obj.number == page %}
<li class="active">{{ page }}</li>
{% else %}
<li>
<a href="?page={{ page }}">{{ page }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
</nav>
{% endif %}Теперь текущая страница:
- отображается без ссылки;
- может быть стилизована отдельно.
Если страниц много, выводить их все — плохая идея. Ограничим диапазон:
{% for page in paginator.page_range %}
{% if page == page_obj.number %}
<li class="active">{{ page }}</li>
{% elif page >= page_obj.number|add:-2 and page <= page_obj.number|add:2 %}
<li>
<a href="?page={{ page }}">{{ page }}</a>
</li>
{% endif %}
{% endfor %}В результате:
- показываются 2 страницы слева и справа от текущей;
- интерфейс становится компактнее.
{% if page_obj.has_previous %}
<li>
<a href="?page={{ page_obj.previous_page_number }}"><</a>
</li>
{% endif %}{% if page_obj.has_next %}
<li>
<a href="?page={{ page_obj.next_page_number }}">></a>
</li>
{% endif %}Теперь навигация становится привычной и удобной.
Если пагинация нужна в нескольких ListView, разумно вынести её в миксин.
class PaginationMixin:
paginate_by = 5class MovieListView(PaginationMixin, ListView):
model = Movie
template_name = 'movies/movie_list.html'
context_object_name = 'movies'Теперь:
- пагинация подключается автоматически;
- значение
paginate_byлегко изменить в одном месте.
Это избыточно.
ListView уже решает эту задачу.
В шаблоне следует работать с:
movies(илиobject_list) для данных;page_objиpaginatorдля навигации.
Без проверки is_paginated навигация может отображаться даже при одной странице.
Создайте ListView для списка актёров с пагинацией по 8 элементов.
Реализуйте пагинацию для списка жанров, используя миксин.
class ActorListView(ListView):
model = Actor
template_name = 'actors/actor_list.html'
context_object_name = 'actors'
paginate_by = 8class GenreListView(PaginationMixin, ListView):
model = Genre
template_name = 'genres/genre_list.html'
context_object_name = 'genres'- Как включить пагинацию в
ListView? - Какие объекты Django автоматически передаёт в шаблон?
- Чем
page_objотличается отpaginator? - Зачем использовать
is_paginated? - Как выделить текущую страницу?
- Как добавить кнопки «вперёд» и «назад»?
- В каких случаях стоит использовать миксин?
- Почему не нужно создавать
Paginatorвручную вListView?