mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-22 17:47:04 +03:00
Add paging controls
This commit is contained in:
parent
0822c9e558
commit
43d983fae8
|
@ -133,9 +133,14 @@ def _decode_cursor(encoded):
|
||||||
try:
|
try:
|
||||||
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
|
querystring = b64decode(encoded.encode('ascii')).decode('ascii')
|
||||||
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
|
tokens = urlparse.parse_qs(querystring, keep_blank_values=True)
|
||||||
offset = _positive_int(tokens['offset'][0])
|
|
||||||
reverse = bool(int(tokens['reverse'][0]))
|
offset = tokens.get('o', ['0'])[0]
|
||||||
position = tokens.get('position', [None])[0]
|
offset = _positive_int(offset)
|
||||||
|
|
||||||
|
reverse = tokens.get('r', ['0'])[0]
|
||||||
|
reverse = bool(int(reverse))
|
||||||
|
|
||||||
|
position = tokens.get('p', [None])[0]
|
||||||
except (TypeError, ValueError):
|
except (TypeError, ValueError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -146,12 +151,13 @@ def _encode_cursor(cursor):
|
||||||
"""
|
"""
|
||||||
Given a Cursor instance, return an encoded string representation.
|
Given a Cursor instance, return an encoded string representation.
|
||||||
"""
|
"""
|
||||||
tokens = {
|
tokens = {}
|
||||||
'offset': str(cursor.offset),
|
if cursor.offset != 0:
|
||||||
'reverse': '1' if cursor.reverse else '0',
|
tokens['o'] = str(cursor.offset)
|
||||||
}
|
if cursor.reverse:
|
||||||
|
tokens['r'] = '1'
|
||||||
if cursor.position is not None:
|
if cursor.position is not None:
|
||||||
tokens['position'] = cursor.position
|
tokens['p'] = cursor.position
|
||||||
|
|
||||||
querystring = urlparse.urlencode(tokens, doseq=True)
|
querystring = urlparse.urlencode(tokens, doseq=True)
|
||||||
return b64encode(querystring.encode('ascii')).decode('ascii')
|
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
|
# Determine how/if True, False and None positions work - do the string
|
||||||
# encodings work with Django queryset filters?
|
# encodings work with Django queryset filters?
|
||||||
# Consider a max offset cap.
|
# Consider a max offset cap.
|
||||||
|
# Tidy up the `get_ordering` API (eg remove queryset from it)
|
||||||
cursor_query_param = 'cursor'
|
cursor_query_param = 'cursor'
|
||||||
page_size = api_settings.PAGINATE_BY
|
page_size = api_settings.PAGINATE_BY
|
||||||
invalid_cursor_message = _('Invalid cursor')
|
invalid_cursor_message = _('Invalid cursor')
|
||||||
ordering = None
|
ordering = None
|
||||||
|
template = 'rest_framework/pagination/previous_and_next.html'
|
||||||
|
|
||||||
def paginate_queryset(self, queryset, request, view=None):
|
def paginate_queryset(self, queryset, request, view=None):
|
||||||
self.base_url = request.build_absolute_uri()
|
self.base_url = request.build_absolute_uri()
|
||||||
|
@ -452,17 +460,22 @@ class CursorPagination(BasePagination):
|
||||||
|
|
||||||
# Cursor pagination always enforces an ordering.
|
# Cursor pagination always enforces an ordering.
|
||||||
if reverse:
|
if reverse:
|
||||||
queryset = queryset.order_by(_reverse_ordering(self.ordering))
|
queryset = queryset.order_by(*_reverse_ordering(self.ordering))
|
||||||
else:
|
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 we have a cursor with a fixed position then filter by that.
|
||||||
if current_position is not None:
|
if current_position is not None:
|
||||||
primary_ordering_attr = self.ordering[0].lstrip('-')
|
order = self.ordering[0]
|
||||||
if self.cursor.reverse:
|
is_reversed = order.startswith('-')
|
||||||
kwargs = {primary_ordering_attr + '__lt': current_position}
|
order_attr = order.lstrip('-')
|
||||||
|
|
||||||
|
# Test for: (cursor reversed) XOR (queryset reversed)
|
||||||
|
if self.cursor.reverse != is_reversed:
|
||||||
|
kwargs = {order_attr + '__lt': current_position}
|
||||||
else:
|
else:
|
||||||
kwargs = {primary_ordering_attr + '__gt': current_position}
|
kwargs = {order_attr + '__gt': current_position}
|
||||||
|
|
||||||
queryset = queryset.filter(**kwargs)
|
queryset = queryset.filter(**kwargs)
|
||||||
|
|
||||||
# If we have an offset cursor then offset the entire page by that amount.
|
# 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:
|
if self.has_previous:
|
||||||
self.previous_position = current_position
|
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
|
return self.page
|
||||||
|
|
||||||
def get_next_link(self):
|
def get_next_link(self):
|
||||||
|
@ -642,5 +660,23 @@ class CursorPagination(BasePagination):
|
||||||
return tuple(ordering)
|
return tuple(ordering)
|
||||||
|
|
||||||
def _get_position_from_instance(self, instance, 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)
|
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)
|
||||||
|
|
|
@ -63,10 +63,20 @@ a single block in the template.
|
||||||
.pagination>.disabled>a,
|
.pagination>.disabled>a,
|
||||||
.pagination>.disabled>a:hover,
|
.pagination>.disabled>a:hover,
|
||||||
.pagination>.disabled>a:focus {
|
.pagination>.disabled>a:focus {
|
||||||
cursor: default;
|
cursor: not-allowed;
|
||||||
pointer-events: none;
|
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 ====*/
|
/*=== dabapps bootstrap styles ====*/
|
||||||
|
|
||||||
html {
|
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>
|
|
@ -1,3 +1,4 @@
|
||||||
|
# coding: utf-8
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from rest_framework import exceptions, generics, pagination, serializers, status, filters
|
from rest_framework import exceptions, generics, pagination, serializers, status, filters
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
@ -471,7 +472,7 @@ class TestCursorPagination:
|
||||||
if item.created < int(created__lt)
|
if item.created < int(created__lt)
|
||||||
])
|
])
|
||||||
|
|
||||||
def order_by(self, ordering):
|
def order_by(self, *ordering):
|
||||||
if ordering[0].startswith('-'):
|
if ordering[0].startswith('-'):
|
||||||
return MockQuerySet(list(reversed(self.items)))
|
return MockQuerySet(list(reversed(self.items)))
|
||||||
return self
|
return self
|
||||||
|
@ -614,6 +615,8 @@ class TestCursorPagination:
|
||||||
assert current == [1, 1, 1, 1, 1]
|
assert current == [1, 1, 1, 1, 1]
|
||||||
assert next == [1, 2, 3, 4, 4]
|
assert next == [1, 2, 3, 4, 4]
|
||||||
|
|
||||||
|
assert isinstance(self.pagination.to_html(), type(''))
|
||||||
|
|
||||||
|
|
||||||
def test_get_displayed_page_numbers():
|
def test_get_displayed_page_numbers():
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue
Block a user