From 7854bd5daf52763190b9b47cf3e57d039a9ccfd7 Mon Sep 17 00:00:00 2001 From: Javier Chamizo Date: Sat, 7 Feb 2015 03:22:22 +0100 Subject: [PATCH] Pass a dict as data to django-filter In order to filter with multivalue params such as 'in' lookups there are two alternatives: * ?multiparam=1,2,3 * ?multiparam=1&multiparam=2 The second option is a better approach when facing strings. However, by definition QueryDict does only take into account the last param included. Django-filters expect a dict as data. To be congruent with what is passed in the request we need to transform `query_params` to a `dict`. As can be seen in the test written, adhoc programming must be done also in django-filter to allow 'in' lookups but that's another issue. Part of the solution was taken from @leo-the-manic in http://stackoverflow.com/questions/13349573/how-to-change-a-django-querydict-to-python-dict#answer-22100334 --- rest_framework/filters.py | 6 +++++- tests/test_filters.py | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index d188a2d1e..ff18bb55b 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -65,7 +65,11 @@ class DjangoFilterBackend(BaseFilterBackend): filter_class = self.get_filter_class(view, queryset) if filter_class: - return filter_class(request.query_params, queryset=queryset).qs + # Using solution by @leo-the-manic goo.gl/3LoFIj + return filter_class( + {k: v[0] if len(v) == 1 else v for k, v in request.query_params.lists()}, + queryset=queryset + ).qs return queryset diff --git a/tests/test_filters.py b/tests/test_filters.py index 355f02cef..efd76cd1a 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -34,11 +34,17 @@ if django_filters: class SeveralFieldsFilter(django_filters.FilterSet): text = django_filters.CharFilter(lookup_type='icontains') decimal = django_filters.NumberFilter(lookup_type='lt') + decimal_in = django_filters.MethodFilter(action="filter_decimal_in") date = django_filters.DateFilter(lookup_type='gt') class Meta: model = FilterableItem - fields = ['text', 'decimal', 'date'] + fields = ['text', 'decimal', 'decimal_in', 'date'] + + def filter_decimal_in(self, qs, values): + if not hasattr(values, '__iter__'): + values = (values,) + return qs.filter(decimal__in=values) class FilterClassRootView(generics.ListCreateAPIView): queryset = FilterableItem.objects.all() @@ -207,6 +213,16 @@ class IntegrationTestFiltering(CommonFilteringTestCase): expected_data = [f for f in self.data if Decimal(f['decimal']) < search_decimal] self.assertEqual(response.data, expected_data) + # Tests that the decimal_in filter set with 'in' in the filter class works + search_decimal_in = ['1.25', '2.25'] + request = factory.get('/', { + 'decimal_in': search_decimal_in, + }) + response = view(request).render() + self.assertEqual(response.status_code, status.HTTP_200_OK) + expected_data = [f for f in self.data if f['decimal'] in search_decimal_in] + self.assertEqual(response.data, expected_data) + # Tests that the date filter set with 'gt' in the filter class works. search_date = datetime.date(2012, 10, 2) request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02'