Пагинация (Pagination)

Представьте, что у вас в базе хранится значительное количество публикаций. Запрос api/v1/posts/ вернёт сразу 100500 объектов, и у клиента будут проблемы: как разобраться в такой груде информации (да и потребности в таком объёме данных обычно не возникает). Поможет Pagination, система деления выдачи на части (с аналогичной системой вы уже встречались, настраивая вывод постов на фронтенд).
Пагинацию можно включить на уровне всего проекта, установив ключи DEFAULT_PAGINATION_CLASS и PAGE_SIZE в словаре настроек REST_FRAMEWORK. Они отвечают за подключение пагинатора и число объектов в выдаче. Обратите внимание: нужно установить оба этих параметра.
Начнем с самого простого класса PageNumberPagination:
Скопировать кодPYTHON
REST_FRAMEWORK = { 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 100 }
Теперь при GET-запросе http://localhost:8000/api/v1/posts/ вы получите такой результат:
Скопировать кодJSON
{ "count": 100500, "next": "http://localhost:8000/api/v1/posts/?page=2", "previous": null, "results": [ { "id": 1, "text": "Это мой блог на Yatube, скорее подписывайтесь на мою ленту!", "author": "sunny_blogger", "pub_date": "2020-04-06T13:45:00.941389Z" }, { "id": 2, "text": "Это нора мизантропа, не стучитесь в двери. Подписчиков забаню.", "author": "black_misanthrope", "pub_date": "2020-04-09T15:07:29.868981Z" }, ... ] }
Посмотрите на результат запроса: если раньше список объектов был прямо в теле JSON, то теперь объекты вложены в список results.
Добавление пагинации изменило структуру выдачи, и если клиенты уже пользуются нашим API — у них будут проблемы: после наших изменений извлечь данные из выдачи не удастся, придётся переписывать обработчики. В такой ситуации будет правильно выпустить следующую версию API (в нашем случае — api/v2/), а для прежних клиентов оставить доступной первую версию (пусть клиенты сами решат, когда перейти на новую).
При включённой пагинации запрос к API можно делать с дополнительным параметром page. Значением этого параметра должно быть целое число, указывающее на нужную «страницу» выдачи. Нумерация «страниц» начинается с единицы.
У объекта выдачи также появились поля count, next и previous: это общее количество объектов и URL'ы следующей и предыдущей страниц. Эти поля упрощают работу с выдачей: например, в интерфейсе поиска можно вывести информацию «Найдено {count} элементов» и дать ссылки для загрузки предыдущей и следующей страниц с результатами поиска.
Пагинацию можно установить на уровне отдельного view-класса (Generics или Viewsets), указав класс пагинатора в поле pagination_class:
Скопировать кодPYTHON
from rest_framework.pagination import PageNumberPagination class PostViewSet(viewsets.ModelViewSet): queryset = Post.objects.all() serializer_class = PostSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) pagination_class = PageNumberPagination
Есть и более гибкий класс для пагинации: LimitOffsetPagination.
В случае с классом PageNumberPagination разработчик жёстко устанавливает разбиение по страницам, а класс LimitOffsetPagination даёт возможность клиенту самостоятельно определять, какое число объектов вернётся (параметр limit) и с какого по счёту объекта начать отсчёт (параметр offset).
При подключении класса LimitOffsetPagination GET-запрос может выглядеть так: http://localhost:8000/api/v1/posts/?limit=10&offset=5 Такой запрос вернёт 10 объектов, с шестого по пятнадцатый (или меньше, если в результате запроса менее 15 объектов).
Обычно этих двух классов пагинации хватает для решения стандартных задач. Но иногда происходят коллизии. Например, данные могут меняться очень быстро — какой-то объект добавится, какой-то удалится — и порядок выдачи нарушится.
Для этих случаев есть «разбиение на основе курсора»: CursorPagination. Такое разбиение гарантирует неизменный порядок элементов и клиент не увидит один и тот же объект дважды при просмотре.
Это похоже на то, как работает API домашек: при запросе вы передавали timestamp и ограничивали периоды выдачи изменения статуса домашки. В CursorPagination действует примерно тот же механизм: сервер запоминает последний объект выдачи и генерирует уникальный URL для следующей страницы, которая начинается с того места, где закончилась предыдущая. Единственное условие — объекты должны быть отсортированы по какому-либо полю (например, по дате создания).