paginator should validate page and provide default

- use the standard paginator.validate_number method rather
  strict_postive_int.
- support optional paginator method, default_page_number, to get the default
  page number rather than hard-coding it to 1
- this allows supporting non-integer based pagination which can be an
  important performance tweak on extermely large datasets or high request
  loads
- relatively thorough unit tests of the changes
This commit is contained in:
Ross McFarland 2013-10-19 20:43:23 -07:00
parent c3aeb16557
commit 63e6a3b492
2 changed files with 99 additions and 3 deletions

View File

@ -145,10 +145,18 @@ class GenericAPIView(views.APIView):
allow_empty_first_page=self.allow_empty)
page_kwarg = self.kwargs.get(self.page_kwarg)
page_query_param = self.request.QUERY_PARAMS.get(self.page_kwarg)
page = page_kwarg or page_query_param or 1
page = page_kwarg or page_query_param
if not page:
# we didn't recieve a page
if hasattr(paginator, 'default_page_number'):
# our paginator has a method that will provide a default
page = paginator.default_page_number()
else:
# fall back on the base default value
page = 1
try:
page_number = strict_positive_int(page)
except ValueError:
page_number = paginator.validate_number(page)
except InvalidPage:
if page == 'last':
page_number = paginator.num_pages
else:

View File

@ -430,3 +430,91 @@ class TestCustomPaginationSerializer(TestCase):
'objects': ['john', 'paul']
}
self.assertEqual(serializer.data, expected)
class NonIntegerPage(object):
def __init__(self, paginator, object_list, prev_token, token, next_token):
self.paginator = paginator
self.object_list = object_list
self.prev_token = prev_token
self.token = token
self.next_token = next_token
def has_next(self):
return not not self.next_token
def next_page_number(self):
return self.next_token
def has_previous(self):
return not not self.prev_token
def previous_page_number(self):
return self.prev_token
class NonIntegerPaginator(object):
def __init__(self, object_list, per_page):
self.object_list = object_list
self.per_page = per_page
def count(self):
# pretend like we don't know how many pages we have
return None
def default_page_token(self):
return None
def page(self, token=None):
if token:
try:
first = self.object_list.index(token)
except ValueError:
first = 0
else:
first = 0
n = len(self.object_list)
last = min(first + self.per_page, n)
prev_token = self.object_list[last - (2 * self.per_page)] if first else None
next_token = self.object_list[last] if last < n else None
return NonIntegerPage(self, self.object_list[first:last], prev_token, token, next_token)
class TestNonIntegerPagination(TestCase):
def test_custom_pagination_serializer(self):
objects = ['john', 'paul', 'george', 'ringo']
paginator = NonIntegerPaginator(objects, 2)
request = APIRequestFactory().get('/foobar')
serializer = CustomPaginationSerializer(
instance=paginator.page(),
context={'request': request}
)
expected = {
'links': {
'next': 'http://testserver/foobar?page={0}'.format(objects[2]),
'prev': None
},
'total_results': None,
'objects': objects[:2]
}
self.assertEqual(serializer.data, expected)
request = APIRequestFactory().get('/foobar')
serializer = CustomPaginationSerializer(
instance=paginator.page('george'),
context={'request': request}
)
expected = {
'links': {
'next': None,
'prev': 'http://testserver/foobar?page={0}'.format(objects[0]),
},
'total_results': None,
'objects': objects[2:]
}
self.assertEqual(serializer.data, expected)