На практике почти любой веб-проект сталкивается с одной и той же проблемой: количество данных растёт, а отображать их все на одной странице становится неудобно и неэффективно. Списки фильмов, актёров, режиссёров или жанров в проекте cinemahub — яркий тому пример. Уже при нескольких десятках записей страница начинает перегружаться, ухудшается восприятие информации и падает производительность.
Для решения этой задачи в Django предусмотрен встроенный механизм пагинации, центральным элементом которого является класс Paginator.
В этом уроке мы разберём:
- что такое пагинация и зачем она нужна;
- как работает класс
Paginatorна простом примере; - как применять пагинацию в Django-представлениях;
- как выводить постраничную навигацию в шаблонах;
- какие ошибки чаще всего допускают при работе с пагинацией и как их избежать.
Пагинация — это разбиение большого набора данных на страницы фиксированного размера. Пользователь видит не весь список целиком, а только его часть, с возможностью перехода между страницами.
В проекте cinemahub пагинация особенно актуальна для:
- списка фильмов (
Movie); - списков актёров и режиссёров;
- результатов поиска и фильтрации.
Без пагинации:
- HTML-страницы становятся громоздкими;
- возрастает время загрузки;
- навигация по списку становится неудобной.
Прежде чем применять пагинацию в представлениях, полезно понять, как работает класс Paginator сам по себе.
Предположим, у нас есть список фильмов:
movies = [
"Inception", "Interstellar", "The Dark Knight",
"The Prestige", "Memento", "Dunkirk",
"Tenet", "Batman Begins", "Oppenheimer"
]Наша задача — выводить по 3 фильма на страницу.
from django.core.paginator import Paginator
paginator = Paginator(movies, 3)Здесь:
- первый аргумент — список или queryset;
- второй аргумент — количество элементов на одной странице.
paginator.count # Общее количество элементов
paginator.num_pages # Количество страниц
paginator.page_range # Диапазон номеров страницЕсли вывести значения:
count→ 9num_pages→ 3page_range→ [1, 2, 3]
Чтобы получить данные для конкретной страницы, используется метод page().
page1 = paginator.page(1)Теперь объект page1 содержит:
page1.object_list— элементы страницы;page1.has_next()— есть ли следующая страница;page1.has_previous()— есть ли предыдущая;page1.next_page_number()— номер следующей страницы.
Этот объект мы и будем передавать в шаблон.
Теперь перенесём эти знания в реальный Django-проект cinemahub.
Создать страницу со списком опубликованных фильмов, разбитых на страницы по 5 фильмов.
# movies/views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Movie
def movie_list(request):
movies = Movie.objects.filter(is_published=True).order_by('-created_at')
paginator = Paginator(movies, 5)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
'title': 'Список фильмов',
}
return render(request, 'movies/movie_list.html', context)Разберём код пошагово:
-
Получаем queryset фильмов.
-
Создаём объект
Paginator. -
Извлекаем номер страницы из
GET-параметраpage. -
Используем
get_page():- если номер страницы некорректный — Django покажет первую;
- если номер слишком большой — последнюю.
-
Передаём
page_objв шаблон.
<h1>{{ title }}</h1>
<ul>
{% for movie in page_obj %}
<li>
{{ movie.title }} ({{ movie.year }})
</li>
{% endfor %}
</ul>Обратите внимание:
page_obj можно использовать как обычный список.
Добавим блок пагинации:
<nav>
<ul>
{% for page in page_obj.paginator.page_range %}
<li>
<a href="?page={{ page }}">{{ page }}</a>
</li>
{% endfor %}
</ul>
</nav>Теперь пользователь может переходить между страницами по ссылкам вида:
/movies/?page=2
После запуска сервера:
-
Переходим на страницу списка фильмов.
-
Видим не более 5 фильмов.
-
Внизу страницы отображаются номера страниц.
-
При переходе:
?page=1— первая страница;?page=999— последняя;?page=abc— первая.
Django обрабатывает эти ситуации автоматически, если используется get_page().
paginator.page(page_number)Если page_number некорректен — возникнет исключение.
Для начального уровня рекомендуется всегда использовать get_page().
В шаблоне нужно работать именно с объектом страницы, а не с Paginator.
Без order_by() порядок элементов может быть непредсказуемым, особенно при добавлении новых записей.
Создайте представление, которое выводит список актёров, разбитый на страницы по 10 человек.
Требования:
- использовать
Paginator; - выводить имя актёра;
- добавить навигацию по страницам.
Реализуйте пагинацию для списка жанров, по 7 жанров на страницу.
# actors/views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Actor
def actor_list(request):
actors = Actor.objects.all().order_by('name')
paginator = Paginator(actors, 10)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'actors/actor_list.html', {'page_obj': page_obj})from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Genre
def genre_list(request):
genres = Genre.objects.all()
paginator = Paginator(genres, 7)
page_obj = paginator.get_page(request.GET.get('page'))
return render(request, 'genres/genre_list.html', {'page_obj': page_obj})- Зачем нужна пагинация в веб-приложениях?
- Какие аргументы принимает класс
Paginator? - В чём разница между
page()иget_page()? - Что содержит объект
page_obj? - Как получить номер текущей страницы?
- Почему важно сортировать queryset перед пагинацией?
- Как формируются ссылки на страницы в шаблоне?
- Что произойдёт при передаче некорректного номера страницы?