Skip to content

Commit e6bafd5

Browse files
author
Carlton Gibson
authored
Merge pull request carltongibson#548 from pySilver/feature/multiple-values-input-format
Add `QueryArrayWidget`
2 parents 470b89c + 703172c commit e6bafd5

File tree

2 files changed

+108
-1
lines changed

2 files changed

+108
-1
lines changed

django_filters/widgets.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django import forms
1212
from django.db.models.fields import BLANK_CHOICE_DASH
1313
from django.forms.widgets import flatatt
14+
from django.utils.datastructures import MultiValueDict
1415
from django.utils.encoding import force_text
1516
from django.utils.safestring import mark_safe
1617
from django.utils.six import string_types
@@ -171,3 +172,38 @@ def render(self, name, value, attrs=None):
171172

172173
class CSVWidget(BaseCSVWidget, forms.TextInput):
173174
pass
175+
176+
177+
class QueryArrayWidget(BaseCSVWidget, forms.TextInput):
178+
"""
179+
Enables request query array notation that might be consumed by MultipleChoiceFilter
180+
181+
1. Values can be provided as csv string: ?foo=bar,baz
182+
2. Values can be provided as query array: ?foo[]=bar&foo[]=baz
183+
3. Values can be provided as query array: ?foo=bar&foo=baz
184+
185+
Note: Duplicate and empty values are skipped from results
186+
"""
187+
188+
def value_from_datadict(self, data, files, name):
189+
if not isinstance(data, MultiValueDict):
190+
data = MultiValueDict(data)
191+
192+
values_list = data.getlist(name, data.getlist('%s[]' % name)) or []
193+
194+
if isinstance(values_list, string_types):
195+
values_list = [values_list]
196+
197+
# apparently its an array, so no need to process it's values as csv
198+
# ?foo=1&foo=2 -> data.getlist(foo) -> foo = [1, 2]
199+
# ?foo[]=1&foo[]=2 -> data.getlist(foo[]) -> foo = [1, 2]
200+
if len(values_list) > 1:
201+
ret = [x for x in values_list if x]
202+
elif len(values_list) == 1:
203+
# treat first element as csv string
204+
# ?foo=1,2 -> data.getlist(foo) -> foo = ['1,2']
205+
ret = [x.strip() for x in values_list[0].rstrip(',').split(',') if x]
206+
else:
207+
ret = []
208+
209+
return list(set(ret))

tests/test_widgets.py

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from django.test import TestCase
55
from django.forms import TextInput, Select
66

7-
from django_filters.widgets import BooleanWidget
7+
from django_filters.widgets import BooleanWidget, QueryArrayWidget
88
from django_filters.widgets import BaseCSVWidget
99
from django_filters.widgets import CSVWidget
1010
from django_filters.widgets import RangeWidget
@@ -279,3 +279,74 @@ def test_widget(self):
279279

280280
self.assertHTMLEqual(w.render('price', [1, 2]), """
281281
<input type="text" name="price" value="1,2" />""")
282+
283+
284+
class QueryArrayWidgetTests(TestCase):
285+
286+
def test_widget_value_from_datadict(self):
287+
w = QueryArrayWidget()
288+
289+
# Values can be provided as csv string: ?foo=bar,baz
290+
data = {'price': None}
291+
result = w.value_from_datadict(data, {}, 'price')
292+
self.assertEqual(result, [])
293+
294+
data = {'price': '1'}
295+
result = w.value_from_datadict(data, {}, 'price')
296+
self.assertEqual(result, ['1'])
297+
298+
data = {'price': '1,2'}
299+
result = w.value_from_datadict(data, {}, 'price')
300+
self.assertEqual(sorted(result), ['1', '2'])
301+
302+
data = {'price': '1,,2'}
303+
result = w.value_from_datadict(data, {}, 'price')
304+
self.assertEqual(sorted(result), ['1', '2'])
305+
306+
data = {'price': '1,'}
307+
result = w.value_from_datadict(data, {}, 'price')
308+
self.assertEqual(result, ['1'])
309+
310+
data = {'price': ','}
311+
result = w.value_from_datadict(data, {}, 'price')
312+
self.assertEqual(result, [])
313+
314+
data = {'price': ''}
315+
result = w.value_from_datadict(data, {}, 'price')
316+
self.assertEqual(result, [])
317+
318+
result = w.value_from_datadict({}, {}, 'price')
319+
self.assertEqual(result, [])
320+
321+
# Values can be provided as query array: ?foo[]=bar&foo[]=baz
322+
323+
data = {'price[]': None}
324+
result = w.value_from_datadict(data, {}, 'price')
325+
self.assertEqual(result, [])
326+
327+
data = {'price[]': ['1']}
328+
result = w.value_from_datadict(data, {}, 'price')
329+
self.assertEqual(result, ['1'])
330+
331+
data = {'price[]': ['1', '2']}
332+
result = w.value_from_datadict(data, {}, 'price')
333+
self.assertEqual(sorted(result), ['1', '2'])
334+
335+
data = {'price[]': ['1', '', '2']}
336+
result = w.value_from_datadict(data, {}, 'price')
337+
self.assertEqual(sorted(result), ['1', '2'])
338+
339+
data = {'price[]': ['1', '']}
340+
result = w.value_from_datadict(data, {}, 'price')
341+
self.assertEqual(result, ['1'])
342+
343+
data = {'price[]': ['', '']}
344+
result = w.value_from_datadict(data, {}, 'price')
345+
self.assertEqual(result, [])
346+
347+
data = {'price[]': []}
348+
result = w.value_from_datadict(data, {}, 'price')
349+
self.assertEqual(result, [])
350+
351+
result = w.value_from_datadict({}, {}, 'price')
352+
self.assertEqual(result, [])

0 commit comments

Comments
 (0)