Repository: django-json-api/django-rest-framework-json-api

Issue:
naming style: JSONAPI prefix or not?
[This started as a misplaced comment thread in #467. Let's move it here.]

I have a naming question: Do we prefix everything with `JSONAPI` or override existing names in DRF? We now have both styles:
- DJA serializers override DRF's `HyperlinkedModelSerializer` and so on. The advantage is code written for DRF can upgrade to DJA by simply changing an import. The disadvantage is that it's easy to "forget" to change the import from `rest_framework` to `rest_framework_json_api`.
- DJA paginators are named `JSONAPIPageNumberPagination` instead of overriding DRF's `PageNumberPagination`. The advantage is that it's very obvious that this is DJA-specific. The disadvantage is that instead of changing one import, you have to change each class use instance.

The approach here could be either:
- [x] DJA `DefaultRouter` overriding DRF `DefaultRouter`. 
- [ ] DJA `JSONAPIDefaultRouter`

---

@sliverc replied:
> Yeah I know I have also noticed. As we had to make backwards incompatible changes for pagination we started to add the JSONAPI prefix to work around this. There were also other cases in the past where this was done.

> Actually I would prefer not to use `JSONAPI` prefix as it is already clear by the rest_framework_json_api module that it is imported from DJA. As you outlined this way someone can also easily switch between DRF and DJA version by simply changing the import.

> However this won't be easy to accomplish to go that direction to make it consistent and backwards compatible. Potentially we have to break this at some point.

> But for new things (actually haven't thought about that when reviewing the filter backends) we can easily leave out the `JSONAPI` prefix.

> What is your preference?

---

I replied:

>> As we had to make backwards incompatible changes for pagination

> I don't think there was actually a backwards compatibility issue here as if one wants to be backward compatible they would just use `rest_framework.pagination.PageNumberPagination`. (Likewise for LimitOffset.)

>> Potentially we have to break this at some point.

> I say sooner than later. In the last release (2.5.0), we used the prefix `JsonApi` for paginators which is already deprecated by #463. The backend filters are not yet in a released version so have never been "seen" by their JSONAPI-prefixed names.

> The only other instance I was able to find was `rest_framework_json_api.metadata.JSONAPIMetadata` which extends `rest_framework.metadata.SimpleMetadata`; This could easily be renamed (with deprecation) to `rest_framework_json_api.metadata.SimpleMetadata` which would more clearly show the inheritance as well.

> The only other prefix I saw like this is `JSONAPIMeta` serializer inner class which I think is required to stay that way to distinguish it from the `Meta` inner class. 

>> What is your preference?

> My preference is to put in the effort now:
> - [x] drop the JSONAPI prefix from #466 before merging it. We have an open question (https://github.com/django-json-api/django-rest-framework-json-api/pull/466#discussion_r213601201) around naming it as well.
> - [ ] new PR to back out the prefix changes done in #463 and eliminate the JsonApi prefix and add deprecation warnings for `page` vs. `page[number]` and `page_size` vs. `page[size]`. Or just document this as a breaking change: `page` and `page_size` were never correct JSON:API usage.
> - [ ] new PR to rename/deprecate `JSONAPIMetadata`.

> Do you agree?

---

@sliverc wrote:

> There is also `JSONAPISettings` which we could rename to `APISettings`.

> Also I have noticed that we are kind of getting into the habit to misuse some issues to discuss things which might be related but not actually the same issue.

> I think for outsiders to better follow and to better track issues (e.g. a PR should solve one issue and not a comment of an issue) it would be better to create new issues for this (in this case a new issue where discussion happens on JSONAPI prefix).

> I will also follow up on a issue for versioning and deprecation policy.


Discussion:

Resolving patch:
diff --git a/example/views.py b/example/views.py
--- 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
--- 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__()


Test patch:
diff --git a/example/settings/test.py b/example/settings/test.py
--- 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
--- 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