Commit 8b0f25aa by Tom Christie

More pagination tests & cleanup

parent 50db8c09
...@@ -151,6 +151,9 @@ class GenericAPIView(views.APIView): ...@@ -151,6 +151,9 @@ class GenericAPIView(views.APIView):
@property @property
def paginator(self): def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'): if not hasattr(self, '_paginator'):
if self.pagination_class is None: if self.pagination_class is None:
self._paginator = None self._paginator = None
...@@ -159,11 +162,18 @@ class GenericAPIView(views.APIView): ...@@ -159,11 +162,18 @@ class GenericAPIView(views.APIView):
return self._paginator return self._paginator
def paginate_queryset(self, queryset): def paginate_queryset(self, queryset):
"""
Return a single page of results, or `None` if pagination is disabled.
"""
if self.paginator is None: if self.paginator is None:
return queryset return None
return self.paginator.paginate_queryset(queryset, self.request, view=self) return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data): def get_paginated_response(self, data):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data) return self.paginator.get_paginated_response(data)
......
...@@ -131,13 +131,13 @@ PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True) ...@@ -131,13 +131,13 @@ PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True)
class BasePagination(object): class BasePagination(object):
display_page_controls = False display_page_controls = False
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None): # pragma: no cover
raise NotImplemented('paginate_queryset() must be implemented.') raise NotImplemented('paginate_queryset() must be implemented.')
def get_paginated_response(self, data): def get_paginated_response(self, data): # pragma: no cover
raise NotImplemented('get_paginated_response() must be implemented.') raise NotImplemented('get_paginated_response() must be implemented.')
def to_html(self): def to_html(self): # pragma: no cover
raise NotImplemented('to_html() must be implemented to display page controls.') raise NotImplemented('to_html() must be implemented to display page controls.')
...@@ -168,10 +168,11 @@ class PageNumberPagination(BasePagination): ...@@ -168,10 +168,11 @@ class PageNumberPagination(BasePagination):
template = 'rest_framework/pagination/numbers.html' template = 'rest_framework/pagination/numbers.html'
def paginate_queryset(self, queryset, request, view=None): def _handle_backwards_compat(self, view):
""" """
Paginate a queryset if required, either returning a Prior to version 3.1, pagination was handled in the view, and the
page object, or `None` if pagination is not configured for this view. attributes were set there. The attributes should now be set on
the pagination class, but the old style is still pending deprecation.
""" """
for attr in ( for attr in (
'paginate_by', 'page_query_param', 'paginate_by', 'page_query_param',
...@@ -180,6 +181,13 @@ class PageNumberPagination(BasePagination): ...@@ -180,6 +181,13 @@ class PageNumberPagination(BasePagination):
if hasattr(view, attr): if hasattr(view, attr):
setattr(self, attr, getattr(view, attr)) setattr(self, attr, getattr(view, attr))
def paginate_queryset(self, queryset, request, view=None):
"""
Paginate a queryset if required, either returning a
page object, or `None` if pagination is not configured for this view.
"""
self._handle_backwards_compat(view)
page_size = self.get_page_size(request) page_size = self.get_page_size(request)
if not page_size: if not page_size:
return None return None
...@@ -277,7 +285,6 @@ class LimitOffsetPagination(BasePagination): ...@@ -277,7 +285,6 @@ class LimitOffsetPagination(BasePagination):
limit_query_param = 'limit' limit_query_param = 'limit'
offset_query_param = 'offset' offset_query_param = 'offset'
max_limit = None max_limit = None
template = 'rest_framework/pagination/numbers.html' template = 'rest_framework/pagination/numbers.html'
def paginate_queryset(self, queryset, request, view=None): def paginate_queryset(self, queryset, request, view=None):
...@@ -340,7 +347,15 @@ class LimitOffsetPagination(BasePagination): ...@@ -340,7 +347,15 @@ class LimitOffsetPagination(BasePagination):
def get_html_context(self): def get_html_context(self):
base_url = self.request.build_absolute_uri() base_url = self.request.build_absolute_uri()
current = _divide_with_ceil(self.offset, self.limit) + 1 current = _divide_with_ceil(self.offset, self.limit) + 1
final = _divide_with_ceil(self.count, self.limit) # The number of pages is a little bit fiddly.
# We need to sum both the number of pages from current offset to end
# plus the number of pages up to the current offset.
# When offset is not strictly divisible by the limit then we may
# end up introducing an extra page as an artifact.
final = (
_divide_with_ceil(self.count - self.offset, self.limit) +
_divide_with_ceil(self.offset, self.limit)
)
def page_number_to_url(page_number): def page_number_to_url(page_number):
if page_number == 1: if page_number == 1:
......
from __future__ import unicode_literals from __future__ import unicode_literals
import datetime from rest_framework import exceptions, generics, pagination, serializers, status, filters
from decimal import Decimal
from django.test import TestCase
from django.utils import unittest
from rest_framework import generics, pagination, serializers, status, filters
from rest_framework.compat import django_filters
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.pagination import PageLink, PAGE_BREAK from rest_framework.pagination import PageLink, PAGE_BREAK
from rest_framework.test import APIRequestFactory from rest_framework.test import APIRequestFactory
from .models import BasicModel, FilterableItem import pytest
factory = APIRequestFactory() factory = APIRequestFactory()
# Helper function to split arguments out of an url class TestPaginationIntegration:
def split_arguments_from_url(url): """
if '?' not in url: Integration tests.
return url """
path, args = url.split('?')
args = dict(r.split('=') for r in args.split('&'))
return path, args
def setup(self):
class PassThroughSerializer(serializers.BaseSerializer):
def to_representation(self, item):
return item
class BasicSerializer(serializers.ModelSerializer): class EvenItemsOnly(filters.BaseFilterBackend):
class Meta: def filter_queryset(self, request, queryset, view):
model = BasicModel return [item for item in queryset if item % 2 == 0]
class BasicPagination(pagination.PageNumberPagination):
paginate_by = 5
paginate_by_param = 'page_size'
max_paginate_by = 20
class FilterableItemSerializer(serializers.ModelSerializer): self.view = generics.ListAPIView.as_view(
class Meta: serializer_class=PassThroughSerializer,
model = FilterableItem queryset=range(1, 101),
filter_backends=[EvenItemsOnly],
pagination_class=BasicPagination
)
def test_filtered_items_are_paginated(self):
request = factory.get('/', {'page': 2})
response = self.view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {
'results': [12, 14, 16, 18, 20],
'previous': 'http://testserver/',
'next': 'http://testserver/?page=3',
'count': 50
}
class RootView(generics.ListCreateAPIView): def test_setting_page_size(self):
""" """
Example description for OPTIONS. When 'paginate_by_param' is set, the client may choose a page size.
""" """
queryset = BasicModel.objects.all() request = factory.get('/', {'page_size': 10})
serializer_class = BasicSerializer response = self.view(request)
paginate_by = 10 assert response.status_code == status.HTTP_200_OK
assert response.data == {
'results': [2, 4, 6, 8, 10, 12, 14, 16, 18, 20],
'previous': None,
'next': 'http://testserver/?page=2&page_size=10',
'count': 50
}
class DefaultPageSizeKwargView(generics.ListAPIView): def test_setting_page_size_over_maximum(self):
""" """
View for testing default paginate_by_param usage When page_size parameter exceeds maxiumum allowable,
then it should be capped to the maxiumum.
""" """
queryset = BasicModel.objects.all() request = factory.get('/', {'page_size': 1000})
serializer_class = BasicSerializer response = self.view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {
'results': [
2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
22, 24, 26, 28, 30, 32, 34, 36, 38, 40
],
'previous': None,
'next': 'http://testserver/?page=2&page_size=1000',
'count': 50
}
def test_additional_query_params_are_preserved(self):
request = factory.get('/', {'page': 2, 'filter': 'even'})
response = self.view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == {
'results': [12, 14, 16, 18, 20],
'previous': 'http://testserver/?filter=even',
'next': 'http://testserver/?filter=even&page=3',
'count': 50
}
class PaginateByParamView(generics.ListAPIView): def test_404_not_found_for_invalid_page(self):
""" request = factory.get('/', {'page': 'invalid'})
View for testing custom paginate_by_param usage response = self.view(request)
""" assert response.status_code == status.HTTP_404_NOT_FOUND
queryset = BasicModel.objects.all() assert response.data == {
serializer_class = BasicSerializer 'detail': 'Invalid page "invalid": That page number is not an integer.'
paginate_by_param = 'page_size' }
class MaxPaginateByView(generics.ListAPIView): class TestPaginationDisabledIntegration:
""" """
View for testing custom max_paginate_by usage Integration tests for disabled pagination.
""" """
queryset = BasicModel.objects.all()
serializer_class = BasicSerializer
paginate_by = 3
max_paginate_by = 5
paginate_by_param = 'page_size'
def setup(self):
class PassThroughSerializer(serializers.BaseSerializer):
def to_representation(self, item):
return item
self.view = generics.ListAPIView.as_view(
serializer_class=PassThroughSerializer,
queryset=range(1, 101),
pagination_class=None
)
def test_unpaginated_list(self):
request = factory.get('/', {'page': 2})
response = self.view(request)
assert response.status_code == status.HTTP_200_OK
assert response.data == range(1, 101)
class IntegrationTestPagination(TestCase):
"""
Integration tests for paginated list views.
"""
def setUp(self):
"""
Create 26 BasicModel instances.
"""
for char in 'abcdefghijklmnopqrstuvwxyz':
BasicModel(text=char * 3).save()
self.objects = BasicModel.objects
self.data = [
{'id': obj.id, 'text': obj.text}
for obj in self.objects.all()
]
self.view = RootView.as_view()
def test_get_paginated_root_view(self): class TestDeprecatedStylePagination:
"""
GET requests to paginated ListCreateAPIView should return paginated results.
""" """
request = factory.get('/') Integration tests for deprecated style of setting pagination
# Note: Database queries are a `SELECT COUNT`, and `SELECT <fields>` attributes on the view.
with self.assertNumQueries(2):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 26)
self.assertEqual(response.data['results'], self.data[:10])
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(2):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 26)
self.assertEqual(response.data['results'], self.data[10:20])
self.assertNotEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(2):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 26)
self.assertEqual(response.data['results'], self.data[20:])
self.assertEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
class IntegrationTestPaginationAndFiltering(TestCase):
def setUp(self):
""" """
Create 50 FilterableItem instances.
"""
base_data = ('a', Decimal('0.25'), datetime.date(2012, 10, 8))
for i in range(26):
text = chr(i + ord(base_data[0])) * 3 # Produces string 'aaa', 'bbb', etc.
decimal = base_data[1] + i
date = base_data[2] - datetime.timedelta(days=i * 2)
FilterableItem(text=text, decimal=decimal, date=date).save()
self.objects = FilterableItem.objects
self.data = [
{'id': obj.id, 'text': obj.text, 'decimal': str(obj.decimal), 'date': obj.date.isoformat()}
for obj in self.objects.all()
]
@unittest.skipUnless(django_filters, 'django-filter not installed') def setup(self):
def test_get_django_filter_paginated_filtered_root_view(self): class PassThroughSerializer(serializers.BaseSerializer):
""" def to_representation(self, item):
GET requests to paginated filtered ListCreateAPIView should return return item
paginated results. The next and previous links should preserve the
filtered parameters.
"""
class DecimalFilter(django_filters.FilterSet):
decimal = django_filters.NumberFilter(lookup_type='lt')
class Meta:
model = FilterableItem
fields = ['text', 'decimal', 'date']
class FilterFieldsRootView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all()
serializer_class = FilterableItemSerializer
paginate_by = 10
filter_class = DecimalFilter
filter_backends = (filters.DjangoFilterBackend,)
view = FilterFieldsRootView.as_view()
EXPECTED_NUM_QUERIES = 2
request = factory.get('/', {'decimal': '15.20'})
with self.assertNumQueries(EXPECTED_NUM_QUERIES):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 15)
self.assertEqual(response.data['results'], self.data[:10])
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(EXPECTED_NUM_QUERIES):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 15)
self.assertEqual(response.data['results'], self.data[10:15])
self.assertEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
request = factory.get(*split_arguments_from_url(response.data['previous']))
with self.assertNumQueries(EXPECTED_NUM_QUERIES):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 15)
self.assertEqual(response.data['results'], self.data[:10])
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
def test_get_basic_paginated_filtered_root_view(self):
"""
Same as `test_get_django_filter_paginated_filtered_root_view`,
except using a custom filter backend instead of the django-filter
backend,
"""
class DecimalFilterBackend(filters.BaseFilterBackend): class ExampleView(generics.ListAPIView):
def filter_queryset(self, request, queryset, view): serializer_class = PassThroughSerializer
return queryset.filter(decimal__lt=Decimal(request.GET['decimal'])) queryset = range(1, 101)
pagination_class = pagination.PageNumberPagination
class BasicFilterFieldsRootView(generics.ListCreateAPIView): paginate_by = 20
queryset = FilterableItem.objects.all() page_query_param = 'page_number'
serializer_class = FilterableItemSerializer
paginate_by = 10
filter_backends = (DecimalFilterBackend,)
view = BasicFilterFieldsRootView.as_view()
request = factory.get('/', {'decimal': '15.20'})
with self.assertNumQueries(2):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 15)
self.assertEqual(response.data['results'], self.data[:10])
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
request = factory.get(*split_arguments_from_url(response.data['next']))
with self.assertNumQueries(2):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 15)
self.assertEqual(response.data['results'], self.data[10:15])
self.assertEqual(response.data['next'], None)
self.assertNotEqual(response.data['previous'], None)
request = factory.get(*split_arguments_from_url(response.data['previous']))
with self.assertNumQueries(2):
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 15)
self.assertEqual(response.data['results'], self.data[:10])
self.assertNotEqual(response.data['next'], None)
self.assertEqual(response.data['previous'], None)
class TestUnpaginated(TestCase):
"""
Tests for list views without pagination.
"""
def setUp(self): self.view = ExampleView.as_view()
"""
Create 13 BasicModel instances.
"""
for i in range(13):
BasicModel(text=i).save()
self.objects = BasicModel.objects
self.data = [
{'id': obj.id, 'text': obj.text}
for obj in self.objects.all()
]
self.view = DefaultPageSizeKwargView.as_view()
def test_unpaginated(self): def test_paginate_by_attribute_on_view(self):
""" request = factory.get('/?page_number=2')
Tests the default page size for this view.
no page size --> no limit --> no meta data
"""
request = factory.get('/')
response = self.view(request) response = self.view(request)
self.assertEqual(response.data, self.data) assert response.status_code == status.HTTP_200_OK
assert response.data == {
'results': [
21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
31, 32, 33, 34, 35, 36, 37, 38, 39, 40
],
'previous': 'http://testserver/',
'next': 'http://testserver/?page_number=3',
'count': 100
}
class TestCustomPaginateByParam(TestCase): class TestPageNumberPagination:
""" """
Tests for list views with default page size kwarg Unit tests for `pagination.PageNumberPagination`.
""" """
def setUp(self): def setup(self):
""" class ExamplePagination(pagination.PageNumberPagination):
Create 13 BasicModel instances. paginate_by = 5
""" self.pagination = ExamplePagination()
for i in range(13): self.queryset = range(1, 101)
BasicModel(text=i).save()
self.objects = BasicModel.objects
self.data = [
{'id': obj.id, 'text': obj.text}
for obj in self.objects.all()
]
self.view = PaginateByParamView.as_view()
def test_default_page_size(self): def paginate_queryset(self, request):
""" return list(self.pagination.paginate_queryset(self.queryset, request))
Tests the default page size for this view.
no page size --> no limit --> no meta data
"""
request = factory.get('/')
response = self.view(request).render()
self.assertEqual(response.data, self.data)
def test_paginate_by_param(self): def get_paginated_content(self, queryset):
""" response = self.pagination.get_paginated_response(queryset)
If paginate_by_param is set, the new kwarg should limit per view requests. return response.data
"""
request = factory.get('/', {'page_size': 5})
response = self.view(request).render()
self.assertEqual(response.data['count'], 13)
self.assertEqual(response.data['results'], self.data[:5])
def get_html_context(self):
return self.pagination.get_html_context()
class TestMaxPaginateByParam(TestCase): def test_no_page_number(self):
""" request = Request(factory.get('/'))
Tests for list views with max_paginate_by kwarg queryset = self.paginate_queryset(request)
""" content = self.get_paginated_content(queryset)
context = self.get_html_context()
assert queryset == [1, 2, 3, 4, 5]
assert content == {
'results': [1, 2, 3, 4, 5],
'previous': None,
'next': 'http://testserver/?page=2',
'count': 100
}
assert context == {
'previous_url': None,
'next_url': 'http://testserver/?page=2',
'page_links': [
PageLink('http://testserver/', 1, True, False),
PageLink('http://testserver/?page=2', 2, False, False),
PageLink('http://testserver/?page=3', 3, False, False),
PAGE_BREAK,
PageLink('http://testserver/?page=20', 20, False, False),
]
}
assert self.pagination.display_page_controls
assert isinstance(self.pagination.to_html(), type(''))
def setUp(self): def test_second_page(self):
""" request = Request(factory.get('/', {'page': 2}))
Create 13 BasicModel instances. queryset = self.paginate_queryset(request)
""" content = self.get_paginated_content(queryset)
for i in range(13): context = self.get_html_context()
BasicModel(text=i).save() assert queryset == [6, 7, 8, 9, 10]
self.objects = BasicModel.objects assert content == {
self.data = [ 'results': [6, 7, 8, 9, 10],
{'id': obj.id, 'text': obj.text} 'previous': 'http://testserver/',
for obj in self.objects.all() 'next': 'http://testserver/?page=3',
'count': 100
}
assert context == {
'previous_url': 'http://testserver/',
'next_url': 'http://testserver/?page=3',
'page_links': [
PageLink('http://testserver/', 1, False, False),
PageLink('http://testserver/?page=2', 2, True, False),
PageLink('http://testserver/?page=3', 3, False, False),
PAGE_BREAK,
PageLink('http://testserver/?page=20', 20, False, False),
] ]
self.view = MaxPaginateByView.as_view() }
def test_max_paginate_by(self): def test_last_page(self):
""" request = Request(factory.get('/', {'page': 'last'}))
If max_paginate_by is set, it should limit page size for the view. queryset = self.paginate_queryset(request)
""" content = self.get_paginated_content(queryset)
request = factory.get('/', data={'page_size': 10}) context = self.get_html_context()
response = self.view(request).render() assert queryset == [96, 97, 98, 99, 100]
self.assertEqual(response.data['count'], 13) assert content == {
self.assertEqual(response.data['results'], self.data[:5]) 'results': [96, 97, 98, 99, 100],
'previous': 'http://testserver/?page=19',
'next': None,
'count': 100
}
assert context == {
'previous_url': 'http://testserver/?page=19',
'next_url': None,
'page_links': [
PageLink('http://testserver/', 1, False, False),
PAGE_BREAK,
PageLink('http://testserver/?page=18', 18, False, False),
PageLink('http://testserver/?page=19', 19, False, False),
PageLink('http://testserver/?page=20', 20, True, False),
]
}
def test_max_paginate_by_without_page_size_param(self): def test_invalid_page(self):
""" request = Request(factory.get('/', {'page': 'invalid'}))
If max_paginate_by is set, but client does not specifiy page_size, with pytest.raises(exceptions.NotFound):
standard `paginate_by` behavior should be used. self.paginate_queryset(request)
"""
request = factory.get('/')
response = self.view(request).render()
self.assertEqual(response.data['results'], self.data[:3])
class TestLimitOffset: class TestLimitOffset:
"""
Unit tests for `pagination.LimitOffsetPagination`.
"""
def setup(self): def setup(self):
self.pagination = pagination.LimitOffsetPagination() class ExamplePagination(pagination.LimitOffsetPagination):
default_limit = 10
self.pagination = ExamplePagination()
self.queryset = range(1, 101) self.queryset = range(1, 101)
def paginate_queryset(self, request): def paginate_queryset(self, request):
...@@ -379,6 +300,37 @@ class TestLimitOffset: ...@@ -379,6 +300,37 @@ class TestLimitOffset:
PageLink('http://testserver/?limit=5&offset=95', 20, False, False), PageLink('http://testserver/?limit=5&offset=95', 20, False, False),
] ]
} }
assert self.pagination.display_page_controls
assert isinstance(self.pagination.to_html(), type(''))
def test_single_offset(self):
"""
When the offset is not a multiple of the limit we get some edge cases:
* The first page should still be offset zero.
* We may end up displaying an extra page in the pagination control.
"""
request = Request(factory.get('/', {'limit': 5, 'offset': 1}))
queryset = self.paginate_queryset(request)
content = self.get_paginated_content(queryset)
context = self.get_html_context()
assert queryset == [2, 3, 4, 5, 6]
assert content == {
'results': [2, 3, 4, 5, 6],
'previous': 'http://testserver/?limit=5',
'next': 'http://testserver/?limit=5&offset=6',
'count': 100
}
assert context == {
'previous_url': 'http://testserver/?limit=5',
'next_url': 'http://testserver/?limit=5&offset=6',
'page_links': [
PageLink('http://testserver/?limit=5', 1, False, False),
PageLink('http://testserver/?limit=5&offset=1', 2, True, False),
PageLink('http://testserver/?limit=5&offset=6', 3, False, False),
PAGE_BREAK,
PageLink('http://testserver/?limit=5&offset=96', 21, False, False),
]
}
def test_first_offset(self): def test_first_offset(self):
request = Request(factory.get('/', {'limit': 5, 'offset': 5})) request = Request(factory.get('/', {'limit': 5, 'offset': 5}))
...@@ -452,3 +404,72 @@ class TestLimitOffset: ...@@ -452,3 +404,72 @@ class TestLimitOffset:
PageLink('http://testserver/?limit=5&offset=95', 20, True, False), PageLink('http://testserver/?limit=5&offset=95', 20, True, False),
] ]
} }
def test_invalid_offset(self):
"""
An invalid offset query param should be treated as 0.
"""
request = Request(factory.get('/', {'limit': 5, 'offset': 'invalid'}))
queryset = self.paginate_queryset(request)
assert queryset == [1, 2, 3, 4, 5]
def test_invalid_limit(self):
"""
An invalid limit query param should be ignored in favor of the default.
"""
request = Request(factory.get('/', {'limit': 'invalid', 'offset': 0}))
queryset = self.paginate_queryset(request)
assert queryset == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def test_get_displayed_page_numbers():
"""
Test our contextual page display function.
This determines which pages to display in a pagination control,
given the current page and the last page.
"""
displayed_page_numbers = pagination._get_displayed_page_numbers
# At five pages or less, all pages are displayed, always.
assert displayed_page_numbers(1, 5) == [1, 2, 3, 4, 5]
assert displayed_page_numbers(2, 5) == [1, 2, 3, 4, 5]
assert displayed_page_numbers(3, 5) == [1, 2, 3, 4, 5]
assert displayed_page_numbers(4, 5) == [1, 2, 3, 4, 5]
assert displayed_page_numbers(5, 5) == [1, 2, 3, 4, 5]
# Between six and either pages we may have a single page break.
assert displayed_page_numbers(1, 6) == [1, 2, 3, None, 6]
assert displayed_page_numbers(2, 6) == [1, 2, 3, None, 6]
assert displayed_page_numbers(3, 6) == [1, 2, 3, 4, 5, 6]
assert displayed_page_numbers(4, 6) == [1, 2, 3, 4, 5, 6]
assert displayed_page_numbers(5, 6) == [1, None, 4, 5, 6]
assert displayed_page_numbers(6, 6) == [1, None, 4, 5, 6]
assert displayed_page_numbers(1, 7) == [1, 2, 3, None, 7]
assert displayed_page_numbers(2, 7) == [1, 2, 3, None, 7]
assert displayed_page_numbers(3, 7) == [1, 2, 3, 4, None, 7]
assert displayed_page_numbers(4, 7) == [1, 2, 3, 4, 5, 6, 7]
assert displayed_page_numbers(5, 7) == [1, None, 4, 5, 6, 7]
assert displayed_page_numbers(6, 7) == [1, None, 5, 6, 7]
assert displayed_page_numbers(7, 7) == [1, None, 5, 6, 7]
assert displayed_page_numbers(1, 8) == [1, 2, 3, None, 8]
assert displayed_page_numbers(2, 8) == [1, 2, 3, None, 8]
assert displayed_page_numbers(3, 8) == [1, 2, 3, 4, None, 8]
assert displayed_page_numbers(4, 8) == [1, 2, 3, 4, 5, None, 8]
assert displayed_page_numbers(5, 8) == [1, None, 4, 5, 6, 7, 8]
assert displayed_page_numbers(6, 8) == [1, None, 5, 6, 7, 8]
assert displayed_page_numbers(7, 8) == [1, None, 6, 7, 8]
assert displayed_page_numbers(8, 8) == [1, None, 6, 7, 8]
# At nine or more pages we may have two page breaks, one on each side.
assert displayed_page_numbers(1, 9) == [1, 2, 3, None, 9]
assert displayed_page_numbers(2, 9) == [1, 2, 3, None, 9]
assert displayed_page_numbers(3, 9) == [1, 2, 3, 4, None, 9]
assert displayed_page_numbers(4, 9) == [1, 2, 3, 4, 5, None, 9]
assert displayed_page_numbers(5, 9) == [1, None, 4, 5, 6, None, 9]
assert displayed_page_numbers(6, 9) == [1, None, 5, 6, 7, 8, 9]
assert displayed_page_numbers(7, 9) == [1, None, 6, 7, 8, 9]
assert displayed_page_numbers(8, 9) == [1, None, 7, 8, 9]
assert displayed_page_numbers(9, 9) == [1, None, 7, 8, 9]
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment