Пагинация

⏱ 20 мин · 🧩 заданий: 5

Зачем пагинация в API

Отдавать все записи разом нельзя: тысячи постов — это мегабайты JSON и медленный запрос. API отдаёт данные порциями и сообщает клиенту, где следующая порция.

Глобальная настройка

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 10,
}

Теперь все списки (generic views и ViewSet) пагинируются:

{
  "count": 57,
  "next": "http://127.0.0.1:8000/api/posts/?page=3",
  "previous": "http://127.0.0.1:8000/api/posts/?page=1",
  "results": [
    {"id": 41, "title": "..."},
    ...
  ]
}

Три стиля пагинации

PageNumberPagination — по номерам страниц

GET /api/posts/?page=2 — привычно, как на сайте.

LimitOffsetPagination — смещение и количество

GET /api/posts/?limit=5&offset=10 — 5 записей, пропустив первые 10. Удобно, когда клиент сам решает, сколько загружать.

CursorPagination — по курсору

GET /api/posts/?cursor=cD0yMDI2LTEwLTI1 — непрозрачный указатель на позицию.

  • Стабильна при добавлении новых записей (нет дубликатов и пропусков в бесконечной ленте).
  • Быстрая на огромных таблицах (не использует OFFSET).
  • Нельзя перейти на произвольную страницу; нужна сортировка по уникальному неизменному полю (по умолчанию -created).
class FeedPagination(CursorPagination):
    page_size = 20
    ordering = "-publish"

Свой класс пагинации

from rest_framework.pagination import PageNumberPagination


class StandardPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = "page_size"   # ?page_size=50
    max_page_size = 100                   # защита от ?page_size=1000000

Глобально — "DEFAULT_PAGINATION_CLASS": "blog.api.pagination.StandardPagination", или для одного представления:

class PostViewSet(viewsets.ModelViewSet):
    pagination_class = StandardPagination


class TagListView(generics.ListAPIView):
    pagination_class = None   # отключить для короткого списка

Свой формат ответа

Например, добавить число страниц:

from rest_framework.response import Response


class StandardPagination(PageNumberPagination):
    page_size = 20

    def get_paginated_response(self, data):
        return Response({
            "count": self.page.paginator.count,
            "total_pages": self.page.paginator.num_pages,
            "current": self.page.number,
            "next": self.get_next_link(),
            "previous": self.get_previous_link(),
            "results": data,
        })

Пагинация в своих APIView

Generic-классы пагинируют сами. В APIView — вручную:

class PostList(APIView):
    def get(self, request):
        paginator = StandardPagination()
        page = paginator.paginate_queryset(Post.published.all(), request, view=self)
        serializer = PostSerializer(page, many=True)
        return paginator.get_paginated_response(serializer.data)

Итог

  • Пагинация включается глобально (DEFAULT_PAGINATION_CLASS, PAGE_SIZE) или для класса (pagination_class).
  • PageNumberPagination — ?page=, LimitOffsetPagination — ?limit=&offset=, CursorPagination — для лент.
  • Ответ содержит count, next, previous, results; формат можно изменить в get_paginated_response.
  • Ограничивайте max_page_size, если клиенту разрешено менять размер страницы.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Написать код

Включите пагинацию глобально в настройках DRF: класс rest_framework.pagination.PageNumberPagination, размер страницы — 10.

Задание 2 Несколько вариантов

Какие ключи есть в ответе списка с PageNumberPagination?

Задание 3 Написать код

Создайте класс StandardPagination (наследник PageNumberPagination): по 20 объектов, клиент может менять размер параметром page_size, но не больше 100.

Задание 4 Один вариант ответа

Какой тип пагинации лучше подходит для бесконечной ленты, где постоянно появляются новые записи?

Задание 5 Ввод ответа

Какой адрес вернёт 5 постов, начиная с 11-го, при LimitOffsetPagination? (для /api/posts/)

Войдите, чтобы отмечать пройденные уроки.