Skip to content

Commit

Permalink
Add SuffixedMultiWidget
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan P Kilby committed Apr 8, 2017
1 parent d491703 commit 9445bda
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 47 deletions.
5 changes: 4 additions & 1 deletion django_filters/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from django.utils.translation import ugettext_lazy as _

from .utils import handle_timezone
from .widgets import RangeWidget, LookupTypeWidget, CSVWidget, BaseCSVWidget
from .widgets import RangeWidget, DateRangeWidget, LookupTypeWidget, CSVWidget, BaseCSVWidget


class RangeField(forms.MultiValueField):
Expand All @@ -31,6 +31,7 @@ def compress(self, data_list):


class DateRangeField(RangeField):
widget = DateRangeWidget

def __init__(self, *args, **kwargs):
fields = (
Expand All @@ -56,6 +57,7 @@ def compress(self, data_list):


class DateTimeRangeField(RangeField):
widget = DateRangeWidget

def __init__(self, *args, **kwargs):
fields = (
Expand All @@ -65,6 +67,7 @@ def __init__(self, *args, **kwargs):


class TimeRangeField(RangeField):
widget = DateRangeWidget

def __init__(self, *args, **kwargs):
fields = (
Expand Down
49 changes: 47 additions & 2 deletions django_filters/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,47 @@ def option_string(self):
return '<li><a%(attrs)s href="?%(query_string)s">%(label)s</a></li>'


class RangeWidget(forms.MultiWidget):
class SuffixedMultiWidget(forms.MultiWidget):
"""
A MultiWidget that allows users to provide custom suffixes instead of indexes.
- Suffixes must be unique.
- There must be the same number of suffixes as fields.
"""
suffixes = []

def __init__(self, *args, **kwargs):
super(SuffixedMultiWidget, self).__init__(*args, **kwargs)

assert len(self.widgets) == len(self.suffixes)
assert len(self.suffixes) == len(set(self.suffixes))

def suffixed(self, name, suffix):
return '_'.join([name, suffix]) if suffix else name

def get_context(self, name, value, attrs):
context = super(SuffixedMultiWidget, self).get_context(name, value, attrs)
for subcontext, suffix in zip(context['widget']['subwidgets'], self.suffixes):
subcontext['name'] = self.suffixed(name, suffix)

return context

def value_from_datadict(self, data, files, name):
return [
widget.value_from_datadict(data, files, self.suffixed(name, suffix))
for widget, suffix in zip(self.widgets, self.suffixes)
]

def value_omitted_from_data(self, data, files, name):
return all(
widget.value_omitted_from_data(data, files, self.suffixed(name, suffix))
for widget, suffix in zip(self.widgets, self.suffixes)
)


class RangeWidget(SuffixedMultiWidget):
template_name = 'django_filters/widgets/multiwidget.html'
suffixes = ['min', 'max']

def __init__(self, attrs=None):
widgets = (forms.TextInput, forms.TextInput)
Expand All @@ -103,7 +142,13 @@ def decompress(self, value):
return [None, None]


class LookupTypeWidget(forms.MultiWidget):
class DateRangeWidget(RangeWidget):
suffixes = ['after', 'before']


class LookupTypeWidget(SuffixedMultiWidget):
suffixes = [None, 'lookup']

def decompress(self, value):
if value is None:
return [None, None]
Expand Down
8 changes: 4 additions & 4 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,14 +118,14 @@ def test_render_used_html5(self):
inner = forms.DecimalField()
f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')])
self.assertHTMLEqual(f.widget.render('price', ''), """
<input type="number" step="any" name="price_0" />
<select name="price_1">
<input type="number" step="any" name="price" />
<select name="price_lookup">
<option value="gt">gt</option>
<option value="lt">lt</option>
</select>""")
self.assertHTMLEqual(f.widget.render('price', ['abc', 'lt']), """
<input type="number" step="any" name="price_0" value="abc" />
<select name="price_1">
<input type="number" step="any" name="price" value="abc" />
<select name="price_lookup">
<option value="gt">gt</option>
<option selected="selected" value="lt">lt</option>
</select>""")
Expand Down
52 changes: 26 additions & 26 deletions tests/test_filtering.py
Original file line number Diff line number Diff line change
Expand Up @@ -675,11 +675,11 @@ class Meta:
fields = ['price']

qs = Book.objects.all()
f = F({'price_0': '15', 'price_1': 'lt'}, queryset=qs)
f = F({'price': '15', 'price_lookup': 'lt'}, queryset=qs)
self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title)
f = F({'price_0': '15', 'price_1': 'lt'})
f = F({'price': '15', 'price_lookup': 'lt'})
self.assertQuerysetEqual(f.qs, ['Ender\'s Game'], lambda o: o.title)
f = F({'price_0': '', 'price_1': 'lt'})
f = F({'price': '', 'price_lookup': 'lt'})
self.assertQuerysetEqual(f.qs,
['Ender\'s Game', 'Rainbow Six', 'Snowcrash'],
lambda o: o.title, ordered=False)
Expand All @@ -691,7 +691,7 @@ class Meta:
model = Book
fields = ['price']

f = F({'price_0': '15'})
f = F({'price': '15'})
self.assertQuerysetEqual(f.qs, ['Rainbow Six'], lambda o: o.title)


Expand Down Expand Up @@ -722,29 +722,29 @@ class Meta:
self.assertQuerysetEqual(f.qs,
['Ender\'s Game', 'Free Book', 'Rainbow Six', 'Refund', 'Snowcrash'],
lambda o: o.title)
f = F({'price_0': '5', 'price_1': '15'}, queryset=qs)
f = F({'price_min': '5', 'price_max': '15'}, queryset=qs)
self.assertQuerysetEqual(f.qs,
['Ender\'s Game', 'Rainbow Six'],
lambda o: o.title)

f = F({'price_0': '11'}, queryset=qs)
f = F({'price_min': '11'}, queryset=qs)
self.assertQuerysetEqual(f.qs,
['Rainbow Six', 'Snowcrash'],
lambda o: o.title)
f = F({'price_1': '19'}, queryset=qs)
f = F({'price_max': '19'}, queryset=qs)
self.assertQuerysetEqual(f.qs,
['Ender\'s Game', 'Free Book', 'Rainbow Six', 'Refund'],
lambda o: o.title)

f = F({'price_0': '0', 'price_1': '12'}, queryset=qs)
f = F({'price_min': '0', 'price_max': '12'}, queryset=qs)
self.assertQuerysetEqual(f.qs,
['Ender\'s Game', 'Free Book'],
lambda o: o.title)
f = F({'price_0': '-11', 'price_1': '0'}, queryset=qs)
f = F({'price_min': '-11', 'price_max': '0'}, queryset=qs)
self.assertQuerysetEqual(f.qs,
['Free Book', 'Refund'],
lambda o: o.title)
f = F({'price_0': '0', 'price_1': '0'}, queryset=qs)
f = F({'price_min': '0', 'price_max': '0'}, queryset=qs)
self.assertQuerysetEqual(f.qs,
['Free Book'],
lambda o: o.title)
Expand Down Expand Up @@ -847,8 +847,8 @@ class Meta:
fields = ['date']

results = F(data={
'published_0': '2016-01-02',
'published_1': '2016-01-03'})
'published_after': '2016-01-02',
'published_before': '2016-01-03'})
self.assertEqual(len(results.qs), 3)

def test_filtering_ignores_time(self):
Expand All @@ -870,8 +870,8 @@ class Meta:
fields = ['published']

results = F(data={
'published_0': '2016-01-02',
'published_1': '2016-01-03'})
'published_after': '2016-01-02',
'published_before': '2016-01-03'})
self.assertEqual(len(results.qs), 3)

@unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware')
Expand All @@ -891,8 +891,8 @@ class Meta:
fields = ['published']

results = F(data={
'published_0': '2017-10-15',
'published_1': '2017-10-15'})
'published_after': '2017-10-15',
'published_before': '2017-10-15'})
self.assertEqual(len(results.qs), 2)

@unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware')
Expand All @@ -912,8 +912,8 @@ class Meta:
fields = ['published']

results = F(data={
'published_0': '2017-02-18',
'published_1': '2017-02-18'})
'published_after': '2017-02-18',
'published_before': '2017-02-18'})
self.assertEqual(len(results.qs), 2)

@unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware')
Expand All @@ -934,8 +934,8 @@ class Meta:
fields = ['published']

results = F(data={
'published_0': '2017-3-26',
'published_1': '2017-3-26'})
'published_after': '2017-3-26',
'published_before': '2017-3-26'})
self.assertEqual(len(results.qs), 3)

@unittest.skipIf(django.VERSION < (1, 9), 'version doesnt supports is_dst parameter for make_aware')
Expand All @@ -956,8 +956,8 @@ class Meta:
fields = ['published']

results = F(data={
'published_0': '2017-10-29',
'published_1': '2017-10-29'})
'published_after': '2017-10-29',
'published_before': '2017-10-29'})
self.assertEqual(len(results.qs), 3)


Expand All @@ -982,8 +982,8 @@ class Meta:
fields = ['published']

results = F(data={
'published_0': '2016-01-02 10:00',
'published_1': '2016-01-03 19:00'})
'published_after': '2016-01-02 10:00',
'published_before': '2016-01-03 19:00'})
self.assertEqual(len(results.qs), 2)


Expand All @@ -1006,8 +1006,8 @@ class Meta:
fields = ['time']

results = F(data={
'time_0': '8:00',
'time_1': '10:00'})
'time_after': '8:00',
'time_before': '10:00'})
self.assertEqual(len(results.qs), 2)


Expand Down
27 changes: 13 additions & 14 deletions tests/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@ def test_widget_render(self):
widgets = [TextInput(), Select(choices=(('a', 'a'), ('b', 'b')))]
w = LookupTypeWidget(widgets)
self.assertHTMLEqual(w.render('price', ''), """
<input name="price_0" type="text" />
<select name="price_1">
<input name="price" type="text" />
<select name="price_lookup">
<option value="a">a</option>
<option value="b">b</option>
</select>""")

self.assertHTMLEqual(w.render('price', None), """
<input name="price_0" type="text" />
<select name="price_1">
<input name="price" type="text" />
<select name="price_lookup">
<option value="a">a</option>
<option value="b">b</option>
</select>""")

self.assertHTMLEqual(w.render('price', ['2', 'a']), """
<input name="price_0" type="text" value="2" />
<select name="price_1">
<input name="price" type="text" value="2" />
<select name="price_lookup">
<option selected="selected" value="a">a</option>
<option value="b">b</option>
</select>""")
Expand Down Expand Up @@ -127,27 +127,26 @@ def test_widget(self):
w = RangeWidget()
self.assertEqual(len(w.widgets), 2)
self.assertHTMLEqual(w.render('price', ''), """
<input type="text" name="price_0" />
<input type="text" name="price_min" />
-
<input type="text" name="price_1" />""")
<input type="text" name="price_max" />""")

self.assertHTMLEqual(w.render('price', slice(5.99, 9.99)), """
<input type="text" name="price_0" value="5.99" />
<input type="text" name="price_min" value="5.99" />
-
<input type="text" name="price_1" value="9.99" />""")
<input type="text" name="price_max" value="9.99" />""")

def test_widget_attributes(self):
w = RangeWidget(attrs={'type': 'date'})
self.assertEqual(len(w.widgets), 2)
self.assertHTMLEqual(w.render('date', ''), """
<input type="date" name="date_0" />
<input type="date" name="date_min" />
-
<input type="date" name="date_1" />""")
<input type="date" name="date_max" />""")


class BooleanWidgetTests(TestCase):
"""
"""

def test_widget_render(self):
w = BooleanWidget()
self.assertHTMLEqual(w.render('price', ''), """
Expand Down

0 comments on commit 9445bda

Please sign in to comment.