# Пагинация

***

источник:

* pagination.py

***

## Пагинация

> Django предоставляет несколько классов, которые помогают вам управлять постраничными данными - то есть данными, разделенными на несколько страниц, со ссылками "Предыдущая/Следующая".
>
> — [Django documentation](https://docs.djangoproject.com/en/stable/topics/pagination/)

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

API пагинации может поддерживать любую из этих функций:

* Ссылки пагинации, которые предоставляются как часть содержимого ответа.
* Ссылки пагинации, включенные в заголовки ответа, такие как `Content-Range` или `Link`.

В настоящее время все встроенные стили используют ссылки, включенные как часть содержимого ответа. Этот стиль более доступен при использовании API с возможностью просмотра.

Пагинация выполняется автоматически, только если вы используете общие представления или наборы представлений. Если вы используете обычное `APIView`, вам нужно будет самостоятельно обратиться к API пагинации, чтобы убедиться, что вы возвращаете ответ с пагинацией. Пример смотрите в исходном коде классов `mixins.ListModelMixin` и `generics.GenericAPIView`.

Пагинацию можно отключить, установив для класса пагинации значение `None`.

### Установка стиля пагинации

Стиль пагинации можно задать глобально, используя ключи настройки `DEFAULT_PAGINATION_CLASS` и `PAGE_SIZE`. Например, чтобы использовать встроенную пагинацию с ограничением/смещением, вы должны сделать следующее:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 100
}
```

Обратите внимание, что необходимо задать как класс пагинации, так и размер страницы, которая будет использоваться. По умолчанию и `DEFAULT_PAGINATION_CLASS`, и `PAGE_SIZE` имеют значение `None`.

Вы также можете установить класс пагинации для отдельного представления с помощью атрибута `pagination_class`. Обычно вы хотите использовать один и тот же стиль пагинации во всем API, хотя вы можете захотеть варьировать отдельные аспекты пагинации, такие как размер страницы по умолчанию или максимальный размер страницы, на основе каждого представления.

### Изменение стиля пагинации

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

```python
class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'page_size'
    max_page_size = 10000

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 100
    page_size_query_param = 'page_size'
    max_page_size = 1000
```

Затем вы можете применить ваш новый стиль к представлению с помощью атрибута `pagination_class`:

```python
class BillingRecordsView(generics.ListAPIView):
    queryset = Billing.objects.all()
    serializer_class = BillingRecordsSerializer
    pagination_class = LargeResultsSetPagination
```

Или примените стиль глобально, используя ключ настройки `DEFAULT_PAGINATION_CLASS`. Например:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'apps.core.pagination.StandardResultsSetPagination'
}
```

***

## Описание API

### PageNumberPagination

Этот стиль пагинации принимает номер страницы с одним номером в параметрах запроса.

**Запрос**:

```http
GET https://api.example.org/accounts/?page=4
```

**Ответ**:

```http
HTTP 200 OK
{
    "count": 1023,
    "next": "https://api.example.org/accounts/?page=5",
    "previous": "https://api.example.org/accounts/?page=3",
    "results": [
       …
    ]
}
```

**Настройка**

Чтобы включить стиль `PageNumberPagination` глобально, используйте следующую конфигурацию и установите `PAGE_SIZE` по желанию:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100
}
```

В подклассах `GenericAPIView` вы также можете установить атрибут `pagination_class` для выбора `PageNumberPagination` на основе каждого вида.

**Конфигурация**

Класс `PageNumberPagination` включает ряд атрибутов, которые могут быть переопределены для изменения стиля пагинации.

Чтобы установить эти атрибуты, необходимо переопределить класс `PageNumberPagination`, а затем включить свой собственный класс пагинации, как указано выше.

* `django_paginator_class` - Класс Django Paginator, который будет использоваться. По умолчанию это `django.core.paginator.Paginator`, что должно быть хорошо для большинства случаев использования.
* `page_size` - Числовое значение, указывающее размер страницы. Если установлено, оно отменяет настройку `PAGE_SIZE`. По умолчанию имеет то же значение, что и ключ настройки `PAGE_SIZE`.
* `page_query_param` - Строковое значение, указывающее имя параметра запроса, который будет использоваться для управления пагинацией.
* `page_size_query_param` - Если установлено, это строковое значение, указывающее имя параметра запроса, который позволяет клиенту устанавливать размер страницы на основе каждого запроса. По умолчанию `None`, что означает, что клиент не может контролировать размер запрашиваемой страницы.
* `max_page_size` - Если установлено, это числовое значение, указывающее на максимально допустимый размер запрашиваемой страницы. Этот атрибут действителен, только если `page_size_query_param` также установлен.
* `last_page_strings` - Список или кортеж строковых значений, указывающих на значения, которые могут быть использованы с `page_query_param` для запроса последней страницы в наборе. По умолчанию `('last',)`
* `template` - Имя шаблона для использования при отображении элементов управления пагинацией в Web-интерфейсе API. Может быть переопределено для изменения стиля рендеринга или установлено в `None` для полного отключения HTML элементов управления пагинацией. По умолчанию используется `"rest_framework/pagination/numbers.html"`.

***

### LimitOffsetPagination

Этот стиль пагинации повторяет синтаксис, используемый при поиске нескольких записей в базе данных. Клиент включает в себя как `'limit'`, так и параметр запроса `'offset'`. Лимит указывает на максимальное количество возвращаемых элементов и эквивалентен `page_size` в других стилях. Смещение указывает начальную позицию запроса по отношению к полному набору непагинированных элементов.

**Запрос**:

```http
GET https://api.example.org/accounts/?limit=100&offset=400
```

**Ответ**:

```http
HTTP 200 OK
{
    "count": 1023,
    "next": "https://api.example.org/accounts/?limit=100&offset=500",
    "previous": "https://api.example.org/accounts/?limit=100&offset=300",
    "results": [
       …
    ]
}
```

**Подключение**

Чтобы включить стиль `LimitOffsetPagination` глобально, используйте следующую конфигурацию:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
}
```

По желанию вы также можете задать ключ `PAGE_SIZE`. Если параметр `PAGE_SIZE` также используется, то параметр запроса `limit` будет необязательным и может быть опущен клиентом.

В подклассах `GenericAPIView` вы также можете установить атрибут `pagination_class` для выбора `LimitOffsetPagination` для каждого представления.

**Настройка**

Класс `LimitOffsetPagination` включает ряд атрибутов, которые могут быть переопределены для изменения стиля пагинации.

Чтобы установить эти атрибуты, вы должны переопределить класс `LimitOffsetPagination`, а затем включить свой собственный класс пагинации, как указано выше.

* `default_limit` - Числовое значение, указывающее предел, который следует использовать, если он не указан клиентом в параметре запроса. По умолчанию имеет то же значение, что и ключ настройки `PAGE_SIZE`.
* `limit_query_param` - Строковое значение, указывающее имя параметра запроса "limit". По умолчанию имеет значение `'limit'`.
* `offset_query_param` - Строковое значение, указывающее имя параметра запроса "offset". По умолчанию имеет значение `'offset'`.
* `max_limit` - Если установлено, то это числовое значение, указывающее на максимально допустимый лимит, который может быть запрошен клиентом. По умолчанию `None`.
* `template` - Имя шаблона, который будет использоваться при отображении элементов управления пагинацией в Web-интерфейсе API. Может быть переопределено для изменения стиля рендеринга или установлено в `None` для полного отключения HTML элементов управления пагинацией. По умолчанию используется `"rest_framework/pagination/numbers.html"`.

***

### CursorPagination

Пагинация на основе курсора представляет непрозрачный индикатор `cursor`, который клиент может использовать для просмотра набора результатов. Этот стиль пагинации представляет только элементы управления перемоткой вперед и назад и не позволяет клиенту переходить к произвольным позициям.

Пагинация на основе курсора требует наличия уникального, неизменного порядка следования элементов в наборе результатов. Обычно таким упорядочиванием может быть временная метка создания записей, так как она представляет собой последовательный порядок для постраничного просмотра.

Пагинация на основе курсора является более сложной, чем другие схемы. Она также требует, чтобы набор результатов представлял фиксированный порядок, и не позволяет клиенту произвольно индексировать набор результатов. Однако она обеспечивает следующие преимущества:

* Обеспечивает последовательное представление пагинации. При правильном использовании `CursorPagination` гарантирует, что клиент никогда не увидит один и тот же элемент дважды при листании записей, даже если новые элементы вставляются другими клиентами во время процесса пагинации.
* Поддержка использования с очень большими наборами данных. При работе с очень большими наборами данных пагинация с использованием стилей пагинации на основе смещения может стать неэффективной или непригодной для использования. Вместо этого схемы пагинации на основе курсора имеют свойства фиксированного времени и не замедляются при увеличении размера набора данных.

**Подробности и ограничения**

Правильное использование пагинации на основе курсора требует некоторого внимания к деталям. Вам нужно подумать о том, в каком порядке вы хотите применять схему. По умолчанию используется порядок по `"-created"`. Это предполагает, что **в экземплярах модели должно быть поле временной метки "created"**, и будет представлено постраничное представление в стиле "временной шкалы", где первыми будут самые последние добавленные элементы.

Вы можете изменить порядок, переопределив атрибут `'ordering'` класса пагинации, или используя класс фильтра `OrderingFilter` вместе с `CursorPagination`. При использовании `OrderingFilter` следует тщательно продумать ограничение полей, по которым пользователь может делать заказ.

Правильное использование пагинации курсора должно иметь поле упорядочивания, которое удовлетворяет следующим требованиям:

* Должно быть неизменным значением, таким как временная метка, slug или другое поле, которое устанавливается только один раз, при создании.
* Должно быть уникальным или почти уникальным. Хорошим примером являются временные метки с точностью до миллисекунды. Эта реализация пагинации курсора использует интеллектуальный стиль "позиция плюс смещение", что позволяет ей правильно поддерживать не строго уникальные значения в качестве упорядочивания.
* Должно быть не нулевым значением, которое можно принудительно преобразовать в строку.
* Не должно быть плавающей точкой. Ошибки точности легко приводят к неправильным результатам. **Совет**: используйте вместо этого десятичные числа. (Если у вас уже есть поле с плавающей запятой и вам нужно сделать постраничную запись по нему, можно воспользоваться командой \[пример подкласса `CursorPagination`, который использует десятичные числа для ограничения точности, доступен здесь] (https://gist.github.com/keturn/8bc88525a183fd41c73ffb729b8865be#file-fpcursorpagination-py).)
* Поле должно иметь индекс базы данных.

Использование поля упорядочивания, которое не удовлетворяет этим ограничениям, как правило, будет работать, но вы потеряете некоторые преимущества пагинации курсора.

Для получения более подробной технической информации о реализации, которую мы используем для пагинации курсоров, в статье ["Building cursors for the Disqus API"](https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api) блога дается хороший обзор основного подхода.

**Подключение**

Чтобы включить стиль `CursorPagination` глобально, используйте следующую конфигурацию, изменяя `PAGE_SIZE` по желанию:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.CursorPagination',
    'PAGE_SIZE': 100
}
```

В подклассах `GenericAPIView` вы также можете установить атрибут `pagination_class` для выбора `CursorPagination` на основе каждого вида.

**Настройка**

Класс `CursorPagination` включает ряд атрибутов, которые могут быть переопределены для изменения стиля пагинации.

Чтобы установить эти атрибуты, необходимо переопределить класс `CursorPagination`, а затем включить свой собственный класс пагинации, как указано выше.

* `page_size` - числовое значение, указывающее размер страницы. Если установлено, оно отменяет настройку `PAGE_SIZE`. По умолчанию имеет то же значение, что и ключ настройки `PAGE_SIZE`.
* `cursor_query_param` - Строковое значение, указывающее имя параметра запроса "cursor". По умолчанию `'cursor'`.
* `orderering` - Это должна быть строка или список строк, указывающих на поле, к которому будет применяться пагинация на основе курсора. Например: `ordering = 'slug'`. По умолчанию используется `-created`. Это значение также может быть переопределено с помощью `OrderingFilter` в представлении.
* `template` - Имя шаблона, который будет использоваться при отображении элементов управления пагинацией в API просмотра. Может быть переопределено для изменения стиля рендеринга или установлено в `None` для полного отключения HTML элементов управления пагинацией. По умолчанию используется `"rest_framework/pagination/previous_and_next.html"`.

***

## Пользовательские стили пагинации

Чтобы создать собственный класс сериализатора пагинации, необходимо унаследовать подкласс `pagination.BasePagination`, переопределить методы `paginate_queryset(self, queryset, request, view=None)` и `get_paginated_response(self, data)`:

* Метод `paginate_queryset` передается начальному кверисету и должен возвращать итерируемый объект. Этот объект содержит только данные запрашиваемой страницы.
* Метод `get_paginated_response` передается сериализованным данным страницы и должен возвращать экземпляр `Response`.

Обратите внимание, что метод `paginate_queryset` может установить состояние экземпляра пагинации, которое впоследствии может быть использовано методом `get_paginated_response`.

### Пример

Предположим, мы хотим заменить стандартный стиль вывода пагинации на модифицированный формат, который включает следующую и предыдущую ссылки во вложенном ключе `'links'`. Мы можем указать пользовательский класс пагинации следующим образом:

```python
class CustomPagination(pagination.PageNumberPagination):
    def get_paginated_response(self, data):
        return Response({
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link()
            },
            'count': self.page.paginator.count,
            'results': data
        })
```

Затем нам нужно будет установить пользовательский класс в нашей конфигурации:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.CustomPagination',
    'PAGE_SIZE': 100
}
```

Обратите внимание, что если вам важно, как порядок ключей отображается в ответах в Web-интерфейсе API, вы можете использовать `OrderedDict` при построении тела постраничных ответов, но это необязательно.

### Использование вашего пользовательского класса пагинации

Чтобы ваш пользовательский класс пагинации использовался по умолчанию, используйте параметр `DEFAULT_PAGINATION_CLASS`:

```python
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'my_project.apps.core.pagination.LinkHeaderPagination',
    'PAGE_SIZE': 100
}
```

Ответы API для конечных точек списка теперь будут включать заголовок `Link`, вместо того чтобы, например, включать ссылки пагинации как часть тела ответа:

![](https://github.com/encode/django-rest-framework/raw/master/docs/img/link-header-pagination.png)

_Настраиваемый стиль пагинации с использованием заголовка `Link`._

***

## Элементы управления пагинацией HTML

По умолчанию использование классов пагинации приводит к отображению элементов управления пагинацией HTML в Web-интерфейсе API. Существует два встроенных стиля отображения. Классы `PageNumberPagination` и `LimitOffsetPagination` отображают список номеров страниц с предыдущим и следующим элементами управления. Класс `CursorPagination` отображает более простой стиль, в котором отображаются только предыдущий и следующий элементы управления.

### Настройка элементов управления

Вы можете переопределить шаблоны, которые отображают элементы управления пагинацией HTML. Есть два встроенных стиля:

* `'rest_framework/pagination/numbers.html'`
* `'rest_framework/pagination/previous_and_next.html'`

Предоставление шаблона с любым из этих путей в глобальном каталоге шаблонов переопределит рендеринг по умолчанию для соответствующих классов пагинации.

В качестве альтернативы вы можете полностью отключить элементы управления HTML-пагинацией, создав подкласс одного из существующих классов и установив `template = None` в качестве атрибута класса. Затем вам нужно будет настроить ключ параметров `DEFAULT_PAGINATION_CLASS`, чтобы использовать ваш пользовательский класс в качестве стиля пагинации по умолчанию.

**Низкоуровневый API**

Низкоуровневый API для определения того, должен ли класс пагинации отображать элементы управления или нет, раскрывается как атрибут `display_page_controls` на экземпляре пагинации. Пользовательские классы пагинации должны быть установлены в `True` в методе `paginate_queryset`, если они требуют отображения элементов управления пагинацией HTML.

Методы `.to_html()` и `.get_html_context()` также могут быть переопределены в пользовательском классе пагинации для дальнейшей настройки отображения элементов управления.

***

## Пакеты сторонних производителей

Также доступны следующие пакеты сторонних производителей.

### DRF-extensions

Пакет [`DRF-extensions`](https://chibisov.github.io/drf-extensions/docs/) включает класс-миксин [`PaginateByMaxMixin`](https://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin), который позволяет вашим клиентам API указывать `?page_size=max` для получения максимально допустимого размера страницы.

### drf-proxy-pagination

Пакет [`drf-proxy-pagination`](https://github.com/tuffnatty/drf-proxy-pagination) включает класс `ProxyPagination`, который позволяет выбирать класс пагинации с помощью параметра запроса.

### link-header-pagination

Пакет [`django-rest-framework-link-header-pagination`](https://github.com/tbeadle/django-rest-framework-link-header-pagination) включает класс `LinkHeaderPagination`, который обеспечивает пагинацию через HTTP-заголовок `Link`, как описано в [документации GitHub REST API](https://docs.github.com/en/rest/guides/traversing-with-pagination).
