Pagination

Django provides a few classes that help you manage paginated data – that is, data that’s split across several pages, with “Previous/Next” links.

Django documentation

REST framework includes support for customizable pagination styles. This allows you to modify how large result sets are split into individual pages of data.

The pagination API can support either:

The built-in styles currently all use links included as part of the content of the response. This style is more accessible when using the browsable API.

Pagination is only performed automatically if you're using the generic views or viewsets. If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the mixins.ListModelMixin and generics.GenericAPIView classes for an example.

Pagination can be turned off by setting the pagination class to None.

Setting the pagination style

The pagination style may be set globally, using the DEFAULT_PAGINATION_CLASS and PAGE_SIZE setting keys. For example, to use the built-in limit/offset pagination, you would do something like this:

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

Note that you need to set both the pagination class, and the page size that should be used. Both DEFAULT_PAGINATION_CLASS and PAGE_SIZE are None by default.

You can also set the pagination class on an individual view by using the pagination_class attribute. Typically you'll want to use the same pagination style throughout your API, although you might want to vary individual aspects of the pagination, such as default or maximum page size, on a per-view basis.

Modifying the pagination style

If you want to modify particular aspects of the pagination style, you'll want to override one of the pagination classes, and set the attributes that you want to change.

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

You can then apply your new style to a view using the pagination_class attribute:

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

Or apply the style globally, using the DEFAULT_PAGINATION_CLASS settings key. For example:

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

API Reference

PageNumberPagination

This pagination style accepts a single number page number in the request query parameters.

Request:

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

Response:

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

Setup

To enable the PageNumberPagination style globally, use the following configuration, and set the PAGE_SIZE as desired:

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

On GenericAPIView subclasses you may also set the pagination_class attribute to select PageNumberPagination on a per-view basis.

Configuration

The PageNumberPagination class includes a number of attributes that may be overridden to modify the pagination style.

To set these attributes you should override the PageNumberPagination class, and then enable your custom pagination class as above.


LimitOffsetPagination

This pagination style mirrors the syntax used when looking up multiple database records. The client includes both a "limit" and an "offset" query parameter. The limit indicates the maximum number of items to return, and is equivalent to the page_size in other styles. The offset indicates the starting position of the query in relation to the complete set of unpaginated items.

Request:

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

Response:

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": [
       …
    ]
}

Setup

To enable the LimitOffsetPagination style globally, use the following configuration:

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

Optionally, you may also set a PAGE_SIZE key. If the PAGE_SIZE parameter is also used then the limit query parameter will be optional, and may be omitted by the client.

On GenericAPIView subclasses you may also set the pagination_class attribute to select LimitOffsetPagination on a per-view basis.

Configuration

The LimitOffsetPagination class includes a number of attributes that may be overridden to modify the pagination style.

To set these attributes you should override the LimitOffsetPagination class, and then enable your custom pagination class as above.


CursorPagination

The cursor-based pagination presents an opaque "cursor" indicator that the client may use to page through the result set. This pagination style only presents forward and reverse controls, and does not allow the client to navigate to arbitrary positions.

Cursor based pagination requires that there is a unique, unchanging ordering of items in the result set. This ordering might typically be a creation timestamp on the records, as this presents a consistent ordering to paginate against.

Cursor based pagination is more complex than other schemes. It also requires that the result set presents a fixed ordering, and does not allow the client to arbitrarily index into the result set. However it does provide the following benefits:

Details and limitations

Proper use of cursor based pagination requires a little attention to detail. You'll need to think about what ordering you want the scheme to be applied against. The default is to order by "-created". This assumes that there must be a 'created' timestamp field on the model instances, and will present a "timeline" style paginated view, with the most recently added items first.

You can modify the ordering by overriding the 'ordering' attribute on the pagination class, or by using the OrderingFilter filter class together with CursorPagination. When used with OrderingFilter you should strongly consider restricting the fields that the user may order by.

Proper usage of cursor pagination should have an ordering field that satisfies the following:

Using an ordering field that does not satisfy these constraints will generally still work, but you'll be losing some of the benefits of cursor pagination.

For more technical details on the implementation we use for cursor pagination, the "Building cursors for the Disqus API" blog post gives a good overview of the basic approach.

Setup

To enable the CursorPagination style globally, use the following configuration, modifying the PAGE_SIZE as desired:

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

On GenericAPIView subclasses you may also set the pagination_class attribute to select CursorPagination on a per-view basis.

Configuration

The CursorPagination class includes a number of attributes that may be overridden to modify the pagination style.

To set these attributes you should override the CursorPagination class, and then enable your custom pagination class as above.


Custom pagination styles

To create a custom pagination serializer class you should subclass pagination.BasePagination and override the paginate_queryset(self, queryset, request, view=None) and get_paginated_response(self, data) methods:

Note that the paginate_queryset method may set state on the pagination instance, that may later be used by the get_paginated_response method.

Example

Suppose we want to replace the default pagination output style with a modified format that includes the next and previous links under in a nested 'links' key. We could specify a custom pagination class like so:

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
        })

We'd then need to setup the custom class in our configuration:

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

Note that if you care about how the ordering of keys is displayed in responses in the browsable API you might choose to use an OrderedDict when constructing the body of paginated responses, but this is optional.

Using your custom pagination class

To have your custom pagination class be used by default, use the DEFAULT_PAGINATION_CLASS setting:

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

API responses for list endpoints will now include a Link header, instead of including the pagination links as part of the body of the response, for example:

Pagination & schemas

You can also make the pagination controls available to the schema autogeneration that REST framework provides, by implementing a get_schema_fields() method. This method should have the following signature:

get_schema_fields(self, view)

The method should return a list of coreapi.Field instances.


Link Header

A custom pagination style, using the 'Link' header'


HTML pagination controls

By default using the pagination classes will cause HTML pagination controls to be displayed in the browsable API. There are two built-in display styles. The PageNumberPagination and LimitOffsetPagination classes display a list of page numbers with previous and next controls. The CursorPagination class displays a simpler style that only displays a previous and next control.

Customizing the controls

You can override the templates that render the HTML pagination controls. The two built-in styles are:

Providing a template with either of these paths in a global template directory will override the default rendering for the relevant pagination classes.

Alternatively you can disable HTML pagination controls completely by subclassing on of the existing classes, setting template = None as an attribute on the class. You'll then need to configure your DEFAULT_PAGINATION_CLASS settings key to use your custom class as the default pagination style.

Low-level API

The low-level API for determining if a pagination class should display the controls or not is exposed as a display_page_controls attribute on the pagination instance. Custom pagination classes should be set to True in the paginate_queryset method if they require the HTML pagination controls to be displayed.

The .to_html() and .get_html_context() methods may also be overridden in a custom pagination class in order to further customize how the controls are rendered.


Third party packages

The following third party packages are also available.

DRF-extensions

The DRF-extensions package includes a PaginateByMaxMixin mixin class that allows your API clients to specify ?page_size=max to obtain the maximum allowed page size.

drf-proxy-pagination

The drf-proxy-pagination package includes a ProxyPagination class which allows to choose pagination class with a query parameter.

The django-rest-framework-link-header-pagination package includes a LinkHeaderPagination class which provides pagination via an HTTP Link header as desribed in Github's developer documentation.