Skip to content

Commit 00dcf52

Browse files
committed
revert JSONAPI prefix from paginators
- see django-json-api#467 (comment) - JSONAPIPageNumberPagination goes back to PageNumberPagination - JSONAPILimitOffsetPagination goas back to LimitOffsetPagination - requires new setting: `JSON_API_STANDARD_PAGINATION=True` to get `page[number]` and `page[size]` instead of `page` and `page_size`. This is a breaking change no matter which is the default value.
1 parent cfe89ea commit 00dcf52

File tree

6 files changed

+164
-49
lines changed

6 files changed

+164
-49
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
* Add related urls support. See [usage docs](docs/usage.md#related-urls)
66
* Replaced binary `drf_example` sqlite3 db with a [fixture](example/fixtures/drf_example.yaml). See [usage docs](docs/usage.md#running-the-example-app).
77
* Add optional [jsonapi-style](http://jsonapi.org/format/) filter backends. See [usage docs](docs/usage.md#filter-backends)
8+
* **breaking change**: Reverted v2.5.0's `JsonApiPagenumberPagination` back to `PageNumberPagination` and `JsonApiLimitOffsetPagination` back to `LimitOffsetPagination`
9+
You have to set `JSON_API_STANDARD_PAGINATION = True` (default is False) to get the "new" JSON:API-style query parameters. This retains the deprecated
10+
behavior for now. See [usage docs](docs/usage.md#pagination).
811

912
v2.5.0 - Released July 11, 2018
1013

README.rst

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,15 @@ Running the example app
118118

119119
::
120120

121-
$ git clone https://github.com/django-json-api/django-rest-framework-json-api.git
122-
$ cd django-rest-framework-json-api
123-
$ pip install -e .
124-
$ django-admin.py runserver --settings=example.settings
121+
$ git clone https://github.com/django-json-api/django-rest-framework-json-api.git
122+
$ cd django-rest-framework-json-api
123+
$ python3 -m venv env
124+
$ source env/bin/activate
125+
$ pip install -r example/requirements.txt
126+
$ pip install -e .
127+
$ django-admin migrate --settings=example.settings
128+
$ django-admin loaddata drf_example --settings=example.settings
129+
$ django-admin runserver --settings=example.settings
125130

126131
Browse to http://localhost:8000
127132

@@ -136,7 +141,7 @@ installed and activated:
136141

137142
$ pip install -e .
138143
$ pip install -r requirements-development.txt
139-
$ py.test
144+
$ DJANGO_SETTINGS_MODULE=example.settings.test py.test
140145

141146

142147
-----
@@ -161,7 +166,7 @@ override ``settings.REST_FRAMEWORK``
161166
'PAGE_SIZE': 10,
162167
'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
163168
'DEFAULT_PAGINATION_CLASS':
164-
'rest_framework_json_api.pagination.JsonApiPageNumberPagination',
169+
'rest_framework_json_api.pagination.PageNumberPagination',
165170
'DEFAULT_PARSER_CLASSES': (
166171
'rest_framework_json_api.parsers.JSONParser',
167172
'rest_framework.parsers.FormParser',
@@ -173,12 +178,15 @@ override ``settings.REST_FRAMEWORK``
173178
),
174179
'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
175180
'DEFAULT_FILTER_BACKENDS': (
176-
'rest_framework_json_api.backends.JSONAPIOrderingFilter',
181+
'rest_framework_json_api.filters.JSONAPIOrderingFilter',
182+
'rest_framework_json_api.filters.DjangoFilterBackend',
177183
),
178184
'TEST_REQUEST_RENDERER_CLASSES': (
179185
'rest_framework_json_api.renderers.JSONRenderer',
180186
),
181187
'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
182188
}
183189

190+
JSON_API_STANDARD_PAGINATION = True
191+
184192
This package provides much more including automatic inflection of JSON keys, extra top level data (using nested serializers), relationships, links, and handy shortcuts like MultipleIDMixin. Read more at http://django-rest-framework-json-api.readthedocs.org/

docs/usage.md

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
The DJA package implements a custom renderer, parser, exception handler, query filter backends, and
55
pagination. To get started enable the pieces in `settings.py` that you want to use.
66

7-
Many features of the [JSON:API](http://jsonapi.org/format) format standard have been implemented using
7+
Many features of the [JSON:API](http://jsonapi.org/format) format standard have been implemented using
88
Mixin classes in `serializers.py`.
99
The easiest way to make use of those features is to import ModelSerializer variants
1010
from `rest_framework_json_api` instead of the usual `rest_framework`
@@ -16,7 +16,7 @@ REST_FRAMEWORK = {
1616
'PAGE_SIZE': 10,
1717
'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler',
1818
'DEFAULT_PAGINATION_CLASS':
19-
'rest_framework_json_api.pagination.JsonApiPageNumberPagination',
19+
'rest_framework_json_api.pagination.PageNumberPagination',
2020
'DEFAULT_PARSER_CLASSES': (
2121
'rest_framework_json_api.parsers.JSONParser',
2222
'rest_framework.parsers.FormParser',
@@ -41,6 +41,8 @@ REST_FRAMEWORK = {
4141
),
4242
'TEST_REQUEST_DEFAULT_FORMAT': 'vnd.api+json'
4343
}
44+
45+
JSON_API_STANDARD_PAGINATION = True
4446
```
4547

4648
### Pagination
@@ -50,6 +52,8 @@ DJA pagination is based on [DRF pagination](https://www.django-rest-framework.or
5052
When pagination is enabled, the renderer will return a `meta` object with
5153
record count and a `links` object with the next, previous, first, and last links.
5254

55+
Optional query parameters can also be provided to customize the page size or offset limit.
56+
5357
#### Configuring the Pagination Style
5458

5559
Pagination style can be set on a particular viewset with the `pagination_class` attribute or by default for all viewsets
@@ -58,36 +62,46 @@ by setting `REST_FRAMEWORK['DEFAULT_PAGINATION_CLASS']` and by setting `REST_FRA
5862
You can configure fixed values for the page size or limit -- or allow the client to choose the size or limit
5963
via query parameters.
6064

65+
You should also set `JSON_API_STANDARD_PAGINATION=True` in order to get up to date JSON:API-style default
66+
query parameters and pagination size limits. The default value (False) sets deprecated values that will
67+
be eliminated in the future. See below.
68+
6169
Two pagination classes are available:
62-
- `JsonApiPageNumberPagination` breaks a response up into pages that start at a given page number with a given size
70+
- `PageNumberPagination` breaks a response up into pages that start at a given page number with a given size
6371
(number of items per page). It can be configured with the following attributes:
64-
- `page_query_param` (default `page[number]`)
65-
- `page_size_query_param` (default `page[size]`) Set this to `None` if you don't want to allow the client
66-
to specify the size.
67-
- `max_page_size` (default `100`) enforces an upper bound on the `page_size_query_param`.
72+
- `page_size` (default `REST_FRAMEWORK['PAGE_SIZE']`) number of items per page.
73+
- `max_page_size` (default `100`) enforces an upper bound on the page size.
6874
Set it to `None` if you don't want to enforce an upper bound.
69-
- `JsonApiLimitOffsetPagination` breaks a response up into pages that start from an item's offset in the viewset for
75+
- `page_query_param` (if `JSON_API_STANDARD_PAGINATION is True`: `page[number]` otherwise `page`) is the query
76+
parameter for the page number.
77+
- `page_size_query_param` (if `JSON_API_STANDARD_PAGINATION is True`: `page[size]` otherwise `page_size`) is the
78+
query parameter for the page size. No more than `max_page_size` items will be returned.
79+
Set this to `None` if you don't want to allow the client to specify the size.
80+
- `LimitOffsetPagination` breaks a response up into pages that start from an item's offset in the viewset for
7081
a given number of items (the limit).
7182
It can be configured with the following attributes:
72-
- `offset_query_param` (default `page[offset]`).
73-
- `limit_query_param` (default `page[limit]`).
74-
- `max_limit` (default `100`) enforces an upper bound on the limit.
75-
Set it to `None` if you don't want to enforce an upper bound.
76-
83+
- `default_limit` (default `REST_FRAMEWORK['PAGE_SIZE']`) is the number of items per page.
84+
- `offset_query_param` (default `page[offset]`) is the query parameter that sets the offset (item number) to return.
85+
- `limit_query_param` (default `page[limit]`) is the query parameter that sets the limit (number of items per page).
86+
No more than `max_limit` items will be returned.
87+
- `max_limit` (if `JSON_API_STANDARD_PAGINATION is True`: `100` otherwise `None`) enforces an upper bound on the
88+
limit. Set it to `None` if you don't want to enforce an upper bound.
7789

7890
These examples show how to configure the parameters to use non-standard names and different limits:
7991

8092
```python
81-
from rest_framework_json_api.pagination import JsonApiPageNumberPagination, JsonApiLimitOffsetPagination
93+
from rest_framework_json_api.pagination import PageNumberPagination, LimitOffsetPagination
8294

83-
class MyPagePagination(JsonApiPageNumberPagination):
95+
class MyPagePagination(PageNumberPagination):
8496
page_query_param = 'page_number'
85-
page_size_query_param = 'page_size'
97+
page_size_query_param = 'page_length'
98+
page_size = 3
8699
max_page_size = 1000
87100

88-
class MyLimitPagination(JsonApiLimitOffsetPagination):
101+
class MyLimitPagination(LimitOffsetPagination):
89102
offset_query_param = 'offset'
90103
limit_query_param = 'limit'
104+
default_limit = 3
91105
max_limit = None
92106
```
93107

@@ -146,7 +160,7 @@ If you are also using [`rest_framework.filters.SearchFilter`](https://django-res
146160
(which performs single parameter searchs across multiple fields) you'll want to customize the name of the query
147161
parameter for searching to make sure it doesn't conflict with a field name defined in the filterset.
148162
The recommended value is: `search_param="filter[search]"` but just make sure it's
149-
`filter[_something_]` to comply with the jsonapi spec requirement to use the filter
163+
`filter[_something_]` to comply with the JSON:API spec requirement to use the filter
150164
keyword. The default is "search" unless overriden.
151165

152166
The filter returns a `400 Bad Request` error for invalid filter query parameters as in this example
@@ -445,7 +459,7 @@ class OrderSerializer(serializers.ModelSerializer):
445459

446460
```
447461

448-
In the [JSON API spec](http://jsonapi.org/format/#document-resource-objects),
462+
In the [JSON:API spec](http://jsonapi.org/format/#document-resource-objects),
449463
relationship objects contain links to related objects. To make this work
450464
on a serializer we need to tell the `ResourceRelatedField` about the
451465
corresponding view. Use the `HyperlinkedModelSerializer` and instantiate
@@ -583,7 +597,7 @@ class OrderSerializer(serializers.HyperlinkedModelSerializer):
583597
### RelationshipView
584598
`rest_framework_json_api.views.RelationshipView` is used to build
585599
relationship views (see the
586-
[JSON API spec](http://jsonapi.org/format/#fetching-relationships)).
600+
[JSON:API spec](http://jsonapi.org/format/#fetching-relationships)).
587601
The `self` link on a relationship object should point to the corresponding
588602
relationship view.
589603

example/tests/unit/test_pagination.py

Lines changed: 85 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
class TestLimitOffset:
1515
"""
16-
Unit tests for `pagination.JsonApiLimitOffsetPagination`.
16+
Unit tests for `pagination.LimitOffsetPagination`.
1717
"""
1818

1919
def setup(self):
20-
class ExamplePagination(pagination.JsonApiLimitOffsetPagination):
20+
class ExamplePagination(pagination.LimitOffsetPagination):
2121
default_limit = 10
2222
max_limit = 15
2323

@@ -81,21 +81,98 @@ def test_valid_offset_limit(self):
8181

8282
def test_limit_offset_deprecation(self):
8383
with pytest.warns(DeprecationWarning) as record:
84-
pagination.LimitOffsetPagination()
84+
pagination.JsonApiLimitOffsetPagination()
8585
assert len(record) == 1
86-
assert 'LimitOffsetPagination' in str(record[0].message)
86+
assert 'JsonApiLimitOffsetPagination' in str(record[0].message)
8787

8888

8989
# TODO: This test fails under py27 but it's not clear why so just leave it out for now.
9090
@pytest.mark.xfail((sys.version_info.major, sys.version_info.minor) == (2, 7),
9191
reason="python2.7 fails for unknown reason")
9292
class TestPageNumber:
9393
"""
94-
Unit tests for `pagination.JsonApiPageNumberPagination`.
95-
TODO: add unit tests for changing query parameter names, limits, etc.
94+
Unit tests for `pagination.PageNumberPagination`.
95+
TODO: add unit tests for changing query parameter names, max limits, etc.
9696
"""
97+
98+
def setup(self):
99+
class ExamplePagination(pagination.PageNumberPagination):
100+
default_page_size = 10
101+
max_page_size = 15
102+
103+
self.pagination = ExamplePagination()
104+
self.queryset = range(1, 401)
105+
self.base_url = 'http://testserver/'
106+
107+
def paginate_queryset(self, request):
108+
return list(self.pagination.paginate_queryset(self.queryset, request))
109+
110+
def get_paginated_content(self, queryset):
111+
response = self.pagination.get_paginated_response(queryset)
112+
return response.data
113+
114+
def get_test_request(self, arguments):
115+
return Request(factory.get('/', arguments))
116+
117+
def test_valid_page_size(self):
118+
"""
119+
Basic test, assumes page and size are given.
120+
"""
121+
page = 10
122+
size = 5
123+
count = len(self.queryset)
124+
last_page = (count // size)
125+
next_page = 11
126+
prev_page = 9
127+
128+
request = self.get_test_request({
129+
self.pagination.page_size_query_param: size,
130+
self.pagination.page_query_param: page
131+
})
132+
base_url = replace_query_param(self.base_url, self.pagination.page_size_query_param, size)
133+
first_url = replace_query_param(base_url, self.pagination.page_query_param, 1)
134+
last_url = replace_query_param(base_url, self.pagination.page_query_param, last_page)
135+
next_url = replace_query_param(base_url, self.pagination.page_query_param, next_page)
136+
prev_url = replace_query_param(base_url, self.pagination.page_query_param, prev_page)
137+
queryset = self.paginate_queryset(request)
138+
content = self.get_paginated_content(queryset)
139+
140+
expected_content = {
141+
'results': list(range((page - 1) * size + 1, (next_page - 1) * size + 1)),
142+
'links': OrderedDict([
143+
('first', first_url),
144+
('last', last_url),
145+
('next', next_url),
146+
('prev', prev_url),
147+
]),
148+
'meta': {
149+
'pagination': OrderedDict([
150+
('page', page),
151+
('pages', count // size),
152+
('count', count),
153+
])
154+
}
155+
}
156+
157+
assert queryset == list(range((page - 1) * size + 1, (next_page - 1) * size + 1))
158+
assert content == expected_content
159+
160+
def test_page_size_too_big(self):
161+
"""
162+
ask for a page size that's more than the max
163+
"""
164+
page = 3
165+
size = 20
166+
request = self.get_test_request({
167+
self.pagination.page_size_query_param: size,
168+
self.pagination.page_query_param: page
169+
})
170+
queryset = self.paginate_queryset(request)
171+
assert len(queryset) == self.pagination.max_page_size
172+
assert len(queryset) < size
173+
97174
def test_page_number_deprecation(self):
98175
with pytest.warns(DeprecationWarning) as record:
99-
pagination.PageNumberPagination()
176+
pagination.JsonApiPageNumberPagination()
100177
assert len(record) == 1
101-
assert 'PageNumberPagination' in str(record[0].message)
178+
assert 'JsonApiPageNumberPagination' in str(record[0].message)

rest_framework_json_api/pagination.py

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,24 @@
88
from rest_framework.utils.urls import remove_query_param, replace_query_param
99
from rest_framework.views import Response
1010

11+
from rest_framework_json_api.settings import json_api_settings
1112

12-
class JsonApiPageNumberPagination(PageNumberPagination):
13+
14+
class PageNumberPagination(PageNumberPagination):
1315
"""
1416
A json-api compatible pagination format
17+
18+
An older version of this used `page` and `page_size` so
19+
use a setting to enable the "standard" query param names.
1520
"""
16-
page_query_param = 'page[number]'
17-
page_size_query_param = 'page[size]'
21+
if json_api_settings.STANDARD_PAGINATION:
22+
page_query_param = 'page[number]'
23+
page_size_query_param = 'page[size]'
24+
else:
25+
page_query_param = 'page'
26+
page_size_query_param = 'page_size'
27+
warnings.warn("'page' and 'page_size' parameters are deprecated. "
28+
"Set JSON_API_STANDARD_PAGINATION=True", DeprecationWarning)
1829
max_page_size = 100
1930

2031
def build_link(self, index):
@@ -50,15 +61,19 @@ def get_paginated_response(self, data):
5061
})
5162

5263

53-
class JsonApiLimitOffsetPagination(LimitOffsetPagination):
64+
class LimitOffsetPagination(LimitOffsetPagination):
5465
"""
5566
A limit/offset based style. For example:
5667
http://api.example.org/accounts/?page[limit]=100
5768
http://api.example.org/accounts/?page[offset]=400&page[limit]=100
5869
"""
5970
limit_query_param = 'page[limit]'
6071
offset_query_param = 'page[offset]'
61-
max_limit = 100
72+
if json_api_settings.STANDARD_PAGINATION:
73+
max_limit = 100
74+
else:
75+
warnings.warn("'max_limit = None' is deprecated. "
76+
"Set JSON_API_STANDARD_PAGINATION=True", DeprecationWarning)
6277

6378
def get_last_link(self):
6479
if self.count == 0:
@@ -100,31 +115,28 @@ def get_paginated_response(self, data):
100115
})
101116

102117

103-
class PageNumberPagination(JsonApiPageNumberPagination):
118+
class JsonApiPageNumberPagination(PageNumberPagination):
104119
"""
105-
Deprecated paginator that uses different query parameters
120+
Changed our minds about the naming scheme. Removed JsonApi prefx.
106121
"""
107-
page_query_param = 'page'
108-
page_size_query_param = 'page_size'
109122

110123
def __init__(self):
111124
warnings.warn(
112-
'PageNumberPagination is deprecated. Use JsonApiPageNumberPagination '
125+
'JsonApiPageNumberPagination is deprecated. Use PageNumberPagination '
113126
'or create custom pagination. See '
114127
'https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#pagination',
115128
DeprecationWarning)
116129
super(PageNumberPagination, self).__init__()
117130

118131

119-
class LimitOffsetPagination(JsonApiLimitOffsetPagination):
132+
class JsonApiLimitOffsetPagination(LimitOffsetPagination):
120133
"""
121-
Deprecated paginator that uses a different max_limit
134+
Changed our minds about the naming scheme. Removed JsonApi prefx.
122135
"""
123-
max_limit = None
124136

125137
def __init__(self):
126138
warnings.warn(
127-
'LimitOffsetPagination is deprecated. Use JsonApiLimitOffsetPagination '
139+
'JsonApiLimitOffsetPagination is deprecated. Use LimitOffsetPagination '
128140
'or create custom pagination. See '
129141
'https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#pagination',
130142
DeprecationWarning)

0 commit comments

Comments
 (0)