diff --git a/filternaut/filters.py b/filternaut/filters.py index ad6a43a..398e876 100644 --- a/filternaut/filters.py +++ b/filternaut/filters.py @@ -2,6 +2,10 @@ from __future__ import absolute_import, unicode_literals +from collections import Iterable + +import six + from django.forms import (BooleanField, CharField, ChoiceField, ComboField, DateField, DateTimeField, DecimalField, EmailField, FilePathField, FloatField, ImageField, IntegerField, @@ -25,6 +29,13 @@ 'TimeFilter', 'TypedChoiceFilter', 'URLFilter'] +def is_listlike(val): + """ + True if `val` is an iterable (list, tuple, ...) but not a string + """ + return isinstance(val, Iterable) and not isinstance(val, six.string_types) + + class FieldFilter(Filter): """ FieldFilters use a django.forms.Field to clean their input value when @@ -42,7 +53,10 @@ def __init__(self, dest, field, **kwargs): super(FieldFilter, self).__init__(dest, **kwargs) def clean(self, value): - return self.field.clean(value) + if is_listlike(value): + return type(value)(self.field.clean(v) for v in value) + else: + return self.field.clean(value) # -- mixtures of fieldfilter and django fields requiring additional arguments diff --git a/tests/test_filters.py b/tests/test_filters.py index dafa289..0b038ee 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -9,7 +9,8 @@ from django import VERSION as DJANGO_VERSION from django.utils.datastructures import MultiValueDict from filternaut import Filter, Optional -from filternaut.filters import ChoiceFilter, FilePathFilter, RegexFilter +from filternaut.filters import (CharFilter, ChoiceFilter, FilePathFilter, + RegexFilter) from tests.util import NopeFilter, flatten_qobj @@ -311,6 +312,20 @@ def test_instantiate_all_filterfields_without_special_args(self): else: assert not hasattr(filternaut.filters, f) + def test_listlike_values_cleaned_individually(self): + f = CharFilter('fieldname', lookups='exact,in') + data = MultiValueDict({ + 'fieldname': ['single value'], # MVDict treats 1-list as single + 'fieldname__in': ['multiple', 'values'], + }) + expected = { + 'fieldname': 'single value', + 'fieldname__in': ['multiple', 'values'] + } + f.parse(data) + actual = dict(flatten_qobj(f.Q)) + assert expected == actual + class DefaultValueTests(TestCase):