Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
django-rest-framework
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
django-rest-framework
Commits
8b0f25aa
Commit
8b0f25aa
authored
Jan 16, 2015
by
Tom Christie
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
More pagination tests & cleanup
parent
50db8c09
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
349 additions
and
303 deletions
+349
-303
rest_framework/generics.py
+11
-1
rest_framework/pagination.py
+23
-8
tests/test_pagination.py
+315
-294
No files found.
rest_framework/generics.py
View file @
8b0f25aa
...
...
@@ -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
)
...
...
rest_framework/pagination.py
View file @
8b0f25aa
...
...
@@ -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
:
...
...
tests/test_pagination.py
View file @
8b0f25aa
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
TestPaginationIntegration
:
"""
Integration tests.
"""
def
setup
(
self
):
class
PassThroughSerializer
(
serializers
.
BaseSerializer
):
def
to_representation
(
self
,
item
):
return
item
class
BasicSerializer
(
serializers
.
ModelSerializer
):
class
Meta
:
model
=
BasicModel
class
EvenItemsOnly
(
filters
.
BaseFilterBackend
):
def
filter_queryset
(
self
,
request
,
queryset
,
view
)
:
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
):
class
Meta
:
model
=
FilterableItem
self
.
view
=
generics
.
ListAPIView
.
as_view
(
serializer_class
=
PassThroughSerializer
,
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
()
serializer_class
=
BasicSerializer
paginate_by
=
10
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
}
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
()
serializer_class
=
BasicSerializer
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
}
class
PaginateByParamView
(
generics
.
ListAPIView
):
"""
View for testing custom paginate_by_param usage
"""
queryset
=
BasicModel
.
objects
.
all
()
serializer_class
=
BasicSerializer
paginate_by_param
=
'page_size'
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
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
):
"""
GET requests to paginated ListCreateAPIView should return paginated results.
class
TestDeprecatedStylePagination
:
"""
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
):
Integration tests for deprecated style of setting pagination
attributes on the view.
"""
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,
"""
def
setup
(
self
):
class
PassThroughSerializer
(
serializers
.
BaseSerializer
):
def
to_representation
(
self
,
item
):
return
item
class
DecimalFilterBackend
(
filters
.
BaseFilterBackend
):
def
filter_queryset
(
self
,
request
,
queryset
,
view
):
return
queryset
.
filter
(
decimal__lt
=
Decimal
(
request
.
GET
[
'decimal'
]))
class
BasicFilterFieldsRootView
(
generics
.
ListCreateAPIView
):
queryset
=
FilterableItem
.
objects
.
all
()
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.
"""
class
ExampleView
(
generics
.
ListAPIView
):
serializer_class
=
PassThroughSerializer
queryset
=
range
(
1
,
101
)
pagination_class
=
pagination
.
PageNumberPagination
paginate_by
=
20
page_query_param
=
'page_number'
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
()
self
.
view
=
ExampleView
.
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_paginate_by_attribute_on_view
(
self
):
request
=
factory
.
get
(
'/?page_number=2'
)
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
Test
CustomPaginateByParam
(
TestCase
)
:
class
Test
PageNumberPagination
:
"""
Tests for list views with default page size kwarg
Unit tests for `pagination.PageNumberPagination`.
"""
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
=
PaginateByParamView
.
as_view
()
def
setup
(
self
):
class
ExamplePagination
(
pagination
.
PageNumberPagination
):
paginate_by
=
5
self
.
pagination
=
ExamplePagination
()
self
.
queryset
=
range
(
1
,
101
)
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
paginate_queryset
(
self
,
request
):
return
list
(
self
.
pagination
.
paginate_queryset
(
self
.
queryset
,
request
))
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
])
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
()
class
TestMaxPaginateByParam
(
TestCase
):
"""
Tests for list views with max_paginate_by kwarg
"""
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
),
]
}
assert
self
.
pagination
.
display_page_controls
assert
isinstance
(
self
.
pagination
.
to_html
(),
type
(
''
))
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
]
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment