JavaRush /Курсы /Модуль 3: Django /Кастомизация пагинации

Кастомизация пагинации

Модуль 3: Django
19 уровень , 3 лекция
Открыта

На предыдущих лекциях мы познакомились с основами пагинации в Django REST Framework (DRF). Мы изучили встроенные классы пагинации, такие как PageNumberPagination и LimitOffsetPagination, а также узнали, как их настраивать на уровне проекта или только для определённых представлений.

Теперь поговорим о кастомизации пагинации. Представьте себе, что вы пишете API для интернет-магазина. На странице результатов поиска клиенту нужно показывать не только данные о товарах, но и дополнительные параметры, например, количество товаров на складе, общий результат поиска и текущую страницу. Встроенные классы пагинации, конечно, хороши, но... иногда нужно немного больше свободы! Так что было бы неплохо научиться создавать кастомные классы пагинации.

Кастомизация пагинации в DRF позволяет:

  • Добавлять в ответ API дополнительную информацию, например, общее количество объектов, страниц и т.д.;
  • Менять формат ответа (например, сделать его проще для фронтенда);
  • Использовать уникальную логику выбора данных для определённых страниц.

Создание кастомного класса пагинации

Шаг 1: Наследуемся от базового класса

Кастомизация пагинации в DRF начинается с создания собственного класса, который наследуется от одного из встроенных базовых классов пагинации, таких как:

  • PageNumberPagination (для пагинации по номеру страницы);
  • LimitOffsetPagination (для пагинации с лимитом и смещением).

Для нашего примера давайте кастомизируем PageNumberPagination, чтобы вернуть дополнительные данные о количестве страниц и текущей странице в ответе API.

from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response

class CustomPageNumberPagination(PageNumberPagination):
    # Задаём количество объектов на одной странице
    page_size = 10

    # Переопределяем метод для изменения формата ответа
    def get_paginated_response(self, data):
        return Response({
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link(),
            },
            'total_items': self.page.paginator.count,  # Общее количество объектов
            'total_pages': self.page.paginator.num_pages,  # Число страниц
            'current_page': self.page.number,  # Текущая страница
            'page_size': self.page_size,  # Размер страницы
            'results': data,  # Результаты текущей страницы
        })

Давайте разберём код выше. Мы унаследовались от PageNumberPagination и переопределили метод get_paginated_response(). Теперь наш API будет возвращать более информативный ответ, который не только содержит данные текущей страницы, но и метаданные о пагинации.

Шаг 2: Подключаем кастомную пагинацию

Теперь добавим наш кастомный класс пагинации в представление. Предположим, у нас есть модель Product (товары) и связанное с ней представление API.

from rest_framework.generics import ListAPIView
from .models import Product
from .serializers import ProductSerializer
from .pagination import CustomPageNumberPagination

class ProductListView(ListAPIView):
    queryset = Product.objects.all()
    serializer_class = ProductSerializer
    pagination_class = CustomPageNumberPagination  # Указываем наш кастомный класс пагинации

Запрос к нашему эндпоинту теперь вернёт данные с дополнительной информацией:

{
    "links": {
        "next": "http://localhost:8000/products/?page=2",
        "previous": null
    },
    "total_items": 150,
    "total_pages": 15,
    "current_page": 1,
    "page_size": 10,
    "results": [
        {
            "id": 1,
            "name": "Товар 1",
            "price": 100.0
        },
        ...
    ]
}

Настройка информации о текущей странице

Допустим, фронтендщики с нашего проекта попросили нас убрать ссылки next и previous, а вместо них добавить is_last_page и is_first_page. Что ж, давайте сделаем их счастливыми!

Вот обновлённый код кастомной пагинации:

class CustomPageNumberPagination(PageNumberPagination):
    page_size = 10

    def get_paginated_response(self, data):
        return Response({
            'total_items': self.page.paginator.count,
            'total_pages': self.page.paginator.num_pages,
            'current_page': self.page.number,
            'is_first_page': self.page.number == 1,  # Проверяем, первая ли это страница
            'is_last_page': not self.page.has_next(),  # Проверяем, последняя ли это страница
            'results': data,
        })

Теперь фронтенд получит такую структуру данных:

{
    "total_items": 150,
    "total_pages": 15,
    "current_page": 1,
    "is_first_page": true,
    "is_last_page": false,
    "results": [
        {
            "id": 1,
            "name": "Товар 1",
            "price": 100.0
        },
        ...
    ]
}

Расширенные сценарии кастомизации

Шаг 1: динамическая настройка размера страницы

Во многих случаях удобно дать клиенту возможность самостоятельно задавать размер страницы через параметр запроса, например, ?page_size=20. Это можно сделать, добавив в наш класс пагинации атрибут page_size_query_param.

class CustomPageNumberPagination(PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'  # Параметр, через который клиент указывает размер страницы
    max_page_size = 100  # Максимальный размер страницы, чтобы избежать перегрузки сервера

Теперь клиент может запросить данные с кастомным размером страницы, например:

GET /products/?page_size=20

Шаг 2: кастомизация для больших данных

Иногда может потребоваться использовать пагинацию с лимитом и смещением LimitOffsetPagination, но с уникальными требованиями. Например, добавить описание пагинации в метаинформацию.

from rest_framework.pagination import LimitOffsetPagination

class CustomLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 10  # Количество объектов по умолчанию
    max_limit = 50  # Максимальное количество объектов

    def get_paginated_response(self, data):
        return Response({
            'pagination': {
                'limit': self.limit,
                'offset': self.offset,
                'total_items': self.count,
            },
            'results': data,
        })

Теперь ответ нашего API будет выглядеть следующим образом:

{
    "pagination": {
        "limit": 10,
        "offset": 0,
        "total_items": 150
    },
    "results": [
        {
            "id": 1,
            "name": "Товар 1",
            "price": 100.0
        },
        ...
    ]
}

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

В процессе кастомизации пагинации есть несколько распространённых ошибок, которые стоит избегать. Например, если вы динамически позволяете устанавливать размер страницы, всегда ограничивайте его максимальное значение. Это помогает избежать ситуаций, когда недобросовестные пользователи пытаются запросить огромный объём данных за один раз, что может привести к снижению производительности вашего сервера.

Также важно учитывать, что информация о пагинации (например, количество страниц) может быть ресурсоёмкой, особенно для больших таблиц в базе данных. Если ваши данные часто меняются, убедитесь, что подсчёты обновляются корректно.

1
Задача
Модуль 3: Django, 19 уровень, 3 лекция
Недоступна
Создание простого класса кастомной пагинации
Создание простого класса кастомной пагинации
1
Задача
Модуль 3: Django, 19 уровень, 3 лекция
Недоступна
Создание кастомной пагинации с дополнительными мета-данными
Создание кастомной пагинации с дополнительными мета-данными
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ