Repository: encode/django-rest-framework

Issue:
Response format change from 3.2.x to 3.3.x
After upgrading DRF from 3.2.5 to 3.3.1 I noticed the api response format changed and no longer represents the format that's shown on the main page.

**Api response with DRF 3.2.5 (expected response)**

```
{
    "count": 1,
    "next": null,
    "previous": null,
    "results": [
        // my object results
    ]
}
```

**Api response with DRF 3.3.1 (only returns list/array)**

```
[
    // my object results
]
```

There is only 1 object returned in the result set so no additional pages exist.  However, this change breaks every api consuming caller since the response format has now changed.



Discussion:
You probably missed previous deprecation affecting the pagination.
Have a look at http://www.django-rest-framework.org/topics/3.3-announcement/#deprecations

If your use case doesn't match the above cases feel free to reopen and if possible add a test case so we can figure the issue.

Reopening to dig a bit deeper. @xordoquy prob correct re. related to deprecation there, but shouldn't be in a position where we've got an altered API response, rather than an error condition (eg hitting an assertion 'this no longer works')

Probably worth a bit more info from @troygrosfield on most minimal way possible to replicate, so we can see if we need to be catching some particular case, and eg raising error XYZ style is no longer supported.

I'll work on the testcase here in a bit, but you can plug any model into a ViewSet and have it do the same thing (alter the response format as mentioned above).

@xordoquy, the deprecation link you provided didn't mention anything about altering response formats in any case which to me would warrant a major version change. 

I'll work on the test case but here was the code I was working with:

``` py
from rest_framework.filters import DjangoFilterBackend
from rest_framework.filters import OrderingFilter
from rest_framework.viewsets import ReadOnlyModelViewSet

from my_models.models import MyModel

from .serializers import MyModelSerializer


class MyModelViewSet(ReadOnlyModelViewSet):
    queryset = MyModel.objects.all()
    serializer_class = MyModelSerializer
    permission_classes = []
    filter_backends = (OrderingFilter, DjangoFilterBackend)
    filter_fields = ('some_field',)
    ordering = ('-id',)
    page_query_param = 'p'
```

@troygrosfield What are your pagination settings for the example given?

> Reopening to dig a bit deeper. @xordoquy prob correct re. related to deprecation there, but shouldn't be in a position where we've got an altered API response, rather than an error condition (eg hitting an assertion 'this no longer works')

Makes sense to make it fail loud.
Waiting for the full use case we can reproduce and will retitle the issue by then.

The current settings are below.  

```
# djangorestframework settings
REST_FRAMEWORK = {
    # Use hyperlinked styles by default.
    # Only used if the `serializer_class` attribute is not set on a view.
    'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.HyperlinkedModelSerializer',
    'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'api.authentication.CustomSessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser',
    ),
    'PAGINATE_BY': 25,
    'PAGINATE_BY_PARAM': 'ps',
    'SEARCH_PARAM': 'q',
    'MAX_PAGINATE_BY': 100
}
```

I understand some of those pagination settings have change from the deprecation post you guys outlined which I'm yet to update.  However, I also never received any deprecation messages in the console when running the application which I guess I would've expected similar to what django does.  Example:

```
/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/lib/python3.4/importlib/_bootstrap.py:321: RemovedInDjango19Warning: django.utils.importlib will be removed in Django 1.9.
  return f(*args, **kwds)
```

That was the issue was changing the global settings.  However, that seems to be a major change as that will most certainly break pagination for all all existing code which will require all DRF consumers to make a change.  A 4.0 release version may have been more appropriate.  

**New global settings (for people that come across this in the future)**

``` py
# djangorestframework settings
REST_FRAMEWORK = {
    # Use hyperlinked styles by default.
    # Only used if the `serializer_class` attribute is not set on a view.
    'DEFAULT_MODEL_SERIALIZER_CLASS': 'rest_framework.serializers.HyperlinkedModelSerializer',
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'DEFAULT_FILTER_BACKENDS': ('rest_framework.filters.DjangoFilterBackend',),
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    ),
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'api.authentication.CustomSessionAuthentication',
        'rest_framework.authentication.BasicAuthentication',
    ),
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAdminUser',
    ),
    'PAGE_SIZE': 25,
    'PAGE_SIZE_QUERY_PARAM': 'ps',
    'SEARCH_PARAM': 'q',
    'MAX_PAGE_SIZE': 100
}
```

Thanks for the quick response guys!

Reopening, as should probably still be taking action here, eg.
- Loud error when `PAGINATE_BY` set.
- Loud error when `PAGINATE_BY_PARAM` set.
- Loud error when `MAX_PAGINATE_BY_SET` set.

:+1: agreed.

Furthermore, not convinced on first sight that `PAGE_SIZE` setting is properly documented (eg no mention in deprecation notes)

Actually, paging page and page size aren't being honored now.  Using the following url (with the settings listed above):
- http://api.localmm.com/mymodel?p=2&ps=10

I get the following response:

```
{
    "count": 25,
    "next": null,
    "previous": null,
    "results": [
        // 25 objects
    ]
}
```

If I override the page size in the url, shouldn't that be honored?

Also, `PAGE_SIZE_QUERY_PARAM` isn't documented in the settings page:
- http://www.django-rest-framework.org/api-guide/settings/

Or am I required to override the `DEFAULT_PAGINATION_CLASS` and there is not more global setting for this?

Ignore that last comment.  I was looking at count as the number of results returned and not as the total count.  So that's correct. 

Is it okay if i work on this issue?

@Cheglader I don't think anyone's working on this at the moment.


Resolving patch:
diff --git a/rest_framework/settings.py b/rest_framework/settings.py
--- a/rest_framework/settings.py
+++ b/rest_framework/settings.py
@@ -19,6 +19,8 @@
 """
 from __future__ import unicode_literals
 
+import warnings
+
 from django.conf import settings
 from django.test.signals import setting_changed
 from django.utils import six
@@ -135,6 +137,12 @@
 )
 
 
+# List of settings that have been removed
+REMOVED_SETTINGS = (
+    "PAGINATE_BY", "PAGINATE_BY_PARAM", "MAX_PAGINATE_BY",
+)
+
+
 def perform_import(val, setting_name):
     """
     If the given setting is a string import notation,
@@ -177,7 +185,7 @@ class APISettings(object):
     """
     def __init__(self, user_settings=None, defaults=None, import_strings=None):
         if user_settings:
-            self._user_settings = user_settings
+            self._user_settings = self.__check_user_settings(user_settings)
         self.defaults = defaults or DEFAULTS
         self.import_strings = import_strings or IMPORT_STRINGS
 
@@ -206,6 +214,13 @@ def __getattr__(self, attr):
         setattr(self, attr, val)
         return val
 
+    def __check_user_settings(self, user_settings):
+        SETTINGS_DOC = "http://www.django-rest-framework.org/api-guide/settings/"
+        for setting in REMOVED_SETTINGS:
+            if setting in user_settings:
+                warnings.warn("The '%s' setting has been removed. Please refer to '%s' for available settings." % (setting, SETTINGS_DOC), DeprecationWarning)
+        return user_settings
+
 
 api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
 


Test patch:
diff --git a/tests/test_settings.py b/tests/test_settings.py
--- a/tests/test_settings.py
+++ b/tests/test_settings.py
@@ -1,5 +1,7 @@
 from __future__ import unicode_literals
 
+import warnings
+
 from django.test import TestCase
 
 from rest_framework.settings import APISettings
@@ -18,6 +20,19 @@ def test_import_error_message_maintained(self):
         with self.assertRaises(ImportError):
             settings.DEFAULT_RENDERER_CLASSES
 
+    def test_warning_raised_on_removed_setting(self):
+        """
+        Make sure user is alerted with an error when a removed setting
+        is set.
+        """
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter("always")
+            APISettings({
+                'MAX_PAGINATE_BY': 100
+            })
+            self.assertEqual(len(w), 1)
+            self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
+
 
 class TestSettingTypes(TestCase):
     def test_settings_consistently_coerced_to_list(self):