class CursorPagination

from rest_framework.pagination import CursorPagination
Documentation Source code
    The cursor pagination implementation is neccessarily complex.
    For an overview of the position/offset style we use, see this post:

Ancestors (MRO)

  1. CursorPagination
  2. BasePagination


  Defined in
cursor_query_param = cursor CursorPagination
display_page_controls = False BasePagination
invalid_cursor_message = Invalid cursor CursorPagination
ordering = -created CursorPagination
page_size = None CursorPagination
template = rest_framework/pagination/previous_and_next.html CursorPagination
Expand Collapse


def _get_position_from_instance(self, instance, ordering): CursorPagination

    def _get_position_from_instance(self, instance, ordering):
        attr = getattr(instance, ordering[0].lstrip('-'))
        return six.text_type(attr)

def decode_cursor(self, request): CursorPagination

        Given a request with a cursor, return a `Cursor` instance.
    def decode_cursor(self, request):
        Given a request with a cursor, return a `Cursor` instance.
        # Determine if we have a cursor, and if so then decode it.
        encoded = request.query_params.get(self.cursor_query_param)
        if encoded is None:
            return None

        # The offset in the cursor is used in situations where we have a
        # nearly-unique index. (Eg millisecond precision creation timestamps)
        # We guard against malicious users attempting to cause expensive database
        # queries, by having a hard cap on the maximum possible size of the offset.
        OFFSET_CUTOFF = 1000

            querystring = b64decode(encoded.encode('ascii')).decode('ascii')
            tokens = urlparse.parse_qs(querystring, keep_blank_values=True)

            offset = tokens.get('o', ['0'])[0]
            offset = _positive_int(offset, cutoff=OFFSET_CUTOFF)

            reverse = tokens.get('r', ['0'])[0]
            reverse = bool(int(reverse))

            position = tokens.get('p', [None])[0]
        except (TypeError, ValueError):
            raise NotFound(self.invalid_cursor_message)

        return Cursor(offset=offset, reverse=reverse, position=position)

def encode_cursor(self, cursor): CursorPagination

        Given a Cursor instance, return an url with encoded cursor.
    def encode_cursor(self, cursor):
        Given a Cursor instance, return an url with encoded cursor.
        tokens = {}
        if cursor.offset != 0:
            tokens['o'] = str(cursor.offset)
        if cursor.reverse:
            tokens['r'] = '1'
        if cursor.position is not None:
            tokens['p'] = cursor.position

        querystring = urlparse.urlencode(tokens, doseq=True)
        encoded = b64encode(querystring.encode('ascii')).decode('ascii')
        return replace_query_param(self.base_url, self.cursor_query_param, encoded)

def get_html_context(self): CursorPagination

    def get_html_context(self):
        return {
            'previous_url': self.get_previous_link(),
            'next_url': self.get_next_link()

def get_next_link(self): CursorPagination

def get_ordering(self, request, queryset, view): CursorPagination

        Return a tuple of strings, that may be used in an `order_by` method.
    def get_ordering(self, request, queryset, view):
        Return a tuple of strings, that may be used in an `order_by` method.
        ordering_filters = [
            filter_cls for filter_cls in getattr(view, 'filter_backends', [])
            if hasattr(filter_cls, 'get_ordering')

        if ordering_filters:
            # If a filter exists on the view that implements `get_ordering`
            # then we defer to that filter to determine the ordering.
            filter_cls = ordering_filters[0]
            filter_instance = filter_cls()
            ordering = filter_instance.get_ordering(request, queryset, view)
            assert ordering is not None, (
                'Using cursor pagination, but filter class {filter_cls} '
                'returned a `None` ordering.'.format(
            # The default case is to check for an `ordering` attribute
            # on this pagination instance.
            ordering = self.ordering
            assert ordering is not None, (
                'Using cursor pagination, but no ordering attribute was declared '
                'on the pagination class.'
            assert '__' not in ordering, (
                'Cursor pagination does not support double underscore lookups '
                'for orderings. Orderings should be an unchanging, unique or '
                'nearly-unique field on the model, such as "-created" or "pk".'

        assert isinstance(ordering, (six.string_types, list, tuple)), (
            'Invalid ordering. Expected string or tuple, but got {type}'.format(

        if isinstance(ordering, six.string_types):
            return (ordering,)
        return tuple(ordering)

def get_page_size(self, request): CursorPagination

    def get_page_size(self, request):
        return self.page_size

def get_paginated_response(self, data):


    def get_paginated_response(self, data):
        return Response(OrderedDict([
            ('next', self.get_next_link()),
            ('previous', self.get_previous_link()),
            ('results', data)


    def get_paginated_response(self, data):  # pragma: no cover
        raise NotImplementedError('get_paginated_response() must be implemented.')

def get_previous_link(self): CursorPagination

def get_results(self, data): BasePagination

    def get_results(self, data):
        return data['results']

def paginate_queryset(self, queryset, request, view=None):


    def paginate_queryset(self, queryset, request, view=None):
        self.page_size = self.get_page_size(request)
        if not self.page_size:
            return None

        self.base_url = request.build_absolute_uri()
        self.ordering = self.get_ordering(request, queryset, view)

        self.cursor = self.decode_cursor(request)
        if self.cursor is None:
            (offset, reverse, current_position) = (0, False, None)
            (offset, reverse, current_position) = self.cursor

        # Cursor pagination always enforces an ordering.
        if reverse:
            queryset = queryset.order_by(*_reverse_ordering(self.ordering))
            queryset = queryset.order_by(*self.ordering)

        # If we have a cursor with a fixed position then filter by that.
        if current_position is not None:
            order = self.ordering[0]
            is_reversed = order.startswith('-')
            order_attr = order.lstrip('-')

            # Test for: (cursor reversed) XOR (queryset reversed)
            if self.cursor.reverse != is_reversed:
                kwargs = {order_attr + '__lt': current_position}
                kwargs = {order_attr + '__gt': current_position}

            queryset = queryset.filter(**kwargs)

        # If we have an offset cursor then offset the entire page by that amount.
        # We also always fetch an extra item in order to determine if there is a
        # page following on from this one.
        results = list(queryset[offset:offset + self.page_size + 1]) = list(results[:self.page_size])

        # Determine the position of the final item following the page.
        if len(results) > len(
            has_following_postion = True
            following_position = self._get_position_from_instance(results[-1], self.ordering)
            has_following_postion = False
            following_position = None

        # If we have a reverse queryset, then the query ordering was in reverse
        # so we need to reverse the items again before returning them to the user.
        if reverse:
   = list(reversed(

        if reverse:
            # Determine next and previous positions for reverse cursors.
            self.has_next = (current_position is not None) or (offset > 0)
            self.has_previous = has_following_postion
            if self.has_next:
                self.next_position = current_position
            if self.has_previous:
                self.previous_position = following_position
            # Determine next and previous positions for forward cursors.
            self.has_next = has_following_postion
            self.has_previous = (current_position is not None) or (offset > 0)
            if self.has_next:
                self.next_position = following_position
            if self.has_previous:
                self.previous_position = current_position

        # Display page controls in the browsable API if there is more
        # than one page.
        if (self.has_previous or self.has_next) and self.template is not None:
            self.display_page_controls = True



    def paginate_queryset(self, queryset, request, view=None):  # pragma: no cover
        raise NotImplementedError('paginate_queryset() must be implemented.')

def to_html(self):


    def to_html(self):
        template = loader.get_template(self.template)
        context = Context(self.get_html_context())
        return template.render(context)


    def to_html(self):  # pragma: no cover
        raise NotImplementedError('to_html() must be implemented to display page controls.')