2015-08-31 02:13:42 +00:00
|
|
|
import copy
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
from django.forms import (
|
|
|
|
CharField,
|
|
|
|
FileInput,
|
2022-01-13 23:08:38 +00:00
|
|
|
Form,
|
2015-08-31 02:13:42 +00:00
|
|
|
MultipleChoiceField,
|
|
|
|
MultiValueField,
|
|
|
|
MultiWidget,
|
|
|
|
RadioSelect,
|
|
|
|
SelectMultiple,
|
|
|
|
SplitDateTimeField,
|
|
|
|
SplitDateTimeWidget,
|
|
|
|
TextInput,
|
|
|
|
)
|
|
|
|
|
|
|
|
from .base import WidgetTest
|
|
|
|
|
|
|
|
|
|
|
|
class MyMultiWidget(MultiWidget):
|
|
|
|
def decompress(self, value):
|
|
|
|
if value:
|
|
|
|
return value.split("__")
|
|
|
|
return ["", ""]
|
|
|
|
|
|
|
|
|
|
|
|
class ComplexMultiWidget(MultiWidget):
|
|
|
|
def __init__(self, attrs=None):
|
|
|
|
widgets = (
|
|
|
|
TextInput(),
|
|
|
|
SelectMultiple(choices=WidgetTest.beatles),
|
|
|
|
SplitDateTimeWidget(),
|
|
|
|
)
|
2017-01-21 13:13:44 +00:00
|
|
|
super().__init__(widgets, attrs)
|
2015-08-31 02:13:42 +00:00
|
|
|
|
|
|
|
def decompress(self, value):
|
|
|
|
if value:
|
|
|
|
data = value.split(",")
|
|
|
|
return [
|
|
|
|
data[0],
|
|
|
|
list(data[1]),
|
|
|
|
datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S"),
|
|
|
|
]
|
|
|
|
return [None, None, None]
|
|
|
|
|
|
|
|
|
|
|
|
class ComplexField(MultiValueField):
|
|
|
|
def __init__(self, required=True, widget=None, label=None, initial=None):
|
|
|
|
fields = (
|
|
|
|
CharField(),
|
|
|
|
MultipleChoiceField(choices=WidgetTest.beatles),
|
|
|
|
SplitDateTimeField(),
|
|
|
|
)
|
2022-01-13 23:08:38 +00:00
|
|
|
super().__init__(
|
|
|
|
fields, required=required, widget=widget, label=label, initial=initial
|
|
|
|
)
|
2015-08-31 02:13:42 +00:00
|
|
|
|
|
|
|
def compress(self, data_list):
|
|
|
|
if data_list:
|
|
|
|
return "%s,%s,%s" % (
|
|
|
|
data_list[0],
|
|
|
|
"".join(data_list[1]),
|
|
|
|
data_list[2],
|
|
|
|
)
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
class DeepCopyWidget(MultiWidget):
|
|
|
|
"""
|
|
|
|
Used to test MultiWidget.__deepcopy__().
|
|
|
|
"""
|
2022-02-03 19:24:19 +00:00
|
|
|
|
2015-08-31 02:13:42 +00:00
|
|
|
def __init__(self, choices=[]):
|
|
|
|
widgets = [
|
|
|
|
RadioSelect(choices=choices),
|
|
|
|
TextInput,
|
|
|
|
]
|
2017-01-21 13:13:44 +00:00
|
|
|
super().__init__(widgets)
|
2015-08-31 02:13:42 +00:00
|
|
|
|
|
|
|
def _set_choices(self, choices):
|
|
|
|
"""
|
|
|
|
When choices are set for this widget, we want to pass those along to
|
|
|
|
the Select widget.
|
|
|
|
"""
|
|
|
|
self.widgets[0].choices = choices
|
|
|
|
|
|
|
|
def _get_choices(self):
|
|
|
|
"""
|
|
|
|
The choices for this widget are the Select widget's choices.
|
|
|
|
"""
|
|
|
|
return self.widgets[0].choices
|
2022-02-03 19:24:19 +00:00
|
|
|
|
2015-08-31 02:13:42 +00:00
|
|
|
choices = property(_get_choices, _set_choices)
|
|
|
|
|
|
|
|
|
|
|
|
class MultiWidgetTest(WidgetTest):
|
2020-02-13 22:12:46 +00:00
|
|
|
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">'
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2020-02-13 22:12:46 +00:00
|
|
|
)
|
2015-08-31 02:13:42 +00:00
|
|
|
|
|
|
|
def test_text_inputs(self):
|
|
|
|
widget = MyMultiWidget(
|
|
|
|
widgets=(
|
|
|
|
TextInput(attrs={"class": "big"}),
|
|
|
|
TextInput(attrs={"class": "small"}),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"name",
|
|
|
|
["john", "lennon"],
|
|
|
|
html=(
|
2018-01-21 07:09:10 +00:00
|
|
|
'<input type="text" class="big" value="john" name="name_0">'
|
|
|
|
'<input type="text" class="small" value="lennon" name="name_1">'
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2015-08-31 02:13:42 +00:00
|
|
|
)
|
2022-04-22 06:51:39 +00:00
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"name",
|
|
|
|
("john", "lennon"),
|
|
|
|
html=(
|
|
|
|
'<input type="text" class="big" value="john" name="name_0">'
|
|
|
|
'<input type="text" class="small" value="lennon" name="name_1">'
|
|
|
|
),
|
|
|
|
)
|
2015-08-31 02:13:42 +00:00
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"name",
|
|
|
|
"john__lennon",
|
|
|
|
html=(
|
2018-01-21 07:09:10 +00:00
|
|
|
'<input type="text" class="big" value="john" name="name_0">'
|
|
|
|
'<input type="text" class="small" value="lennon" name="name_1">'
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2015-08-31 02:13:42 +00:00
|
|
|
)
|
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"name",
|
|
|
|
"john__lennon",
|
|
|
|
attrs={"id": "foo"},
|
|
|
|
html=(
|
2018-01-21 07:09:10 +00:00
|
|
|
'<input id="foo_0" type="text" class="big" value="john" name="name_0">'
|
|
|
|
'<input id="foo_1" type="text" class="small" value="lennon" '
|
|
|
|
'name="name_1">'
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2015-08-31 02:13:42 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
def test_constructor_attrs(self):
|
|
|
|
widget = MyMultiWidget(
|
|
|
|
widgets=(
|
|
|
|
TextInput(attrs={"class": "big"}),
|
|
|
|
TextInput(attrs={"class": "small"}),
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2015-08-31 02:13:42 +00:00
|
|
|
attrs={"id": "bar"},
|
2022-02-03 19:24:19 +00:00
|
|
|
)
|
2017-01-13 11:34:33 +00:00
|
|
|
self.check_html(
|
2022-02-03 19:24:19 +00:00
|
|
|
widget,
|
|
|
|
"name",
|
2015-08-31 02:13:42 +00:00
|
|
|
["john", "lennon"],
|
2022-02-03 19:24:19 +00:00
|
|
|
html=(
|
2015-08-31 02:13:42 +00:00
|
|
|
'<input id="bar_0" type="text" class="big" value="john" name="name_0">'
|
2018-01-21 07:09:10 +00:00
|
|
|
'<input id="bar_1" type="text" class="small" value="lennon" '
|
|
|
|
'name="name_1">'
|
2015-08-31 02:13:42 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2017-01-13 11:34:33 +00:00
|
|
|
def test_constructor_attrs_with_type(self):
|
|
|
|
attrs = {"type": "number"}
|
|
|
|
widget = MyMultiWidget(widgets=(TextInput, TextInput()), attrs=attrs)
|
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"code",
|
|
|
|
["1", "2"],
|
|
|
|
html=(
|
2018-01-21 07:09:10 +00:00
|
|
|
'<input type="number" value="1" name="code_0">'
|
|
|
|
'<input type="number" value="2" name="code_1">'
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2017-01-13 11:34:33 +00:00
|
|
|
)
|
|
|
|
widget = MyMultiWidget(
|
|
|
|
widgets=(TextInput(attrs), TextInput(attrs)), attrs={"class": "bar"}
|
|
|
|
)
|
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"code",
|
|
|
|
["1", "2"],
|
|
|
|
html=(
|
2018-01-21 07:09:10 +00:00
|
|
|
'<input type="number" value="1" name="code_0" class="bar">'
|
|
|
|
'<input type="number" value="2" name="code_1" class="bar">'
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2017-01-13 11:34:33 +00:00
|
|
|
)
|
|
|
|
|
2016-09-06 21:41:54 +00:00
|
|
|
def test_value_omitted_from_data(self):
|
|
|
|
widget = MyMultiWidget(widgets=(TextInput(), TextInput()))
|
|
|
|
self.assertIs(widget.value_omitted_from_data({}, {}, "field"), True)
|
|
|
|
self.assertIs(
|
|
|
|
widget.value_omitted_from_data({"field_0": "x"}, {}, "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,
|
|
|
|
)
|
|
|
|
|
2020-02-13 22:12:46 +00:00
|
|
|
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,
|
|
|
|
)
|
|
|
|
|
2015-08-31 02:13:42 +00:00
|
|
|
def test_needs_multipart_true(self):
|
|
|
|
"""
|
|
|
|
needs_multipart_form should be True if any widgets need it.
|
|
|
|
"""
|
|
|
|
widget = MyMultiWidget(widgets=(TextInput(), FileInput()))
|
|
|
|
self.assertTrue(widget.needs_multipart_form)
|
|
|
|
|
|
|
|
def test_needs_multipart_false(self):
|
|
|
|
"""
|
|
|
|
needs_multipart_form should be False if no widgets need it.
|
|
|
|
"""
|
|
|
|
widget = MyMultiWidget(widgets=(TextInput(), TextInput()))
|
|
|
|
self.assertFalse(widget.needs_multipart_form)
|
|
|
|
|
|
|
|
def test_nested_multiwidget(self):
|
|
|
|
"""
|
|
|
|
MultiWidgets can be composed of other MultiWidgets.
|
|
|
|
"""
|
|
|
|
widget = ComplexMultiWidget()
|
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"name",
|
|
|
|
"some text,JP,2007-04-25 06:24:00",
|
|
|
|
html=(
|
|
|
|
"""
|
2018-01-21 07:09:10 +00:00
|
|
|
<input type="text" name="name_0" value="some text">
|
2018-01-20 16:19:06 +00:00
|
|
|
<select multiple name="name_1">
|
2016-09-21 22:12:13 +00:00
|
|
|
<option value="J" selected>John</option>
|
|
|
|
<option value="P" selected>Paul</option>
|
2015-08-31 02:13:42 +00:00
|
|
|
<option value="G">George</option>
|
|
|
|
<option value="R">Ringo</option>
|
|
|
|
</select>
|
2018-01-21 07:09:10 +00:00
|
|
|
<input type="text" name="name_2_0" value="2007-04-25">
|
|
|
|
<input type="text" name="name_2_1" value="06:24:00">
|
2015-08-31 02:13:42 +00:00
|
|
|
"""
|
2022-02-03 19:24:19 +00:00
|
|
|
),
|
2015-08-31 02:13:42 +00:00
|
|
|
)
|
|
|
|
|
2017-12-05 14:42:10 +00:00
|
|
|
def test_no_whitespace_between_widgets(self):
|
|
|
|
widget = MyMultiWidget(widgets=(TextInput, TextInput()))
|
|
|
|
self.check_html(
|
|
|
|
widget,
|
|
|
|
"code",
|
|
|
|
None,
|
2018-01-21 07:09:10 +00:00
|
|
|
html=('<input type="text" name="code_0"><input type="text" name="code_1">'),
|
2017-12-05 14:42:10 +00:00
|
|
|
strict=True,
|
|
|
|
)
|
|
|
|
|
2015-08-31 02:13:42 +00:00
|
|
|
def test_deepcopy(self):
|
|
|
|
"""
|
|
|
|
MultiWidget should define __deepcopy__() (#12048).
|
|
|
|
"""
|
|
|
|
w1 = DeepCopyWidget(choices=[1, 2, 3])
|
|
|
|
w2 = copy.deepcopy(w1)
|
|
|
|
w2.choices = [4, 5, 6]
|
|
|
|
# w2 ought to be independent of w1, since MultiWidget ought
|
|
|
|
# to make a copy of its sub-widgets when it is copied.
|
|
|
|
self.assertEqual(w1.choices, [1, 2, 3])
|
2022-01-13 23:08:38 +00:00
|
|
|
|
|
|
|
def test_fieldset(self):
|
|
|
|
class TestForm(Form):
|
|
|
|
template_name = "forms_tests/use_fieldset.html"
|
|
|
|
field = ComplexField(widget=ComplexMultiWidget)
|
|
|
|
|
|
|
|
form = TestForm()
|
|
|
|
self.assertIs(form["field"].field.widget.use_fieldset, True)
|
|
|
|
self.assertHTMLEqual(
|
|
|
|
"<div><fieldset><legend>Field:</legend>"
|
|
|
|
'<input type="text" name="field_0" required id="id_field_0">'
|
|
|
|
'<select name="field_1" required id="id_field_1" multiple>'
|
|
|
|
'<option value="J">John</option><option value="P">Paul</option>'
|
|
|
|
'<option value="G">George</option><option value="R">Ringo</option></select>'
|
|
|
|
'<input type="text" name="field_2_0" required id="id_field_2_0">'
|
|
|
|
'<input type="text" name="field_2_1" required id="id_field_2_1">'
|
|
|
|
"</fieldset></div>",
|
|
|
|
form.render(),
|
|
|
|
)
|