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

Issue:
spec compliance: sparse fieldsets, sorting & filtering query parameters
Split #416 and refactor as appropriate to implement sparse [fieldsets](http://jsonapi.org/format/#fetching-sparse-fieldsets), [sorting](http://jsonapi.org/format/#fetching-sorting) and [filtering](http://jsonapi.org/format/#fetching-filtering).


Discussion:
See #455

Resolving patch:
diff --git a/example/settings/dev.py b/example/settings/dev.py
--- a/example/settings/dev.py
+++ b/example/settings/dev.py
@@ -89,7 +89,7 @@
     ),
     'DEFAULT_METADATA_CLASS': 'rest_framework_json_api.metadata.JSONAPIMetadata',
     'DEFAULT_FILTER_BACKENDS': (
-        'rest_framework.filters.OrderingFilter',
+        'rest_framework_json_api.backends.JSONAPIOrderingFilter',
     ),
     'ORDERING_PARAM': 'sort',
     'TEST_REQUEST_RENDERER_CLASSES': (
diff --git a/rest_framework_json_api/backends.py b/rest_framework_json_api/backends.py
new file mode 100644
--- /dev/null
+++ b/rest_framework_json_api/backends.py
@@ -0,0 +1,36 @@
+from rest_framework.exceptions import ValidationError
+from rest_framework.filters import OrderingFilter
+
+from rest_framework_json_api.utils import format_value
+
+
+class JSONAPIOrderingFilter(OrderingFilter):
+    """
+    This implements http://jsonapi.org/format/#fetching-sorting and raises 400
+    if any sort field is invalid. If you prefer *not* to report 400 errors for
+    invalid sort fields, just use OrderingFilter with `ordering_param='sort'`
+
+    TODO: Add sorting based upon relationships (sort=relname.fieldname)
+    """
+    ordering_param = 'sort'
+
+    def remove_invalid_fields(self, queryset, fields, view, request):
+        """
+        overrides remove_invalid_fields to raise a 400 exception instead of
+        silently removing them. set `ignore_bad_sort_fields = True` to not
+        do this validation.
+        """
+        valid_fields = [
+            item[0] for item in self.get_valid_fields(queryset, view,
+                                                      {'request': request})
+        ]
+        bad_terms = [
+            term for term in fields
+            if format_value(term.lstrip('-'), "underscore") not in valid_fields
+        ]
+        if bad_terms:
+            raise ValidationError('invalid sort parameter{}: {}'.format(
+                ('s' if len(bad_terms) > 1 else ''), ','.join(bad_terms)))
+
+        return super(JSONAPIOrderingFilter, self).remove_invalid_fields(
+            queryset, fields, view, request)


Test patch:
diff --git a/example/tests/test_backends.py b/example/tests/test_backends.py
new file mode 100644
--- /dev/null
+++ b/example/tests/test_backends.py
@@ -0,0 +1,54 @@
+from rest_framework.reverse import reverse
+from rest_framework.test import APITestCase
+
+from ..models import Blog, Entry
+
+
+class DJATestParameters(APITestCase):
+    """
+    tests of JSON:API backends
+    """
+    fixtures = ('blogentry',)
+
+    def setUp(self):
+        self.entries = Entry.objects.all()
+        self.blogs = Blog.objects.all()
+        self.url = reverse('nopage-entry-list')
+
+    def test_sort(self):
+        """
+        test sort
+        """
+        response = self.client.get(self.url, data={'sort': 'headline'})
+        self.assertEqual(response.status_code, 200,
+                         msg=response.content.decode("utf-8"))
+        dja_response = response.json()
+        headlines = [c['attributes']['headline'] for c in dja_response['data']]
+        sorted_headlines = [c['attributes']['headline'] for c in dja_response['data']]
+        sorted_headlines.sort()
+        self.assertEqual(headlines, sorted_headlines)
+
+    def test_sort_reverse(self):
+        """
+        confirm switching the sort order actually works
+        """
+        response = self.client.get(self.url, data={'sort': '-headline'})
+        self.assertEqual(response.status_code, 200,
+                         msg=response.content.decode("utf-8"))
+        dja_response = response.json()
+        headlines = [c['attributes']['headline'] for c in dja_response['data']]
+        sorted_headlines = [c['attributes']['headline'] for c in dja_response['data']]
+        sorted_headlines.sort()
+        self.assertNotEqual(headlines, sorted_headlines)
+
+    def test_sort_invalid(self):
+        """
+        test sort of invalid field
+        """
+        response = self.client.get(self.url,
+                                   data={'sort': 'nonesuch,headline,-not_a_field'})
+        self.assertEqual(response.status_code, 400,
+                         msg=response.content.decode("utf-8"))
+        dja_response = response.json()
+        self.assertEqual(dja_response['errors'][0]['detail'],
+                         "invalid sort parameters: nonesuch,-not_a_field")