Fixed #7664 -- Allowed customizing suffixes of MultiWidget.widgets' names.
This commit is contained in:
parent
8fe2447a01
commit
27746ab28a
|
@ -799,6 +799,13 @@ class MultiWidget(Widget):
|
||||||
template_name = 'django/forms/widgets/multiwidget.html'
|
template_name = 'django/forms/widgets/multiwidget.html'
|
||||||
|
|
||||||
def __init__(self, widgets, attrs=None):
|
def __init__(self, widgets, attrs=None):
|
||||||
|
if isinstance(widgets, dict):
|
||||||
|
self.widgets_names = [
|
||||||
|
('_%s' % name) if name else '' for name in widgets
|
||||||
|
]
|
||||||
|
widgets = widgets.values()
|
||||||
|
else:
|
||||||
|
self.widgets_names = ['_%s' % i for i in range(len(widgets))]
|
||||||
self.widgets = [w() if isinstance(w, type) else w for w in widgets]
|
self.widgets = [w() if isinstance(w, type) else w for w in widgets]
|
||||||
super().__init__(attrs)
|
super().__init__(attrs)
|
||||||
|
|
||||||
|
@ -820,10 +827,10 @@ class MultiWidget(Widget):
|
||||||
input_type = final_attrs.pop('type', None)
|
input_type = final_attrs.pop('type', None)
|
||||||
id_ = final_attrs.get('id')
|
id_ = final_attrs.get('id')
|
||||||
subwidgets = []
|
subwidgets = []
|
||||||
for i, widget in enumerate(self.widgets):
|
for i, (widget_name, widget) in enumerate(zip(self.widgets_names, self.widgets)):
|
||||||
if input_type is not None:
|
if input_type is not None:
|
||||||
widget.input_type = input_type
|
widget.input_type = input_type
|
||||||
widget_name = '%s_%s' % (name, i)
|
widget_name = name + widget_name
|
||||||
try:
|
try:
|
||||||
widget_value = value[i]
|
widget_value = value[i]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
|
@ -843,12 +850,15 @@ class MultiWidget(Widget):
|
||||||
return id_
|
return id_
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
|
return [
|
||||||
|
widget.value_from_datadict(data, files, name + widget_name)
|
||||||
|
for widget_name, widget in zip(self.widgets_names, self.widgets)
|
||||||
|
]
|
||||||
|
|
||||||
def value_omitted_from_data(self, data, files, name):
|
def value_omitted_from_data(self, data, files, name):
|
||||||
return all(
|
return all(
|
||||||
widget.value_omitted_from_data(data, files, name + '_%s' % i)
|
widget.value_omitted_from_data(data, files, name + widget_name)
|
||||||
for i, widget in enumerate(self.widgets)
|
for widget_name, widget in zip(self.widgets_names, self.widgets)
|
||||||
)
|
)
|
||||||
|
|
||||||
def decompress(self, value):
|
def decompress(self, value):
|
||||||
|
|
|
@ -354,7 +354,27 @@ foundation for custom widgets.
|
||||||
|
|
||||||
.. attribute:: MultiWidget.widgets
|
.. attribute:: MultiWidget.widgets
|
||||||
|
|
||||||
An iterable containing the widgets needed.
|
An iterable containing the widgets needed. For example::
|
||||||
|
|
||||||
|
>>> from django.forms import MultiWidget, TextInput
|
||||||
|
>>> widget = MultiWidget(widgets=[TextInput, TextInput])
|
||||||
|
>>> widget.render('name', ['john', 'paul'])
|
||||||
|
'<input type="text" name="name_0" value="john"><input type="text" name="name_1" value="paul">'
|
||||||
|
|
||||||
|
You may provide a dictionary in order to specify custom suffixes for
|
||||||
|
the ``name`` attribute on each subwidget. In this case, for each
|
||||||
|
``(key, widget)`` pair, the key will be appended to the ``name`` of the
|
||||||
|
widget in order to generate the attribute value. You may provide the
|
||||||
|
empty string (`''`) for a single key, in order to suppress the suffix
|
||||||
|
for one widget. For example::
|
||||||
|
|
||||||
|
>>> widget = MultiWidget(widgets={'': TextInput, 'last': TextInput})
|
||||||
|
>>> widget.render('name', ['john', 'lennon'])
|
||||||
|
'<input type="text" name="name" value="john"><input type="text" name="name_last" value="paul">'
|
||||||
|
|
||||||
|
.. versionchanged::3.1
|
||||||
|
|
||||||
|
Support for using a dictionary was added.
|
||||||
|
|
||||||
And one required method:
|
And one required method:
|
||||||
|
|
||||||
|
|
|
@ -270,6 +270,9 @@ Forms
|
||||||
now uses ``DATE_INPUT_FORMATS`` in addition to ``DATETIME_INPUT_FORMATS``
|
now uses ``DATE_INPUT_FORMATS`` in addition to ``DATETIME_INPUT_FORMATS``
|
||||||
when converting a field input to a ``datetime`` value.
|
when converting a field input to a ``datetime`` value.
|
||||||
|
|
||||||
|
* :attr:`.MultiWidget.widgets` now accepts a dictionary which allows
|
||||||
|
customizing subwidget ``name`` attributes.
|
||||||
|
|
||||||
Generic Views
|
Generic Views
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -79,6 +79,19 @@ class DeepCopyWidget(MultiWidget):
|
||||||
|
|
||||||
|
|
||||||
class MultiWidgetTest(WidgetTest):
|
class MultiWidgetTest(WidgetTest):
|
||||||
|
def test_subwidgets_name(self):
|
||||||
|
widget = MultiWidget(
|
||||||
|
widgets={
|
||||||
|
'': TextInput(),
|
||||||
|
'big': TextInput(attrs={'class': 'big'}),
|
||||||
|
'small': TextInput(attrs={'class': 'small'}),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.check_html(widget, 'name', ['John', 'George', 'Paul'], html=(
|
||||||
|
'<input type="text" name="name" value="John">'
|
||||||
|
'<input type="text" name="name_big" value="George" class="big">'
|
||||||
|
'<input type="text" name="name_small" value="Paul" class="small">'
|
||||||
|
))
|
||||||
|
|
||||||
def test_text_inputs(self):
|
def test_text_inputs(self):
|
||||||
widget = MyMultiWidget(
|
widget = MyMultiWidget(
|
||||||
|
@ -133,6 +146,36 @@ class MultiWidgetTest(WidgetTest):
|
||||||
self.assertIs(widget.value_omitted_from_data({'field_1': 'y'}, {}, 'field'), False)
|
self.assertIs(widget.value_omitted_from_data({'field_1': 'y'}, {}, 'field'), False)
|
||||||
self.assertIs(widget.value_omitted_from_data({'field_0': 'x', 'field_1': 'y'}, {}, 'field'), False)
|
self.assertIs(widget.value_omitted_from_data({'field_0': 'x', 'field_1': 'y'}, {}, 'field'), False)
|
||||||
|
|
||||||
|
def test_value_from_datadict_subwidgets_name(self):
|
||||||
|
widget = MultiWidget(widgets={'x': TextInput(), '': TextInput()})
|
||||||
|
tests = [
|
||||||
|
({}, [None, None]),
|
||||||
|
({'field': 'x'}, [None, 'x']),
|
||||||
|
({'field_x': 'y'}, ['y', None]),
|
||||||
|
({'field': 'x', 'field_x': 'y'}, ['y', 'x']),
|
||||||
|
]
|
||||||
|
for data, expected in tests:
|
||||||
|
with self.subTest(data):
|
||||||
|
self.assertEqual(
|
||||||
|
widget.value_from_datadict(data, {}, 'field'),
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_value_omitted_from_data_subwidgets_name(self):
|
||||||
|
widget = MultiWidget(widgets={'x': TextInput(), '': TextInput()})
|
||||||
|
tests = [
|
||||||
|
({}, True),
|
||||||
|
({'field': 'x'}, False),
|
||||||
|
({'field_x': 'y'}, False),
|
||||||
|
({'field': 'x', 'field_x': 'y'}, False),
|
||||||
|
]
|
||||||
|
for data, expected in tests:
|
||||||
|
with self.subTest(data):
|
||||||
|
self.assertIs(
|
||||||
|
widget.value_omitted_from_data(data, {}, 'field'),
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
|
||||||
def test_needs_multipart_true(self):
|
def test_needs_multipart_true(self):
|
||||||
"""
|
"""
|
||||||
needs_multipart_form should be True if any widgets need it.
|
needs_multipart_form should be True if any widgets need it.
|
||||||
|
|
Loading…
Reference in New Issue