diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a14f730..0230d7e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,8 @@ any parts of the framework not mentioned in the documentation should generally b ### Fixed * Performance improvement when rendering relationships with `ModelSerializer` +* Do not show deprecation warning when user has implemented custom pagination class overwriting default values. + ## [2.5.0] - 2018-07-11 @@ -40,6 +42,16 @@ any parts of the framework not mentioned in the documentation should generally b ### Deprecated * Deprecate `PageNumberPagination` and `LimitOffsetPagination`. Use `JsonApiPageNumberPagination` and `JsonApiLimitOffsetPagination` instead. + * To retain deprecated values for `PageNumberPagination` and `LimitOffsetPagination` create new custom class like the following in your code base: + ```python + class CustomPageNumberPagination(PageNumberPagination): + page_query_param = "page" + page_size_query_param = "page_size" + + class CustomLimitOffsetPagination(LimitOffsetPagination): + max_limit = None + ``` + and adjust `REST_FRAMEWORK['DEFAULT_PAGINATION_CLASS']` setting accordingly. * Deprecate `JSON_API_FORMAT_KEYS`, use `JSON_API_FORMAT_FIELD_NAMES`. ### Fixed diff --git a/README.rst b/README.rst index d690bb3f..cb0647fa 100644 --- a/README.rst +++ b/README.rst @@ -161,7 +161,7 @@ override ``settings.REST_FRAMEWORK`` 'PAGE_SIZE': 10, 'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler', 'DEFAULT_PAGINATION_CLASS': - 'rest_framework_json_api.pagination.JSONAPIPageNumberPagination', + 'rest_framework_json_api.pagination.JsonApiPageNumberPagination', 'DEFAULT_PARSER_CLASSES': ( 'rest_framework_json_api.parsers.JSONParser', 'rest_framework.parsers.FormParser', diff --git a/docs/usage.md b/docs/usage.md index 51576d06..7d329a3c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -16,7 +16,7 @@ REST_FRAMEWORK = { 'PAGE_SIZE': 10, 'EXCEPTION_HANDLER': 'rest_framework_json_api.exceptions.exception_handler', 'DEFAULT_PAGINATION_CLASS': - 'rest_framework_json_api.pagination.JSONAPIPageNumberPagination', + 'rest_framework_json_api.pagination.JsonApiPageNumberPagination', 'DEFAULT_PARSER_CLASSES': ( 'rest_framework_json_api.parsers.JSONParser', 'rest_framework.parsers.FormParser', @@ -50,6 +50,8 @@ DJA pagination is based on [DRF pagination](https://www.django-rest-framework.or When pagination is enabled, the renderer will return a `meta` object with record count and a `links` object with the next, previous, first, and last links. +Optional query parameters can also be provided to customize the page size or offset limit. + #### Configuring the Pagination Style Pagination style can be set on a particular viewset with the `pagination_class` attribute or by default for all viewsets @@ -59,35 +61,42 @@ You can configure fixed values for the page size or limit -- or allow the client via query parameters. Two pagination classes are available: -- `JSONAPIPageNumberPagination` breaks a response up into pages that start at a given page number - with a given size (number of items per page). It can be configured with the following attributes: +- `JsonApiPageNumberPagination` breaks a response up into pages that start at a given page number with a given size + (number of items per page). It can be configured with the following attributes: - `page_query_param` (default `page[number]`) - `page_size_query_param` (default `page[size]`) Set this to `None` if you don't want to allow the client to specify the size. + - `page_size` (default `REST_FRAMEWORK['PAGE_SIZE']`) default number of items per page unless overridden by + `page_size_query_param`. - `max_page_size` (default `100`) enforces an upper bound on the `page_size_query_param`. Set it to `None` if you don't want to enforce an upper bound. -- `JSONAPILimitOffsetPagination` breaks a response up into pages that start from an item's offset - in the viewset for a given number of items (the limit). + +- `JsonApiLimitOffsetPagination` breaks a response up into pages that start from an item's offset in the viewset for + a given number of items (the limit). It can be configured with the following attributes: - `offset_query_param` (default `page[offset]`). - `limit_query_param` (default `page[limit]`). + - `default_limit` (default `REST_FRAMEWORK['PAGE_SIZE']`) is the default number of items per page unless + overridden by `limit_query_param`. - `max_limit` (default `100`) enforces an upper bound on the limit. Set it to `None` if you don't want to enforce an upper bound. - +##### Examples These examples show how to configure the parameters to use non-standard names and different limits: ```python -from rest_framework_json_api.pagination import JSONAPIPageNumberPagination, JSONAPILimitOffsetPagination +from rest_framework_json_api.pagination import JsonApiPageNumberPagination, JsonApiLimitOffsetPagination -class MyPagePagination(JSONAPIPageNumberPagination): +class MyPagePagination(JsonApiPageNumberPagination): page_query_param = 'page_number' - page_size_query_param = 'page_size' + page_size_query_param = 'page_length' + page_size = 3 max_page_size = 1000 -class MyLimitPagination(JSONAPILimitOffsetPagination): +class MyLimitPagination(JsonApiLimitOffsetPagination): offset_query_param = 'offset' limit_query_param = 'limit' + default_limit = 3 max_limit = None ``` @@ -146,7 +155,7 @@ If you are also using [`rest_framework.filters.SearchFilter`](https://django-res (which performs single parameter searchs across multiple fields) you'll want to customize the name of the query parameter for searching to make sure it doesn't conflict with a field name defined in the filterset. The recommended value is: `search_param="filter[search]"` but just make sure it's -`filter[_something_]` to comply with the jsonapi spec requirement to use the filter +`filter[_something_]` to comply with the JSON:API spec requirement to use the filter keyword. The default is "search" unless overriden. The filter returns a `400 Bad Request` error for invalid filter query parameters as in this example @@ -446,7 +455,7 @@ class OrderSerializer(serializers.ModelSerializer): ``` -In the [JSON API spec](http://jsonapi.org/format/#document-resource-objects), +In the [JSON:API spec](http://jsonapi.org/format/#document-resource-objects), relationship objects contain links to related objects. To make this work on a serializer we need to tell the `ResourceRelatedField` about the corresponding view. Use the `HyperlinkedModelSerializer` and instantiate @@ -584,7 +593,7 @@ class OrderSerializer(serializers.HyperlinkedModelSerializer): ### RelationshipView `rest_framework_json_api.views.RelationshipView` is used to build relationship views (see the -[JSON API spec](http://jsonapi.org/format/#fetching-relationships)). +[JSON:API spec](http://jsonapi.org/format/#fetching-relationships)). The `self` link on a relationship object should point to the corresponding relationship view. diff --git a/example/settings/test.py b/example/settings/test.py index bbf6e400..c165e187 100644 --- a/example/settings/test.py +++ b/example/settings/test.py @@ -12,6 +12,7 @@ JSON_API_FIELD_NAMES = 'camelize' JSON_API_FORMAT_TYPES = 'camelize' JSON_API_PLURALIZE_TYPES = True + REST_FRAMEWORK.update({ 'PAGE_SIZE': 1, }) diff --git a/example/tests/unit/test_pagination.py b/example/tests/unit/test_pagination.py index 5fdcade6..9da8675f 100644 --- a/example/tests/unit/test_pagination.py +++ b/example/tests/unit/test_pagination.py @@ -13,11 +13,11 @@ class TestLimitOffset: """ - Unit tests for `pagination.JSONAPILimitOffsetPagination`. + Unit tests for `pagination.JsonApiLimitOffsetPagination`. """ def setup(self): - class ExamplePagination(pagination.JSONAPILimitOffsetPagination): + class ExamplePagination(pagination.JsonApiLimitOffsetPagination): default_limit = 10 max_limit = 15 @@ -79,33 +79,71 @@ def test_valid_offset_limit(self): assert queryset == list(range(offset + 1, next_offset + 1)) assert content == expected_content + @pytest.mark.xfail((sys.version_info.major, sys.version_info.minor) == (2, 7), + reason="python2.7 fails to generate DeprecationWarrning for unknown reason") def test_limit_offset_deprecation(self): with pytest.warns(DeprecationWarning) as record: pagination.LimitOffsetPagination() assert len(record) == 1 - assert 'LimitOffsetPagination' in str(record[0].message) + assert 'LimitOffsetPagination is deprecated' in str(record[0].message) + class MyInheritedLimitOffsetPagination(pagination.LimitOffsetPagination): + """ + Inherit the default values + """ + pass + + class MyOverridenLimitOffsetPagination(pagination.LimitOffsetPagination): + """ + Explicitly set max_limit to the "old" values. + """ + max_limit = None + + def test_my_limit_offset_deprecation(self): with pytest.warns(DeprecationWarning) as record: - pagination.JsonApiLimitOffsetPagination() + self.MyInheritedLimitOffsetPagination() assert len(record) == 1 - assert 'JsonApiLimitOffsetPagination' in str(record[0].message) + assert 'LimitOffsetPagination is deprecated' in str(record[0].message) + + with pytest.warns(None) as record: + self.MyOverridenLimitOffsetPagination() + assert len(record) == 0 -# TODO: This test fails under py27 but it's not clear why so just leave it out for now. -@pytest.mark.xfail((sys.version_info.major, sys.version_info.minor) == (2, 7), - reason="python2.7 fails for unknown reason") class TestPageNumber: """ - Unit tests for `pagination.JSONAPIPageNumberPagination`. - TODO: add unit tests for changing query parameter names, limits, etc. + Unit tests for `pagination.JsonApiPageNumberPagination`. """ + + @pytest.mark.xfail((sys.version_info.major, sys.version_info.minor) == (2, 7), + reason="python2.7 fails to generate DeprecationWarrning for unknown reason") def test_page_number_deprecation(self): with pytest.warns(DeprecationWarning) as record: pagination.PageNumberPagination() assert len(record) == 1 - assert 'PageNumberPagination' in str(record[0].message) + assert 'PageNumberPagination is deprecated' in str(record[0].message) + class MyInheritedPageNumberPagination(pagination.PageNumberPagination): + """ + Inherit the default values + """ + pass + + class MyOverridenPageNumberPagination(pagination.PageNumberPagination): + """ + Explicitly set page_query_param and page_size_query_param to the "old" values. + """ + page_query_param = "page" + page_size_query_param = "page_size" + + @pytest.mark.xfail((sys.version_info.major, sys.version_info.minor) == (2, 7), + reason="python2.7 fails to generate DeprecationWarrning for unknown reason") + def test_my_page_number_deprecation(self): with pytest.warns(DeprecationWarning) as record: - pagination.JsonApiPageNumberPagination() + self.MyInheritedPageNumberPagination() assert len(record) == 1 - assert 'JsonApiPageNumberPagination' in str(record[0].message) + assert 'PageNumberPagination is deprecated' in str(record[0].message) + + with pytest.warns(None) as record: + self.MyOverridenPageNumberPagination() + assert len(record) == 0 diff --git a/example/views.py b/example/views.py index f3b9ccec..98907902 100644 --- a/example/views.py +++ b/example/views.py @@ -35,7 +35,7 @@ def get_object(self): return super(BlogViewSet, self).get_object() -class JSONAPIViewSet(ModelViewSet): +class JsonApiViewSet(ModelViewSet): """ This is an example on how to configure DRF-jsonapi from within a class. It allows using DRF-jsonapi alongside @@ -59,12 +59,12 @@ def handle_exception(self, exc): exc.status_code = HTTP_422_UNPROCESSABLE_ENTITY # exception handler can't be set on class so you have to # override the error response in this method - response = super(JSONAPIViewSet, self).handle_exception(exc) + response = super(JsonApiViewSet, self).handle_exception(exc) context = self.get_exception_handler_context() return format_drf_errors(response, context, exc) -class BlogCustomViewSet(JSONAPIViewSet): +class BlogCustomViewSet(JsonApiViewSet): queryset = Blog.objects.all() serializer_class = BlogSerializer diff --git a/rest_framework_json_api/pagination.py b/rest_framework_json_api/pagination.py index b150aa83..281c78f2 100644 --- a/rest_framework_json_api/pagination.py +++ b/rest_framework_json_api/pagination.py @@ -9,9 +9,10 @@ from rest_framework.views import Response -class JSONAPIPageNumberPagination(PageNumberPagination): +class JsonApiPageNumberPagination(PageNumberPagination): """ - A json-api compatible pagination format + A json-api compatible pagination format. + Use a private name for the implementation because the public name is pending deprecation. """ page_query_param = 'page[number]' page_size_query_param = 'page[size]' @@ -50,11 +51,13 @@ def get_paginated_response(self, data): }) -class JSONAPILimitOffsetPagination(LimitOffsetPagination): +class JsonApiLimitOffsetPagination(LimitOffsetPagination): """ A limit/offset based style. For example: http://api.example.org/accounts/?page[limit]=100 http://api.example.org/accounts/?page[offset]=400&page[limit]=100 + + Use a private name for the implementation because the public name is pending deprecation. """ limit_query_param = 'page[limit]' offset_query_param = 'page[offset]' @@ -100,63 +103,48 @@ def get_paginated_response(self, data): }) -class JsonApiPageNumberPagination(JSONAPIPageNumberPagination): +class PageNumberPagination(JsonApiPageNumberPagination): """ - Deprecated due to desire to use `JSONAPI` prefix for all classes. + A soon-to-be-changed paginator that uses non-JSON:API query parameters (default: + 'page' and 'page_size' instead of 'page[number]' and 'page[size]'). """ page_query_param = 'page' page_size_query_param = 'page_size' def __init__(self): - warnings.warn( - 'JsonApiPageNumberPagination is deprecated. Use JSONAPIPageNumberPagination ' - 'or create custom pagination. See ' - 'https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#pagination', - DeprecationWarning) - super(JsonApiPageNumberPagination, self).__init__() - - -class PageNumberPagination(JSONAPIPageNumberPagination): - """ - Deprecated paginator that uses different query parameters - """ - page_query_param = 'page' - page_size_query_param = 'page_size' + if type(self) == PageNumberPagination: + warn = self.page_query_param == 'page' or self.page_size_query_param == 'page_size' + else: # inherited class doesn't override the attributes? + warn = ('page_query_param' not in type(self).__dict__ or + 'page_size_query_param' not in type(self).__dict__) + if warn: + warnings.warn( + 'PageNumberPagination is deprecated use JsonApiPageNumberPagination instead. ' + 'If you want to retain current defaults you will need to implement custom ' + 'pagination class explicitly setting `page_query_param = "page"` and ' + '`page_size_query_param = "page_size"`. ' + 'See changelog for more details.', + DeprecationWarning) - def __init__(self): - warnings.warn( - 'PageNumberPagination is deprecated. Use JSONAPIPageNumberPagination ' - 'or create custom pagination. See ' - 'https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#pagination', - DeprecationWarning) super(PageNumberPagination, self).__init__() -class JsonApiLimitOffsetPagination(JSONAPILimitOffsetPagination): - """ - Deprecated due to desire to use `JSONAPI` prefix for all classes. - """ - max_limit = None - - def __init__(self): - warnings.warn( - 'JsonApiLimitOffsetPagination is deprecated. Use JSONAPILimitOffsetPagination ' - 'or create custom pagination. See ' - 'https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#pagination', - DeprecationWarning) - super(JsonApiLimitOffsetPagination, self).__init__() - - -class LimitOffsetPagination(JSONAPILimitOffsetPagination): +class LimitOffsetPagination(JsonApiLimitOffsetPagination): """ Deprecated paginator that uses a different max_limit """ max_limit = None def __init__(self): - warnings.warn( - 'LimitOffsetPagination is deprecated. Use JSONAPILimitOffsetPagination ' - 'or create custom pagination. See ' - 'https://django-rest-framework-json-api.readthedocs.io/en/stable/usage.html#pagination', - DeprecationWarning) + if type(self) == LimitOffsetPagination: + warn = self.max_limit is None + else: + warn = 'max_limit' not in type(self).__dict__ + if warn: + warnings.warn( + 'LimitOffsetPagination is deprecated use JsonApiLimitOffsetPagination instead. ' + 'If you want to retain current defaults you will need to implement custom ' + 'pagination class explicitly setting `max_limit = None`. ' + 'See changelog for more details.', + DeprecationWarning) super(LimitOffsetPagination, self).__init__()