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
        },
        ...
    ]
}

Тонкощі та типові помилки

У процесі кастомізації пагінації є кілька поширених помилок, яких варто уникати. Наприклад, якщо ви динамічно дозволяєте встановлювати розмір сторінки, завжди обмежуйте його максимальне значення. Це допомагає уникнути ситуацій, коли недобросовісні користувачі намагаються запросити величезний обсяг даних за один раз, що може призвести до зниження продуктивності вашого сервера.

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

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ