diff --git a/django_filters/fields.py b/django_filters/fields.py index 99a107651..58433e7b1 100644 --- a/django_filters/fields.py +++ b/django_filters/fields.py @@ -11,7 +11,13 @@ from .conf import settings from .utils import handle_timezone -from .widgets import BaseCSVWidget, CSVWidget, LookupTypeWidget, RangeWidget +from .widgets import ( + BaseCSVWidget, + CSVWidget, + DateRangeWidget, + LookupTypeWidget, + RangeWidget +) class RangeField(forms.MultiValueField): @@ -31,6 +37,7 @@ def compress(self, data_list): class DateRangeField(RangeField): + widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( @@ -56,6 +63,7 @@ def compress(self, data_list): class DateTimeRangeField(RangeField): + widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( @@ -65,6 +73,7 @@ def __init__(self, *args, **kwargs): class TimeRangeField(RangeField): + widget = DateRangeWidget def __init__(self, *args, **kwargs): fields = ( diff --git a/django_filters/widgets.py b/django_filters/widgets.py index 572ee8507..043d597d5 100644 --- a/django_filters/widgets.py +++ b/django_filters/widgets.py @@ -140,8 +140,9 @@ def decompress(self, value): return value -class RangeWidget(forms.MultiWidget): +class RangeWidget(SuffixedMultiWidget): template_name = 'django_filters/widgets/multiwidget.html' + suffixes = ['min', 'max'] def __init__(self, attrs=None): widgets = (forms.TextInput, forms.TextInput) @@ -149,6 +150,10 @@ def __init__(self, attrs=None): def format_output(self, rendered_widgets): # Method was removed in Django 1.11. + rendered_widgets = [ + self.replace_name(output, i) + for i, output in enumerate(rendered_widgets) + ] return '-'.join(rendered_widgets) def decompress(self, value): @@ -157,7 +162,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] diff --git a/docs/ref/filters.txt b/docs/ref/filters.txt index 062a2424e..dd2432e16 100644 --- a/docs/ref/filters.txt +++ b/docs/ref/filters.txt @@ -470,13 +470,13 @@ Filters where a value is between two numerical values, or greater than a minimum qs = Book.objects.all().order_by('title') # Range: Books between 5€ and 15€ - f = F({'price_0': '5', 'price_1': '15'}, queryset=qs) + f = F({'price_min': '5', 'price_max': '15'}, queryset=qs) # Min-Only: Books costing more the 11€ - f = F({'price_0': '11'}, queryset=qs) + f = F({'price_min': '11'}, queryset=qs) # Max-Only: Books costing less than 19€ - f = F({'price_1': '19'}, queryset=qs) + f = F({'price_max': '19'}, queryset=qs) ``DateRangeFilter`` @@ -504,13 +504,13 @@ Example of using the ``DateField`` field:: fields = ['date'] # Range: Comments added between 2016-01-01 and 2016-02-01 - f = F({'date_0': '2016-01-01', 'date_1': '2016-02-01'}) + f = F({'date_after': '2016-01-01', 'date_before': '2016-02-01'}) # Min-Only: Comments added after 2016-01-01 - f = F({'date_0': '2016-01-01'}) + f = F({'date_after': '2016-01-01'}) # Max-Only: Comments added before 2016-02-01 - f = F({'date_1': '2016-02-01'}) + f = F({'date_before': '2016-02-01'}) .. note:: When filtering ranges that occurs on DST transition dates ``DateFromToRangeFilter`` will use the first valid hour of the day for start datetime and the last valid hour of the day for end datetime. @@ -537,15 +537,15 @@ Example of using the ``DateTimeField`` field:: Article.objects.create(published='2016-02-10 12:00') # Range: Articles published between 2016-01-01 and 2016-02-01 - f = F({'published_0': '2016-01-01', 'published_1': '2016-02-01'}) + f = F({'published_after': '2016-01-01', 'published_before': '2016-02-01'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 - f = F({'published_0': '2016-01-01'}) + f = F({'published_after': '2016-01-01'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-02-01 - f = F({'published_1': '2016-02-01'}) + f = F({'published_before': '2016-02-01'}) assert len(f.qs) == 2 ``DateTimeFromToRangeFilter`` @@ -570,15 +570,15 @@ Example:: Article.objects.create(published='2016-01-02 8:00') # Range: Articles published 2016-01-01 between 8:00 and 10:00 - f = F({'published_0': '2016-01-01 8:00', 'published_1': '2016-01-01 10:00'}) + f = F({'published_after': '2016-01-01 8:00', 'published_before': '2016-01-01 10:00'}) assert len(f.qs) == 2 # Min-Only: Articles published after 2016-01-01 8:00 - f = F({'published_0': '2016-01-01 8:00'}) + f = F({'published_after': '2016-01-01 8:00'}) assert len(f.qs) == 3 # Max-Only: Articles published before 2016-01-01 10:00 - f = F({'published_1': '2016-01-01 10:00'}) + f = F({'published_before': '2016-01-01 10:00'}) assert len(f.qs) == 2 ``TimeRangeFilter`` @@ -600,13 +600,13 @@ Example:: fields = ['time'] # Range: Comments added between 8:00 and 10:00 - f = F({'time_0': '8:00', 'time_1': '10:00'}) + f = F({'time_after': '8:00', 'time_before': '10:00'}) # Min-Only: Comments added after 8:00 - f = F({'time_0': '8:00'}) + f = F({'time_after': '8:00'}) # Max-Only: Comments added before 10:00 - f = F({'time_1': '10:00'}) + f = F({'time_before': '10:00'}) ``AllValuesFilter`` ~~~~~~~~~~~~~~~~~~~ diff --git a/tests/test_fields.py b/tests/test_fields.py index 66a6d7fa5..8ebfe6d22 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -124,14 +124,14 @@ def test_render_used_html5(self): inner = forms.DecimalField() f = LookupTypeField(inner, [('gt', 'gt'), ('lt', 'lt')]) self.assertHTMLEqual(f.widget.render('price', ''), """ - - + """) self.assertHTMLEqual(f.widget.render('price', ['abc', 'lt']), """ - - + """) diff --git a/tests/test_filtering.py b/tests/test_filtering.py index 36167130a..079c3ae36 100644 --- a/tests/test_filtering.py +++ b/tests/test_filtering.py @@ -742,11 +742,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) @@ -758,7 +758,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) @@ -789,29 +789,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) @@ -914,8 +914,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): @@ -937,8 +937,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') @@ -958,8 +958,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') @@ -979,8 +979,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') @@ -1001,8 +1001,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') @@ -1023,8 +1023,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) @@ -1049,8 +1049,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) @@ -1073,8 +1073,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) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index 78ff92d30..1c83a403a 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -25,22 +25,22 @@ def test_widget_render(self): widgets = [TextInput(), Select(choices=(('a', 'a'), ('b', 'b')))] w = LookupTypeWidget(widgets) self.assertHTMLEqual(w.render('price', ''), """ - - + """) self.assertHTMLEqual(w.render('price', None), """ - - + """) self.assertHTMLEqual(w.render('price', ['2', 'a']), """ - - + """) @@ -194,27 +194,26 @@ def test_widget(self): w = RangeWidget() self.assertEqual(len(w.widgets), 2) self.assertHTMLEqual(w.render('price', ''), """ - + - - """) + """) self.assertHTMLEqual(w.render('price', slice(5.99, 9.99)), """ - + - - """) + """) def test_widget_attributes(self): w = RangeWidget(attrs={'type': 'date'}) self.assertEqual(len(w.widgets), 2) self.assertHTMLEqual(w.render('date', ''), """ - + - - """) + """) class BooleanWidgetTests(TestCase): - """ - """ + def test_widget_render(self): w = BooleanWidget() self.assertHTMLEqual(w.render('price', ''), """