diff options
| -rw-r--r-- | rest_framework/pagination.py | 66 | ||||
| -rw-r--r-- | rest_framework/static/rest_framework/css/bootstrap-tweaks.css | 12 | ||||
| -rw-r--r-- | rest_framework/templates/rest_framework/pagination/previous_and_next.html | 12 | ||||
| -rw-r--r-- | tests/test_pagination.py | 5 | 
4 files changed, 78 insertions, 17 deletions
| diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 1b4174bc..b3658aca 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -133,9 +133,14 @@ def _decode_cursor(encoded):      try:          querystring = b64decode(encoded.encode('ascii')).decode('ascii')          tokens = urlparse.parse_qs(querystring, keep_blank_values=True) -        offset = _positive_int(tokens['offset'][0]) -        reverse = bool(int(tokens['reverse'][0])) -        position = tokens.get('position', [None])[0] + +        offset = tokens.get('o', ['0'])[0] +        offset = _positive_int(offset) + +        reverse = tokens.get('r', ['0'])[0] +        reverse = bool(int(reverse)) + +        position = tokens.get('p', [None])[0]      except (TypeError, ValueError):          return None @@ -146,12 +151,13 @@ def _encode_cursor(cursor):      """      Given a Cursor instance, return an encoded string representation.      """ -    tokens = { -        'offset': str(cursor.offset), -        'reverse': '1' if cursor.reverse else '0', -    } +    tokens = {} +    if cursor.offset != 0: +        tokens['o'] = str(cursor.offset) +    if cursor.reverse: +        tokens['r'] = '1'      if cursor.position is not None: -        tokens['position'] = cursor.position +        tokens['p'] = cursor.position      querystring = urlparse.urlencode(tokens, doseq=True)      return b64encode(querystring.encode('ascii')).decode('ascii') @@ -430,10 +436,12 @@ class CursorPagination(BasePagination):      # Determine how/if True, False and None positions work - do the string      # encodings work with Django queryset filters?      # Consider a max offset cap. +    # Tidy up the `get_ordering` API (eg remove queryset from it)      cursor_query_param = 'cursor'      page_size = api_settings.PAGINATE_BY      invalid_cursor_message = _('Invalid cursor')      ordering = None +    template = 'rest_framework/pagination/previous_and_next.html'      def paginate_queryset(self, queryset, request, view=None):          self.base_url = request.build_absolute_uri() @@ -452,17 +460,22 @@ class CursorPagination(BasePagination):          # Cursor pagination always enforces an ordering.          if reverse: -            queryset = queryset.order_by(_reverse_ordering(self.ordering)) +            queryset = queryset.order_by(*_reverse_ordering(self.ordering))          else: -            queryset = queryset.order_by(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: -            primary_ordering_attr = self.ordering[0].lstrip('-') -            if self.cursor.reverse: -                kwargs = {primary_ordering_attr + '__lt': current_position} +            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}              else: -                kwargs = {primary_ordering_attr + '__gt': 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. @@ -501,6 +514,11 @@ class CursorPagination(BasePagination):              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: +            self.display_page_controls = True +          return self.page      def get_next_link(self): @@ -642,5 +660,23 @@ class CursorPagination(BasePagination):          return tuple(ordering)      def _get_position_from_instance(self, instance, ordering): -        attr = getattr(instance, ordering[0]) +        attr = getattr(instance, ordering[0].lstrip('-'))          return six.text_type(attr) + +    def get_paginated_response(self, data): +        return Response(OrderedDict([ +            ('next', self.get_next_link()), +            ('previous', self.get_previous_link()), +            ('results', data) +        ])) + +    def get_html_context(self): +        return { +            'previous_url': self.get_previous_link(), +            'next_url': self.get_next_link() +        } + +    def to_html(self): +        template = loader.get_template(self.template) +        context = Context(self.get_html_context()) +        return template.render(context) diff --git a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css index 15b42178..04f12ed3 100644 --- a/rest_framework/static/rest_framework/css/bootstrap-tweaks.css +++ b/rest_framework/static/rest_framework/css/bootstrap-tweaks.css @@ -63,10 +63,20 @@ a single block in the template.  .pagination>.disabled>a,  .pagination>.disabled>a:hover,  .pagination>.disabled>a:focus { -  cursor: default; +  cursor: not-allowed;    pointer-events: none;  } +.pager>.disabled>a, +.pager>.disabled>a:hover, +.pager>.disabled>a:focus { +  pointer-events: none; +} + +.pager .next { +  margin-left: 10px; +} +  /*=== dabapps bootstrap styles ====*/  html { diff --git a/rest_framework/templates/rest_framework/pagination/previous_and_next.html b/rest_framework/templates/rest_framework/pagination/previous_and_next.html new file mode 100644 index 00000000..eacbfff4 --- /dev/null +++ b/rest_framework/templates/rest_framework/pagination/previous_and_next.html @@ -0,0 +1,12 @@ +<ul class="pager"> +{% if previous_url %} +    <li class="previous"><a href="{{ previous_url }}">« Previous</a></li> +{% else %} +    <li class="previous disabled"><a href="#">« Previous</a></li> +{% endif %} +{% if next_url %} +    <li class="next"><a href="{{ next_url }}">Next »</a></li> +{% else %} +    <li class="next disabled"><a href="#">Next »</li> +{% endif %} +</ul> diff --git a/tests/test_pagination.py b/tests/test_pagination.py index 338be610..13bfb627 100644 --- a/tests/test_pagination.py +++ b/tests/test_pagination.py @@ -1,3 +1,4 @@ +# coding: utf-8  from __future__ import unicode_literals  from rest_framework import exceptions, generics, pagination, serializers, status, filters  from rest_framework.request import Request @@ -471,7 +472,7 @@ class TestCursorPagination:                      if item.created < int(created__lt)                  ]) -            def order_by(self, ordering): +            def order_by(self, *ordering):                  if ordering[0].startswith('-'):                      return MockQuerySet(list(reversed(self.items)))                  return self @@ -614,6 +615,8 @@ class TestCursorPagination:          assert current == [1, 1, 1, 1, 1]          assert next == [1, 2, 3, 4, 4] +        assert isinstance(self.pagination.to_html(), type('')) +  def test_get_displayed_page_numbers():      """ | 
