Skip to content

Latest commit

 

History

History
320 lines (220 loc) · 9.87 KB

File metadata and controls

320 lines (220 loc) · 9.87 KB

Модуль 8. Урок 54. Пагинация с использованием ListView в Django

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

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

В этом уроке мы:

  • подключим пагинацию к ListView буквально одной строкой;
  • разберём, какие переменные Django автоматически передаёт в шаблон;
  • научимся управлять отображением номеров страниц;
  • добавим кнопки «вперёд» и «назад»;
  • рассмотрим типичные ошибки и способы их избежать.

Все примеры будут построены на проекте cinemahub и основной модели Movie.


Встроенная пагинация в ListView

ListView поддерживает пагинацию через атрибут класса:

paginate_by = N

где N — количество объектов, отображаемых на одной странице.

Как только этот атрибут добавлен, Django автоматически:

  • разбивает queryset на страницы;
  • извлекает номер страницы из GET-параметра page;
  • передаёт в шаблон специальные объекты для работы с пагинацией.

Добавление пагинации к списку фильмов

Представление (movies/views.py)

Создадим представление для главной страницы фильмов:

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, список снова станет «бесконечным».


Что Django автоматически передаёт в шаблон

При использовании ListView с пагинацией в шаблоне доступны:

  • page_obj — объект текущей страницы;
  • paginator — объект пагинатора;
  • is_paginated — булево значение (есть ли пагинация).

Эти переменные появляются без дополнительного кода.


Базовый шаблон со списком фильмов

Шаблон (templates/movies/movie_list.html)

<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 %}

Теперь под списком фильмов появляются номера страниц.


Проверка результата в браузере

  1. Запускаем сервер.

  2. Открываем страницу списка фильмов.

  3. Видим не более 5 записей.

  4. Внизу отображаются номера страниц.

  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 }}">&lt;</a>
</li>
{% endif %}

Кнопка «вперёд»

{% if page_obj.has_next %}
<li>
    <a href="?page={{ page_obj.next_page_number }}">&gt;</a>
</li>
{% endif %}

Теперь навигация становится привычной и удобной.


Использование пагинации через миксин

Если пагинация нужна в нескольких ListView, разумно вынести её в миксин.

Миксин (utils.py)

class PaginationMixin:
    paginate_by = 5

Применение в представлении

class MovieListView(PaginationMixin, ListView):
    model = Movie
    template_name = 'movies/movie_list.html'
    context_object_name = 'movies'

Теперь:

  • пагинация подключается автоматически;
  • значение paginate_by легко изменить в одном месте.

Типичные ошибки

Ошибка 1: Работа с Paginator вручную в ListView

Это избыточно. ListView уже решает эту задачу.


Ошибка 2: Использование object_list вместо page_obj

В шаблоне следует работать с:

  • movies (или object_list) для данных;
  • page_obj и paginator для навигации.

Ошибка 3: Отсутствие is_paginated

Без проверки is_paginated навигация может отображаться даже при одной странице.


Практические задания

Задание 1

Создайте ListView для списка актёров с пагинацией по 8 элементов.


Задание 2

Реализуйте пагинацию для списка жанров, используя миксин.


Сравнить решение

Решение задания 1

class ActorListView(ListView):
    model = Actor
    template_name = 'actors/actor_list.html'
    context_object_name = 'actors'
    paginate_by = 8

Решение задания 2

class GenreListView(PaginationMixin, ListView):
    model = Genre
    template_name = 'genres/genre_list.html'
    context_object_name = 'genres'

Вопросы

  1. Как включить пагинацию в ListView?
  2. Какие объекты Django автоматически передаёт в шаблон?
  3. Чем page_obj отличается от paginator?
  4. Зачем использовать is_paginated?
  5. Как выделить текущую страницу?
  6. Как добавить кнопки «вперёд» и «назад»?
  7. В каких случаях стоит использовать миксин?
  8. Почему не нужно создавать Paginator вручную в ListView?

Предыдущий урок | Следующий урок