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
This commit is contained in:
Javier Chamizo 2015-02-07 03:22:22 +01:00
parent 235b98e427
commit 7854bd5daf
2 changed files with 22 additions and 2 deletions

View File

@ -65,7 +65,11 @@ class DjangoFilterBackend(BaseFilterBackend):
filter_class = self.get_filter_class(view, queryset) filter_class = self.get_filter_class(view, queryset)
if filter_class: 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 return queryset

View File

@ -34,11 +34,17 @@ if django_filters:
class SeveralFieldsFilter(django_filters.FilterSet): class SeveralFieldsFilter(django_filters.FilterSet):
text = django_filters.CharFilter(lookup_type='icontains') text = django_filters.CharFilter(lookup_type='icontains')
decimal = django_filters.NumberFilter(lookup_type='lt') decimal = django_filters.NumberFilter(lookup_type='lt')
decimal_in = django_filters.MethodFilter(action="filter_decimal_in")
date = django_filters.DateFilter(lookup_type='gt') date = django_filters.DateFilter(lookup_type='gt')
class Meta: class Meta:
model = FilterableItem 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): class FilterClassRootView(generics.ListCreateAPIView):
queryset = FilterableItem.objects.all() 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] expected_data = [f for f in self.data if Decimal(f['decimal']) < search_decimal]
self.assertEqual(response.data, expected_data) 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. # Tests that the date filter set with 'gt' in the filter class works.
search_date = datetime.date(2012, 10, 2) search_date = datetime.date(2012, 10, 2)
request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02' request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02'