mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-25 11:04:02 +03:00
More pagination tests & cleanup
This commit is contained in:
parent
50db8c092a
commit
8b0f25aa0a
|
@ -151,6 +151,9 @@ class GenericAPIView(views.APIView):
|
|||
|
||||
@property
|
||||
def paginator(self):
|
||||
"""
|
||||
The paginator instance associated with the view, or `None`.
|
||||
"""
|
||||
if not hasattr(self, '_paginator'):
|
||||
if self.pagination_class is None:
|
||||
self._paginator = None
|
||||
|
@ -159,11 +162,18 @@ class GenericAPIView(views.APIView):
|
|||
return self._paginator
|
||||
|
||||
def paginate_queryset(self, queryset):
|
||||
"""
|
||||
Return a single page of results, or `None` if pagination is disabled.
|
||||
"""
|
||||
if self.paginator is None:
|
||||
return queryset
|
||||
return None
|
||||
return self.paginator.paginate_queryset(queryset, self.request, view=self)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -131,13 +131,13 @@ PAGE_BREAK = PageLink(url=None, number=None, is_active=False, is_break=True)
|
|||
class BasePagination(object):
|
||||
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.')
|
||||
|
||||
def get_paginated_response(self, data):
|
||||
def get_paginated_response(self, data): # pragma: no cover
|
||||
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.')
|
||||
|
||||
|
||||
|
@ -168,10 +168,11 @@ class PageNumberPagination(BasePagination):
|
|||
|
||||
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
|
||||
page object, or `None` if pagination is not configured for this view.
|
||||
Prior to version 3.1, pagination was handled in the view, and the
|
||||
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 (
|
||||
'paginate_by', 'page_query_param',
|
||||
|
@ -180,6 +181,13 @@ class PageNumberPagination(BasePagination):
|
|||
if hasattr(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)
|
||||
if not page_size:
|
||||
return None
|
||||
|
@ -277,7 +285,6 @@ class LimitOffsetPagination(BasePagination):
|
|||
limit_query_param = 'limit'
|
||||
offset_query_param = 'offset'
|
||||
max_limit = None
|
||||
|
||||
template = 'rest_framework/pagination/numbers.html'
|
||||
|
||||
def paginate_queryset(self, queryset, request, view=None):
|
||||
|
@ -340,7 +347,15 @@ class LimitOffsetPagination(BasePagination):
|
|||
def get_html_context(self):
|
||||
base_url = self.request.build_absolute_uri()
|
||||
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):
|
||||
if page_number == 1:
|
||||
|
|
|
@ -1,349 +1,270 @@
|
|||
from __future__ import unicode_literals
|
||||
import datetime
|
||||
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 import exceptions, generics, pagination, serializers, status, filters
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.pagination import PageLink, PAGE_BREAK
|
||||
from rest_framework.test import APIRequestFactory
|
||||
from .models import BasicModel, FilterableItem
|
||||
import pytest
|
||||
|
||||
factory = APIRequestFactory()
|
||||
|
||||
|
||||
# Helper function to split arguments out of an url
|
||||
def split_arguments_from_url(url):
|
||||
if '?' not in url:
|
||||
return url
|
||||
|
||||
path, args = url.split('?')
|
||||
args = dict(r.split('=') for r in args.split('&'))
|
||||
return path, args
|
||||
|
||||
|
||||
class BasicSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = BasicModel
|
||||
|
||||
|
||||
class FilterableItemSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FilterableItem
|
||||
|
||||
|
||||
class RootView(generics.ListCreateAPIView):
|
||||
class TestPaginationIntegration:
|
||||
"""
|
||||
Example description for OPTIONS.
|
||||
"""
|
||||
queryset = BasicModel.objects.all()
|
||||
serializer_class = BasicSerializer
|
||||
paginate_by = 10
|
||||
|
||||
|
||||
class DefaultPageSizeKwargView(generics.ListAPIView):
|
||||
"""
|
||||
View for testing default paginate_by_param usage
|
||||
"""
|
||||
queryset = BasicModel.objects.all()
|
||||
serializer_class = BasicSerializer
|
||||
|
||||
|
||||
class PaginateByParamView(generics.ListAPIView):
|
||||
"""
|
||||
View for testing custom paginate_by_param usage
|
||||
"""
|
||||
queryset = BasicModel.objects.all()
|
||||
serializer_class = BasicSerializer
|
||||
paginate_by_param = 'page_size'
|
||||
|
||||
|
||||
class MaxPaginateByView(generics.ListAPIView):
|
||||
"""
|
||||
View for testing custom max_paginate_by usage
|
||||
"""
|
||||
queryset = BasicModel.objects.all()
|
||||
serializer_class = BasicSerializer
|
||||
paginate_by = 3
|
||||
max_paginate_by = 5
|
||||
paginate_by_param = 'page_size'
|
||||
|
||||
|
||||
class IntegrationTestPagination(TestCase):
|
||||
"""
|
||||
Integration tests for paginated list views.
|
||||
Integration tests.
|
||||
"""
|
||||
|
||||
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 setup(self):
|
||||
class PassThroughSerializer(serializers.BaseSerializer):
|
||||
def to_representation(self, item):
|
||||
return item
|
||||
|
||||
def test_get_paginated_root_view(self):
|
||||
"""
|
||||
GET requests to paginated ListCreateAPIView should return paginated results.
|
||||
"""
|
||||
request = factory.get('/')
|
||||
# Note: Database queries are a `SELECT COUNT`, and `SELECT <fields>`
|
||||
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 test_get_django_filter_paginated_filtered_root_view(self):
|
||||
"""
|
||||
GET requests to paginated filtered ListCreateAPIView should return
|
||||
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 EvenItemsOnly(filters.BaseFilterBackend):
|
||||
def filter_queryset(self, request, queryset, view):
|
||||
return queryset.filter(decimal__lt=Decimal(request.GET['decimal']))
|
||||
return [item for item in queryset if item % 2 == 0]
|
||||
|
||||
class BasicFilterFieldsRootView(generics.ListCreateAPIView):
|
||||
queryset = FilterableItem.objects.all()
|
||||
serializer_class = FilterableItemSerializer
|
||||
paginate_by = 10
|
||||
filter_backends = (DecimalFilterBackend,)
|
||||
class BasicPagination(pagination.PageNumberPagination):
|
||||
paginate_by = 5
|
||||
paginate_by_param = 'page_size'
|
||||
max_paginate_by = 20
|
||||
|
||||
view = BasicFilterFieldsRootView.as_view()
|
||||
self.view = generics.ListAPIView.as_view(
|
||||
serializer_class=PassThroughSerializer,
|
||||
queryset=range(1, 101),
|
||||
filter_backends=[EvenItemsOnly],
|
||||
pagination_class=BasicPagination
|
||||
)
|
||||
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Tests the default page size for this view.
|
||||
no page size --> no limit --> no meta data
|
||||
"""
|
||||
request = factory.get('/')
|
||||
def test_filtered_items_are_paginated(self):
|
||||
request = factory.get('/', {'page': 2})
|
||||
response = self.view(request)
|
||||
self.assertEqual(response.data, self.data)
|
||||
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
|
||||
}
|
||||
|
||||
def test_setting_page_size(self):
|
||||
"""
|
||||
When 'paginate_by_param' is set, the client may choose a page size.
|
||||
"""
|
||||
request = factory.get('/', {'page_size': 10})
|
||||
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],
|
||||
'previous': None,
|
||||
'next': 'http://testserver/?page=2&page_size=10',
|
||||
'count': 50
|
||||
}
|
||||
|
||||
def test_setting_page_size_over_maximum(self):
|
||||
"""
|
||||
When page_size parameter exceeds maxiumum allowable,
|
||||
then it should be capped to the maxiumum.
|
||||
"""
|
||||
request = factory.get('/', {'page_size': 1000})
|
||||
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
|
||||
}
|
||||
|
||||
def test_404_not_found_for_invalid_page(self):
|
||||
request = factory.get('/', {'page': 'invalid'})
|
||||
response = self.view(request)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
assert response.data == {
|
||||
'detail': 'Invalid page "invalid": That page number is not an integer.'
|
||||
}
|
||||
|
||||
|
||||
class TestCustomPaginateByParam(TestCase):
|
||||
class TestPaginationDisabledIntegration:
|
||||
"""
|
||||
Tests for list views with default page size kwarg
|
||||
Integration tests for disabled pagination.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
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 TestDeprecatedStylePagination:
|
||||
"""
|
||||
Create 13 BasicModel instances.
|
||||
Integration tests for deprecated style of setting pagination
|
||||
attributes on the view.
|
||||
"""
|
||||
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()
|
||||
|
||||
def setup(self):
|
||||
class PassThroughSerializer(serializers.BaseSerializer):
|
||||
def to_representation(self, item):
|
||||
return item
|
||||
|
||||
class ExampleView(generics.ListAPIView):
|
||||
serializer_class = PassThroughSerializer
|
||||
queryset = range(1, 101)
|
||||
pagination_class = pagination.PageNumberPagination
|
||||
paginate_by = 20
|
||||
page_query_param = 'page_number'
|
||||
|
||||
self.view = ExampleView.as_view()
|
||||
|
||||
def test_paginate_by_attribute_on_view(self):
|
||||
request = factory.get('/?page_number=2')
|
||||
response = self.view(request)
|
||||
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 TestPageNumberPagination:
|
||||
"""
|
||||
Unit tests for `pagination.PageNumberPagination`.
|
||||
"""
|
||||
|
||||
def setup(self):
|
||||
class ExamplePagination(pagination.PageNumberPagination):
|
||||
paginate_by = 5
|
||||
self.pagination = ExamplePagination()
|
||||
self.queryset = range(1, 101)
|
||||
|
||||
def paginate_queryset(self, request):
|
||||
return list(self.pagination.paginate_queryset(self.queryset, request))
|
||||
|
||||
def get_paginated_content(self, queryset):
|
||||
response = self.pagination.get_paginated_response(queryset)
|
||||
return response.data
|
||||
|
||||
def get_html_context(self):
|
||||
return self.pagination.get_html_context()
|
||||
|
||||
def test_no_page_number(self):
|
||||
request = Request(factory.get('/'))
|
||||
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),
|
||||
]
|
||||
self.view = PaginateByParamView.as_view()
|
||||
}
|
||||
assert self.pagination.display_page_controls
|
||||
assert isinstance(self.pagination.to_html(), type(''))
|
||||
|
||||
def test_default_page_size(self):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
If paginate_by_param is set, the new kwarg should limit per view requests.
|
||||
"""
|
||||
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])
|
||||
|
||||
|
||||
class TestMaxPaginateByParam(TestCase):
|
||||
"""
|
||||
Tests for list views with max_paginate_by kwarg
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
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()
|
||||
def test_second_page(self):
|
||||
request = Request(factory.get('/', {'page': 2}))
|
||||
queryset = self.paginate_queryset(request)
|
||||
content = self.get_paginated_content(queryset)
|
||||
context = self.get_html_context()
|
||||
assert queryset == [6, 7, 8, 9, 10]
|
||||
assert content == {
|
||||
'results': [6, 7, 8, 9, 10],
|
||||
'previous': 'http://testserver/',
|
||||
'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):
|
||||
"""
|
||||
If max_paginate_by is set, it should limit page size for the view.
|
||||
"""
|
||||
request = factory.get('/', data={'page_size': 10})
|
||||
response = self.view(request).render()
|
||||
self.assertEqual(response.data['count'], 13)
|
||||
self.assertEqual(response.data['results'], self.data[:5])
|
||||
def test_last_page(self):
|
||||
request = Request(factory.get('/', {'page': 'last'}))
|
||||
queryset = self.paginate_queryset(request)
|
||||
content = self.get_paginated_content(queryset)
|
||||
context = self.get_html_context()
|
||||
assert queryset == [96, 97, 98, 99, 100]
|
||||
assert content == {
|
||||
'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):
|
||||
"""
|
||||
If max_paginate_by is set, but client does not specifiy page_size,
|
||||
standard `paginate_by` behavior should be used.
|
||||
"""
|
||||
request = factory.get('/')
|
||||
response = self.view(request).render()
|
||||
self.assertEqual(response.data['results'], self.data[:3])
|
||||
def test_invalid_page(self):
|
||||
request = Request(factory.get('/', {'page': 'invalid'}))
|
||||
with pytest.raises(exceptions.NotFound):
|
||||
self.paginate_queryset(request)
|
||||
|
||||
|
||||
class TestLimitOffset:
|
||||
"""
|
||||
Unit tests for `pagination.LimitOffsetPagination`.
|
||||
"""
|
||||
|
||||
def setup(self):
|
||||
self.pagination = pagination.LimitOffsetPagination()
|
||||
class ExamplePagination(pagination.LimitOffsetPagination):
|
||||
default_limit = 10
|
||||
self.pagination = ExamplePagination()
|
||||
self.queryset = range(1, 101)
|
||||
|
||||
def paginate_queryset(self, request):
|
||||
|
@ -379,6 +300,37 @@ class TestLimitOffset:
|
|||
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):
|
||||
request = Request(factory.get('/', {'limit': 5, 'offset': 5}))
|
||||
|
@ -452,3 +404,72 @@ class TestLimitOffset:
|
|||
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]
|
||||
|
|
Loading…
Reference in New Issue
Block a user