mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-26 03:23:59 +03:00
commit
a17270589c
|
@ -127,50 +127,6 @@ def _get_page_links(page_numbers, current, url_func):
|
||||||
return page_links
|
return page_links
|
||||||
|
|
||||||
|
|
||||||
def _decode_cursor(encoded):
|
|
||||||
"""
|
|
||||||
Given a string representing an encoded cursor, return a `Cursor` instance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
try:
|
|
||||||
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):
|
|
||||||
return None
|
|
||||||
|
|
||||||
return Cursor(offset=offset, reverse=reverse, position=position)
|
|
||||||
|
|
||||||
|
|
||||||
def _encode_cursor(cursor):
|
|
||||||
"""
|
|
||||||
Given a Cursor instance, return an encoded string representation.
|
|
||||||
"""
|
|
||||||
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)
|
|
||||||
return b64encode(querystring.encode('ascii')).decode('ascii')
|
|
||||||
|
|
||||||
|
|
||||||
def _reverse_ordering(ordering_tuple):
|
def _reverse_ordering(ordering_tuple):
|
||||||
"""
|
"""
|
||||||
Given an order_by tuple such as `('-created', 'uuid')` reverse the
|
Given an order_by tuple such as `('-created', 'uuid')` reverse the
|
||||||
|
@ -503,15 +459,10 @@ class CursorPagination(BasePagination):
|
||||||
self.base_url = request.build_absolute_uri()
|
self.base_url = request.build_absolute_uri()
|
||||||
self.ordering = self.get_ordering(request, queryset, view)
|
self.ordering = self.get_ordering(request, queryset, view)
|
||||||
|
|
||||||
# Determine if we have a cursor, and if so then decode it.
|
self.cursor = self.decode_cursor(request)
|
||||||
encoded = request.query_params.get(self.cursor_query_param)
|
if self.cursor is None:
|
||||||
if encoded is None:
|
|
||||||
self.cursor = None
|
|
||||||
(offset, reverse, current_position) = (0, False, None)
|
(offset, reverse, current_position) = (0, False, None)
|
||||||
else:
|
else:
|
||||||
self.cursor = _decode_cursor(encoded)
|
|
||||||
if self.cursor is None:
|
|
||||||
raise NotFound(self.invalid_cursor_message)
|
|
||||||
(offset, reverse, current_position) = self.cursor
|
(offset, reverse, current_position) = self.cursor
|
||||||
|
|
||||||
# Cursor pagination always enforces an ordering.
|
# Cursor pagination always enforces an ordering.
|
||||||
|
@ -623,8 +574,7 @@ class CursorPagination(BasePagination):
|
||||||
position = self.previous_position
|
position = self.previous_position
|
||||||
|
|
||||||
cursor = Cursor(offset=offset, reverse=False, position=position)
|
cursor = Cursor(offset=offset, reverse=False, position=position)
|
||||||
encoded = _encode_cursor(cursor)
|
return self.encode_cursor(cursor)
|
||||||
return replace_query_param(self.base_url, self.cursor_query_param, encoded)
|
|
||||||
|
|
||||||
def get_previous_link(self):
|
def get_previous_link(self):
|
||||||
if not self.has_previous:
|
if not self.has_previous:
|
||||||
|
@ -672,8 +622,7 @@ class CursorPagination(BasePagination):
|
||||||
position = self.next_position
|
position = self.next_position
|
||||||
|
|
||||||
cursor = Cursor(offset=offset, reverse=True, position=position)
|
cursor = Cursor(offset=offset, reverse=True, position=position)
|
||||||
encoded = _encode_cursor(cursor)
|
return self.encode_cursor(cursor)
|
||||||
return replace_query_param(self.base_url, self.cursor_query_param, encoded)
|
|
||||||
|
|
||||||
def get_ordering(self, request, queryset, view):
|
def get_ordering(self, request, queryset, view):
|
||||||
"""
|
"""
|
||||||
|
@ -715,6 +664,53 @@ class CursorPagination(BasePagination):
|
||||||
return (ordering,)
|
return (ordering,)
|
||||||
return tuple(ordering)
|
return tuple(ordering)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
try:
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
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_position_from_instance(self, instance, ordering):
|
def _get_position_from_instance(self, instance, ordering):
|
||||||
attr = getattr(instance, ordering[0].lstrip('-'))
|
attr = getattr(instance, ordering[0].lstrip('-'))
|
||||||
return six.text_type(attr)
|
return six.text_type(attr)
|
||||||
|
|
|
@ -186,6 +186,7 @@ class TestPageNumberPagination:
|
||||||
def setup(self):
|
def setup(self):
|
||||||
class ExamplePagination(pagination.PageNumberPagination):
|
class ExamplePagination(pagination.PageNumberPagination):
|
||||||
page_size = 5
|
page_size = 5
|
||||||
|
|
||||||
self.pagination = ExamplePagination()
|
self.pagination = ExamplePagination()
|
||||||
self.queryset = range(1, 101)
|
self.queryset = range(1, 101)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user