From ec90ed20e47e4895de0ec95b3c082ce119574ded Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 21:41:43 -0700 Subject: [PATCH 01/14] json complex backend --- README.rst | 20 +++++ rest_framework_filters/backends.py | 60 +++++++++++++++ tests/test_backends.py | 115 ++++++++++++++++++++++++++++- tests/testapp/urls.py | 1 + tests/testapp/views.py | 13 ++++ 5 files changed, 208 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 5c360ee..7970301 100644 --- a/README.rst +++ b/README.rst @@ -584,6 +584,26 @@ errors would be raised like so: { +Complex JSON Filtering with Boolean Logic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``ComplexJsonFilterBackend`` backend allows a user to filter using a JSON definition instead of an encoded string. Pass an encoded representation of a json object that has a top-level `or` or `and` key, mapped to an array of clauses to the `json_filters` option. These clauses can either be other `or` or `and` clauses or a mapping of query params to their values. For example: + +``` +filters = { + "or": [ + { + "title__startswith": "Who" + }, + { + "title__startswith": "What" + } + ] +} +querystring = f"json_filters={quote(json.dumps(filters))}" + +``` + Migrating to 1.0 ---------------- diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index 0901f3f..0338d82 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -1,13 +1,18 @@ +import json from contextlib import contextmanager +from django.db.models import QuerySet from django.http import QueryDict from django_filters import compat from django_filters.rest_framework import backends from rest_framework.exceptions import ValidationError from .complex_ops import combine_complex_queryset, decode_complex_ops + from .filterset import FilterSet +COMPLEX_JSON_OPERATORS = {"and": QuerySet.__and__, "or": QuerySet.__or__} + class RestFrameworkFilterBackend(backends.DjangoFilterBackend): filterset_base = FilterSet @@ -94,3 +99,58 @@ def get_filtered_querysets(self, querystrings, request, queryset, view): if errors: raise ValidationError(errors) return querysets + + +class ComplexJsonFilterBackend(RestFrameworkFilterBackend): + complex_filter_param = "json_filters" + + def filter_queryset(self, request, queryset, view): + if self.complex_filter_param not in request.query_params: + return super().filter_queryset(request, queryset, view) + + encoded_querystring = request.query_params[self.complex_filter_param] + try: + complex_ops = json.loads(encoded_querystring) + return self.combine_filtered_querysets(complex_ops, request, queryset, view) + except ValidationError as exc: + raise ValidationError({self.complex_filter_param: exc.detail}) + + def combine_filtered_querysets(self, complex_filter, request, queryset, view): + """ + Function used recursively to filter the complex filter boolean logic + Args: + complex_filter: the json complex filter + request: request + queryset: starting queryset, unfiltered + view: the view + + Returns: + queryset + """ + operator = None + combined_queryset = None + for symbol, complex_operator in COMPLEX_JSON_OPERATORS.items(): + if operator is None and symbol in complex_filter: + operator = complex_operator + for sub_filter in complex_filter[symbol]: + filtered_queryset = self.combine_filtered_querysets(sub_filter, request, queryset, view) + if combined_queryset is None: + combined_queryset = filtered_queryset + else: + combined_queryset = complex_operator(combined_queryset, filtered_queryset) + if operator: + return combined_queryset + + return self.get_filtered_queryset( + "&".join([f"{k}={v}" for k, v in complex_filter.items()]), request, queryset, view + ) + + def get_filtered_queryset(self, querystring, request, queryset, view): + original_GET = request._request.GET + request._request.GET = QueryDict(querystring) + try: + res = super().filter_queryset(request, queryset, view) + finally: + request._request.GET = original_GET + return res + diff --git a/tests/test_backends.py b/tests/test_backends.py index f0bdc72..b538646 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -1,5 +1,5 @@ from urllib.parse import quote, urlencode - +import json import django_filters from django.test import modify_settings from rest_framework import status @@ -464,3 +464,116 @@ def test_pagination_compatibility(self): [r['username'] for r in response.data['results']], ['user3'] ) + + +class ComplexJsonFilterBackendTests(APITestCase): + + @classmethod + def setUpTestData(cls): + models.User.objects.create(username="user1", email="user1@example.com") + models.User.objects.create(username="user2", email="user2@example.com") + models.User.objects.create(username="user3", email="user3@example.org") + models.User.objects.create(username="user4", email="user4@example.org") + + def test_valid(self): + readable = json.dumps({ + "or": [ + { + "username": "user1" + }, + { + "email__contains": "example.org" + } + ] + }) + response = self.client.get('/ffjsoncomplex-users/?json_filters=' + quote(readable), content_type='json') + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + [r['username'] for r in response.data], + ['user1', 'user3', 'user4'] + ) + + def test_invalid(self): + readable = json.dumps({ + "or": [ + { + "username": "user1" + }, + { + "email__contains": "example.org" + } + ] + })[0:10] + response = self.client.get('/ffjsoncomplex-users/?json_filters=' + quote(readable), content_type='json') + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.data, { + 'filters': ["Invalid querystring operator. Matched: 'asdf'."], + }) + + def test_invalid_filterset_errors(self): + readable = json.dumps({ + "or": [ + { + "id": "foo" + }, + { + "id": "bar" + } + ] + }) + response = self.client.get('/ffjsoncomplex-users/?json_filters=' + quote(readable), content_type='json') + + self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) + self.assertDictEqual(response.data, { + 'filters': { + 'id=foo': { + 'id': ['Enter a number.'], + }, + 'id=bar': { + 'id': ['Enter a number.'], + }, + }, + }) + + def test_pagination_compatibility(self): + """ + Ensure that complex-filtering does not interfere with additional query param processing. + """ + readable = json.dumps({ + "or": [ + { + "email__contains": "example.org" + } + ] + }) + + # sanity check w/o pagination + response = self.client.get('/ffjsoncomplex-users/?json_filters=' + quote(readable), content_type='json') + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertListEqual( + [r['username'] for r in response.data], + ['user3', 'user4'] + ) + + # sanity check w/o complex-filtering + response = self.client.get('/ffjsoncomplex-users/?page_size=1', content_type='json') + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('results', response.data) + self.assertListEqual( + [r['username'] for r in response.data['results']], + ['user1'] + ) + + # pagination + complex-filtering + response = self.client.get('/ffjsoncomplex-users/?page_size=1&filters=' + quote(readable), content_type='json') + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertIn('results', response.data) + self.assertListEqual( + [r['username'] for r in response.data['results']], + ['user3'] + ) diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py index 5ac3e17..eb099d0 100644 --- a/tests/testapp/urls.py +++ b/tests/testapp/urls.py @@ -8,6 +8,7 @@ router.register(r'df-users', views.DFUserViewSet, basename='df-users') router.register(r'ff-users', views.FilterFieldsUserViewSet, basename='ff-users') router.register(r'ffcomplex-users', views.ComplexFilterFieldsUserViewSet, basename='ffcomplex-users') +router.register(r'ffjsoncomplex-users', views.ComplexJsonFilterFieldsUserViewSet, basename='ffcomplex-users') router.register(r'users', views.UserViewSet,) router.register(r'notes', views.NoteViewSet,) diff --git a/tests/testapp/views.py b/tests/testapp/views.py index 54e3132..8be0d87 100644 --- a/tests/testapp/views.py +++ b/tests/testapp/views.py @@ -52,6 +52,19 @@ class pagination_class(pagination.PageNumberPagination): page_size_query_param = 'page_size' +class ComplexJsonFilterFieldsUserViewSet(FilterFieldsUserViewSet): + queryset = User.objects.order_by('pk') + filter_backends = (backends.ComplexJsonFilterBackend, ) + filterset_fields = { + 'id': '__all__', + 'username': '__all__', + 'email': '__all__', + } + + class pagination_class(pagination.PageNumberPagination): + page_size_query_param = 'page_size' + + class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer From cee07278069b421f8def7df6382d4446660375ff Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 21:47:08 -0700 Subject: [PATCH 02/14] no string interpolation --- README.rst | 2 +- rest_framework_filters/backends.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 7970301..c4ff3f1 100644 --- a/README.rst +++ b/README.rst @@ -600,7 +600,7 @@ filters = { } ] } -querystring = f"json_filters={quote(json.dumps(filters))}" +querystring = "json_filters={filters}".format(filters=quote(json.dumps(filters))) ``` diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index 0338d82..b3d2ddc 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -142,7 +142,7 @@ def combine_filtered_querysets(self, complex_filter, request, queryset, view): return combined_queryset return self.get_filtered_queryset( - "&".join([f"{k}={v}" for k, v in complex_filter.items()]), request, queryset, view + "&".join(["{k}={v}".format(k=k, v=v) for k, v in complex_filter.items()]), request, queryset, view ) def get_filtered_queryset(self, querystring, request, queryset, view): From deb0a21d0e53d6827e7faf2ac84c4396586c9fcf Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 21:58:00 -0700 Subject: [PATCH 03/14] fixes --- rest_framework_filters/backends.py | 2 +- tests/test_backends.py | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index b3d2ddc..6430385 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -108,8 +108,8 @@ def filter_queryset(self, request, queryset, view): if self.complex_filter_param not in request.query_params: return super().filter_queryset(request, queryset, view) - encoded_querystring = request.query_params[self.complex_filter_param] try: + encoded_querystring = request.query_params[self.complex_filter_param] complex_ops = json.loads(encoded_querystring) return self.combine_filtered_querysets(complex_ops, request, queryset, view) except ValidationError as exc: diff --git a/tests/test_backends.py b/tests/test_backends.py index b538646..eab6cb0 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -526,14 +526,9 @@ def test_invalid_filterset_errors(self): response = self.client.get('/ffjsoncomplex-users/?json_filters=' + quote(readable), content_type='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.data, { - 'filters': { - 'id=foo': { - 'id': ['Enter a number.'], - }, - 'id=bar': { - 'id': ['Enter a number.'], - }, + self.assertDictEqual(response.json(), { + 'json_filters': { + 'id': "Enter a number", }, }) @@ -569,7 +564,7 @@ def test_pagination_compatibility(self): ) # pagination + complex-filtering - response = self.client.get('/ffjsoncomplex-users/?page_size=1&filters=' + quote(readable), content_type='json') + response = self.client.get('/ffjsoncomplex-users/?page_size=1&json_filters=' + quote(readable), content_type='json') self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('results', response.data) From ad0b1ca66eb2370ab7a479349d52e0a1834bac65 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 22:03:39 -0700 Subject: [PATCH 04/14] handle parsing json --- rest_framework_filters/backends.py | 2 ++ tests/test_backends.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index 6430385..8d6cf3b 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -114,6 +114,8 @@ def filter_queryset(self, request, queryset, view): return self.combine_filtered_querysets(complex_ops, request, queryset, view) except ValidationError as exc: raise ValidationError({self.complex_filter_param: exc.detail}) + except json.decoder.JSONDecodeError: + raise ValidationError({self.complex_filter_param: "unable to parse json"}) def combine_filtered_querysets(self, complex_filter, request, queryset, view): """ diff --git a/tests/test_backends.py b/tests/test_backends.py index eab6cb0..3dcf66c 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -509,7 +509,7 @@ def test_invalid(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.data, { - 'filters': ["Invalid querystring operator. Matched: 'asdf'."], + 'json_filters': ["unable to parse json"], }) def test_invalid_filterset_errors(self): @@ -528,7 +528,7 @@ def test_invalid_filterset_errors(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { 'json_filters': { - 'id': "Enter a number", + 'id': ["Enter a number"], }, }) From ab5d0a9af66fb6d5bbe3a92b3e93d0b9bc29440d Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 22:07:38 -0700 Subject: [PATCH 05/14] fixes --- rest_framework_filters/backends.py | 2 +- tests/test_backends.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index 8d6cf3b..974604c 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -115,7 +115,7 @@ def filter_queryset(self, request, queryset, view): except ValidationError as exc: raise ValidationError({self.complex_filter_param: exc.detail}) except json.decoder.JSONDecodeError: - raise ValidationError({self.complex_filter_param: "unable to parse json"}) + raise ValidationError({self.complex_filter_param: "unable to parse json."}) def combine_filtered_querysets(self, complex_filter, request, queryset, view): """ diff --git a/tests/test_backends.py b/tests/test_backends.py index 3dcf66c..aebb855 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -508,8 +508,8 @@ def test_invalid(self): response = self.client.get('/ffjsoncomplex-users/?json_filters=' + quote(readable), content_type='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) - self.assertDictEqual(response.data, { - 'json_filters': ["unable to parse json"], + self.assertDictEqual(response.json(), { + 'json_filters': ["unable to parse json."], }) def test_invalid_filterset_errors(self): @@ -528,7 +528,7 @@ def test_invalid_filterset_errors(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { 'json_filters': { - 'id': ["Enter a number"], + 'id': ["Enter a number."], }, }) From 2df4b126d42d1341f6f123c9170a37eda02e2455 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 22:11:39 -0700 Subject: [PATCH 06/14] fixes --- tests/test_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_backends.py b/tests/test_backends.py index aebb855..10e6b06 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -509,7 +509,7 @@ def test_invalid(self): self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertDictEqual(response.json(), { - 'json_filters': ["unable to parse json."], + 'json_filters': "unable to parse json.", }) def test_invalid_filterset_errors(self): From b5191d5153f9eeca8f335311759cf25d75994b12 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 22:15:59 -0700 Subject: [PATCH 07/14] linting fixes --- rest_framework_filters/backends.py | 2 -- tests/test_backends.py | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index 974604c..a6b5d42 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -8,7 +8,6 @@ from rest_framework.exceptions import ValidationError from .complex_ops import combine_complex_queryset, decode_complex_ops - from .filterset import FilterSet COMPLEX_JSON_OPERATORS = {"and": QuerySet.__and__, "or": QuerySet.__or__} @@ -155,4 +154,3 @@ def get_filtered_queryset(self, querystring, request, queryset, view): finally: request._request.GET = original_GET return res - diff --git a/tests/test_backends.py b/tests/test_backends.py index 10e6b06..d8a7246 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -1,5 +1,6 @@ -from urllib.parse import quote, urlencode import json +from urllib.parse import quote, urlencode + import django_filters from django.test import modify_settings from rest_framework import status @@ -8,7 +9,6 @@ from rest_framework_filters import FilterSet, filters from rest_framework_filters.backends import RestFrameworkFilterBackend from rest_framework_filters.filterset import SubsetDisabledMixin - from .testapp import models, views factory = APIRequestFactory() From 599bc5efd404fa7a49ebbf76507c92b9894ff398 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 22:20:46 -0700 Subject: [PATCH 08/14] fix code block and indentation --- README.rst | 28 +++++++++++++++------------- tests/test_backends.py | 1 + 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.rst b/README.rst index c4ff3f1..7db06aa 100644 --- a/README.rst +++ b/README.rst @@ -589,20 +589,22 @@ Complex JSON Filtering with Boolean Logic The ``ComplexJsonFilterBackend`` backend allows a user to filter using a JSON definition instead of an encoded string. Pass an encoded representation of a json object that has a top-level `or` or `and` key, mapped to an array of clauses to the `json_filters` option. These clauses can either be other `or` or `and` clauses or a mapping of query params to their values. For example: -``` -filters = { - "or": [ - { - "title__startswith": "Who" - }, - { - "title__startswith": "What" - } - ] -} -querystring = "json_filters={filters}".format(filters=quote(json.dumps(filters))) +.. code-block:: python + + filters = { + "or": [ + { + "title__startswith": "Who" + }, + { + "title__startswith": "What" + } + ] + } + querystring = "json_filters={filters}".format( + filters=quote(json.dumps(filters)) + ) -``` Migrating to 1.0 ---------------- diff --git a/tests/test_backends.py b/tests/test_backends.py index d8a7246..742a0d3 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -9,6 +9,7 @@ from rest_framework_filters import FilterSet, filters from rest_framework_filters.backends import RestFrameworkFilterBackend from rest_framework_filters.filterset import SubsetDisabledMixin + from .testapp import models, views factory = APIRequestFactory() From 73c5661b463db05feb7fe23bcdf35b4a7abd192f Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 15 Nov 2019 22:24:54 -0700 Subject: [PATCH 09/14] fix long line --- tests/test_backends.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/test_backends.py b/tests/test_backends.py index 742a0d3..1caa95d 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -565,7 +565,10 @@ def test_pagination_compatibility(self): ) # pagination + complex-filtering - response = self.client.get('/ffjsoncomplex-users/?page_size=1&json_filters=' + quote(readable), content_type='json') + response = self.client.get( + '/ffjsoncomplex-users/?page_size=1&json_filters=' + quote(readable), + content_type='json' + ) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertIn('results', response.data) From 14e12246e1264cd06ecd8a79b665b1ead167cf65 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Wed, 20 Nov 2019 20:33:02 -0700 Subject: [PATCH 10/14] more comprehensive example --- README.rst | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 7db06aa..4e2d713 100644 --- a/README.rst +++ b/README.rst @@ -587,18 +587,25 @@ errors would be raised like so: Complex JSON Filtering with Boolean Logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``ComplexJsonFilterBackend`` backend allows a user to filter using a JSON definition instead of an encoded string. Pass an encoded representation of a json object that has a top-level `or` or `and` key, mapped to an array of clauses to the `json_filters` option. These clauses can either be other `or` or `and` clauses or a mapping of query params to their values. For example: +The ``ComplexJsonFilterBackend`` backend allows a user to filter using a JSON definition instead of an encoded string. Pass an encoded representation of a json object that has a top-level `or` or `and` key, mapped to an array of clauses to the `json_filters` option. These clauses can either be other `or` or `and` clauses or a mapping of query params to their values. For example to query for all resources where (title does not contain "Why") AND (title starts with "Who" OR title starts with "What"): .. code-block:: python filters = { - "or": [ + "and": [ { - "title__startswith": "Who" + "or": [ + { + "title__startswith": "Who" + }, + { + "title__startswith": "What" + }, + ] }, { - "title__startswith": "What" - } + "title__icontains!": "Why" + }, ] } querystring = "json_filters={filters}".format( From 201c48f21b8ea5ea9311173457eb45900c545952 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Wed, 27 Nov 2019 19:44:15 -0700 Subject: [PATCH 11/14] fix error with combining other query params and complex one --- rest_framework_filters/backends.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index a6b5d42..2b49b82 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -104,13 +104,14 @@ class ComplexJsonFilterBackend(RestFrameworkFilterBackend): complex_filter_param = "json_filters" def filter_queryset(self, request, queryset, view): + res = super().filter_queryset(request, queryset, view) if self.complex_filter_param not in request.query_params: - return super().filter_queryset(request, queryset, view) + return res + encoded_querystring = request.query_params[self.complex_filter_param] try: - encoded_querystring = request.query_params[self.complex_filter_param] complex_ops = json.loads(encoded_querystring) - return self.combine_filtered_querysets(complex_ops, request, queryset, view) + return self.combine_filtered_querysets(complex_ops, request, res, view) except ValidationError as exc: raise ValidationError({self.complex_filter_param: exc.detail}) except json.decoder.JSONDecodeError: From 6252a9423dc265a793f1a504d28687045a1c7aba Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 13 Dec 2019 10:17:28 -0700 Subject: [PATCH 12/14] Json -> JSON --- README.rst | 2 +- rest_framework_filters/backends.py | 2 +- tests/testapp/urls.py | 2 +- tests/testapp/views.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 4e2d713..6c1d0fe 100644 --- a/README.rst +++ b/README.rst @@ -587,7 +587,7 @@ errors would be raised like so: Complex JSON Filtering with Boolean Logic ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The ``ComplexJsonFilterBackend`` backend allows a user to filter using a JSON definition instead of an encoded string. Pass an encoded representation of a json object that has a top-level `or` or `and` key, mapped to an array of clauses to the `json_filters` option. These clauses can either be other `or` or `and` clauses or a mapping of query params to their values. For example to query for all resources where (title does not contain "Why") AND (title starts with "Who" OR title starts with "What"): +The ``ComplexJSONFilterBackend`` backend allows a user to filter using a JSON definition instead of an encoded string. Pass an encoded representation of a json object that has a top-level `or` or `and` key, mapped to an array of clauses to the `json_filters` option. These clauses can either be other `or` or `and` clauses or a mapping of query params to their values. For example to query for all resources where (title does not contain "Why") AND (title starts with "Who" OR title starts with "What"): .. code-block:: python diff --git a/rest_framework_filters/backends.py b/rest_framework_filters/backends.py index 2b49b82..e4538fa 100644 --- a/rest_framework_filters/backends.py +++ b/rest_framework_filters/backends.py @@ -100,7 +100,7 @@ def get_filtered_querysets(self, querystrings, request, queryset, view): return querysets -class ComplexJsonFilterBackend(RestFrameworkFilterBackend): +class ComplexJSONFilterBackend(RestFrameworkFilterBackend): complex_filter_param = "json_filters" def filter_queryset(self, request, queryset, view): diff --git a/tests/testapp/urls.py b/tests/testapp/urls.py index eb099d0..489243b 100644 --- a/tests/testapp/urls.py +++ b/tests/testapp/urls.py @@ -8,7 +8,7 @@ router.register(r'df-users', views.DFUserViewSet, basename='df-users') router.register(r'ff-users', views.FilterFieldsUserViewSet, basename='ff-users') router.register(r'ffcomplex-users', views.ComplexFilterFieldsUserViewSet, basename='ffcomplex-users') -router.register(r'ffjsoncomplex-users', views.ComplexJsonFilterFieldsUserViewSet, basename='ffcomplex-users') +router.register(r'ffjsoncomplex-users', views.ComplexJSONFilterFieldsUserViewSet, basename='ffcomplex-users') router.register(r'users', views.UserViewSet,) router.register(r'notes', views.NoteViewSet,) diff --git a/tests/testapp/views.py b/tests/testapp/views.py index 8be0d87..3a374c5 100644 --- a/tests/testapp/views.py +++ b/tests/testapp/views.py @@ -52,7 +52,7 @@ class pagination_class(pagination.PageNumberPagination): page_size_query_param = 'page_size' -class ComplexJsonFilterFieldsUserViewSet(FilterFieldsUserViewSet): +class ComplexJSONFilterFieldsUserViewSet(FilterFieldsUserViewSet): queryset = User.objects.order_by('pk') filter_backends = (backends.ComplexJsonFilterBackend, ) filterset_fields = { From 84a1a5ac1e5016e423597becabb0b9fa66de27be Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 13 Dec 2019 10:18:57 -0700 Subject: [PATCH 13/14] Json -> JSON --- tests/test_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_backends.py b/tests/test_backends.py index 1caa95d..c4aa110 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -467,7 +467,7 @@ def test_pagination_compatibility(self): ) -class ComplexJsonFilterBackendTests(APITestCase): +class ComplexJSONFilterBackendTests(APITestCase): @classmethod def setUpTestData(cls): From c51d12ea8d170b61301bd18ea77d92941593c883 Mon Sep 17 00:00:00 2001 From: matt ferrante Date: Fri, 13 Dec 2019 10:37:38 -0700 Subject: [PATCH 14/14] Json -> JSON --- tests/testapp/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testapp/views.py b/tests/testapp/views.py index 3a374c5..60e690d 100644 --- a/tests/testapp/views.py +++ b/tests/testapp/views.py @@ -54,7 +54,7 @@ class pagination_class(pagination.PageNumberPagination): class ComplexJSONFilterFieldsUserViewSet(FilterFieldsUserViewSet): queryset = User.objects.order_by('pk') - filter_backends = (backends.ComplexJsonFilterBackend, ) + filter_backends = (backends.ComplexJSONFilterBackend, ) filterset_fields = { 'id': '__all__', 'username': '__all__',