diff --git a/django/contrib/postgres/forms/ranges.py b/django/contrib/postgres/forms/ranges.py
index 4f8989a96d..23db4fe514 100644
--- a/django/contrib/postgres/forms/ranges.py
+++ b/django/contrib/postgres/forms/ranges.py
@@ -1,5 +1,6 @@
from django.core import exceptions
from django import forms
+from django.forms.widgets import MultiWidget
from django.utils.translation import ugettext_lazy as _
from psycopg2.extras import NumericRange, DateRange, DateTimeTZRange
@@ -15,8 +16,7 @@ class BaseRangeField(forms.MultiValueField):
}
def __init__(self, **kwargs):
- widget = forms.MultiWidget([self.base_field.widget, self.base_field.widget])
- kwargs.setdefault('widget', widget)
+ kwargs.setdefault('widget', RangeWidget(self.base_field.widget))
kwargs.setdefault('fields', [self.base_field(required=False), self.base_field(required=False)])
kwargs.setdefault('required', False)
kwargs.setdefault('require_all_fields', False)
@@ -67,3 +67,14 @@ class DateTimeRangeField(BaseRangeField):
class DateRangeField(BaseRangeField):
base_field = forms.DateField
range_type = DateRange
+
+
+class RangeWidget(MultiWidget):
+ def __init__(self, base_widget, attrs=None):
+ widgets = (base_widget, base_widget)
+ super(RangeWidget, self).__init__(widgets, attrs)
+
+ def decompress(self, value):
+ if value:
+ return (value.lower, value.upper)
+ return (None, None)
diff --git a/docs/ref/contrib/postgres/forms.txt b/docs/ref/contrib/postgres/forms.txt
index f6226c4b55..d1a15907ae 100644
--- a/docs/ref/contrib/postgres/forms.txt
+++ b/docs/ref/contrib/postgres/forms.txt
@@ -161,7 +161,8 @@ Range Fields
This group of fields all share similar functionality for accepting range data.
They are based on :class:`~django.forms.MultiValueField`. They treat one
omitted value as an unbounded range. They also validate that the lower bound is
-not greater than the upper bound.
+not greater than the upper bound. All of these fields use
+:class:`~django.contrib.postgres.forms.RangeWidget`.
IntegerRangeField
~~~~~~~~~~~~~~~~~
@@ -199,3 +200,26 @@ DateRangeField
Based on :class:`~django.forms.DateField` and translates its input into
:class:`~psycopg2:psycopg2.extras.DateRange`. Default for
:class:`~django.contrib.postgres.fields.DateRangeField`.
+
+Widgets
+-------
+
+RangeWidget
+~~~~~~~~~~~
+
+.. class:: RangeWidget(base_widget, attrs=None)
+
+ Widget used by all of the range fields.
+ Based on :class:`~django.forms.MultiWidget`.
+
+ :class:`~RangeWidget` has one required argument:
+
+ .. attribute:: base_widget
+
+ A :class:`~RangeWidget` comprises a 2-tuple of ``base_widget``.
+
+ .. method:: decompress(value)
+
+ Takes a single "compressed" value of a field, for example a
+ :class:`~django.contrib.postgres.fields.DateRangeField`,
+ and returns a tuple representing and lower and upper bound.
diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py
index 6d35e62cc5..bc15987ba7 100644
--- a/tests/postgres_tests/test_ranges.py
+++ b/tests/postgres_tests/test_ranges.py
@@ -374,3 +374,24 @@ class TestFormField(TestCase):
model_field = pg_fields.DateTimeRangeField()
form_field = model_field.formfield()
self.assertIsInstance(form_field, pg_forms.DateTimeRangeField)
+
+
+class TestWidget(TestCase):
+ def test_range_widget(self):
+ f = pg_forms.ranges.DateTimeRangeField()
+ self.assertHTMLEqual(
+ f.widget.render('datetimerange', ''),
+ ''
+ )
+ self.assertHTMLEqual(
+ f.widget.render('datetimerange', None),
+ ''
+ )
+ dt_range = DateTimeTZRange(
+ datetime.datetime(2006, 1, 10, 7, 30),
+ datetime.datetime(2006, 2, 12, 9, 50)
+ )
+ self.assertHTMLEqual(
+ f.widget.render('datetimerange', dt_range),
+ ''
+ )