mirror of
https://github.com/django/django.git
synced 2025-06-05 03:29:12 +00:00
Fixed #34034 -- Allow setting HTML attributes on Select widget options.
Co-authored-by: Mariana <mmariana.pereira.20@gmail.com>
This commit is contained in:
parent
cf03aa4e94
commit
d667c2b894
@ -500,6 +500,7 @@ class AutocompleteMixin:
|
|||||||
self.db = using
|
self.db = using
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
self.attrs = {} if attrs is None else attrs.copy()
|
self.attrs = {} if attrs is None else attrs.copy()
|
||||||
|
self.option_attrs = {}
|
||||||
self.i18n_name = get_select2_language()
|
self.i18n_name = get_select2_language()
|
||||||
|
|
||||||
def get_url(self):
|
def get_url(self):
|
||||||
|
@ -623,13 +623,15 @@ class ChoiceWidget(Widget):
|
|||||||
checked_attribute = {"checked": True}
|
checked_attribute = {"checked": True}
|
||||||
option_inherits_attrs = True
|
option_inherits_attrs = True
|
||||||
|
|
||||||
def __init__(self, attrs=None, choices=()):
|
def __init__(self, attrs=None, choices=(), option_attrs=None):
|
||||||
super().__init__(attrs)
|
super().__init__(attrs)
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
|
self.option_attrs = {} if option_attrs is None else option_attrs.copy()
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
obj = copy.copy(self)
|
obj = copy.copy(self)
|
||||||
obj.attrs = self.attrs.copy()
|
obj.attrs = self.attrs.copy()
|
||||||
|
obj.option_attrs = self.option_attrs.copy()
|
||||||
obj.choices = copy.copy(self.choices)
|
obj.choices = copy.copy(self.choices)
|
||||||
memo[id(self)] = obj
|
memo[id(self)] = obj
|
||||||
return obj
|
return obj
|
||||||
@ -691,9 +693,14 @@ class ChoiceWidget(Widget):
|
|||||||
self, name, value, label, selected, index, subindex=None, attrs=None
|
self, name, value, label, selected, index, subindex=None, attrs=None
|
||||||
):
|
):
|
||||||
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
|
index = str(index) if subindex is None else "%s_%s" % (index, subindex)
|
||||||
option_attrs = (
|
|
||||||
self.build_attrs(self.attrs, attrs) if self.option_inherits_attrs else {}
|
if self.option_attrs:
|
||||||
)
|
option_attrs = self.build_attrs(self.option_attrs)
|
||||||
|
elif self.option_inherits_attrs:
|
||||||
|
option_attrs = self.build_attrs(self.attrs, attrs)
|
||||||
|
else:
|
||||||
|
option_attrs = {}
|
||||||
|
|
||||||
if selected:
|
if selected:
|
||||||
option_attrs.update(self.checked_attribute)
|
option_attrs.update(self.checked_attribute)
|
||||||
if "id" in option_attrs:
|
if "id" in option_attrs:
|
||||||
|
@ -195,6 +195,18 @@ Django will then include the extra attributes in the rendered output:
|
|||||||
You can also set the HTML ``id`` using :attr:`~Widget.attrs`. See
|
You can also set the HTML ``id`` using :attr:`~Widget.attrs`. See
|
||||||
:attr:`BoundField.id_for_label` for an example.
|
:attr:`BoundField.id_for_label` for an example.
|
||||||
|
|
||||||
|
For widgets that have options on their own, like those inheriting from ``ChoiceWidget``,
|
||||||
|
you can also pass option-specific attributes using ``option_attrs``.
|
||||||
|
For example, if you wish to set custom CSS classes for a RadioSelect's options, you could
|
||||||
|
do as follows:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
>>> from django.forms.widgets import RadioSelect
|
||||||
|
>>> widget = RadioSelect(choices=(("J", "John"),), option_attrs={"class": "special"})
|
||||||
|
>>> widget.render(name="beatle", value=["J"])
|
||||||
|
'<div><div>\n <label><input type="radio" name="beatle" value="J" class="special" checked>\n John</label>\n\n</div>\n</div>'
|
||||||
|
|
||||||
.. _styling-widget-classes:
|
.. _styling-widget-classes:
|
||||||
|
|
||||||
Styling widget classes
|
Styling widget classes
|
||||||
|
@ -41,6 +41,8 @@ class ChoiceWidgetTest(WidgetTest):
|
|||||||
self.assertIsNot(widget.choices, obj.choices)
|
self.assertIsNot(widget.choices, obj.choices)
|
||||||
self.assertEqual(widget.attrs, obj.attrs)
|
self.assertEqual(widget.attrs, obj.attrs)
|
||||||
self.assertIsNot(widget.attrs, obj.attrs)
|
self.assertIsNot(widget.attrs, obj.attrs)
|
||||||
|
self.assertEqual(widget.option_attrs, obj.option_attrs)
|
||||||
|
self.assertIsNot(widget.option_attrs, obj.option_attrs)
|
||||||
|
|
||||||
def test_options(self):
|
def test_options(self):
|
||||||
options = list(
|
options = list(
|
||||||
|
@ -179,6 +179,42 @@ class RadioSelectTest(ChoiceWidgetTest):
|
|||||||
"""
|
"""
|
||||||
self.check_html(widget, "beatle", "J", html=html)
|
self.check_html(widget, "beatle", "J", html=html)
|
||||||
|
|
||||||
|
def test_constructor_option_attrs(self):
|
||||||
|
"""
|
||||||
|
Attributes provided at instantiation are passed to the constituent
|
||||||
|
inputs.
|
||||||
|
"""
|
||||||
|
widget = self.widget(
|
||||||
|
attrs={"id": "foo"},
|
||||||
|
option_attrs={"data-test": "custom", "class": "other"},
|
||||||
|
choices=self.beatles,
|
||||||
|
)
|
||||||
|
html = """
|
||||||
|
<div id="foo">
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input checked class="other" data-test="custom" type="radio"
|
||||||
|
value="J" name="beatle">John</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input class="other" data-test="custom" type="radio"
|
||||||
|
value="P" name="beatle">Paul</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input class="other" data-test="custom" type="radio"
|
||||||
|
value="G" name="beatle">George</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>
|
||||||
|
<input class="other" data-test="custom" type="radio"
|
||||||
|
value="R" name="beatle">Ringo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
self.check_html(widget, "beatle", "J", html=html)
|
||||||
|
|
||||||
def test_compare_to_str(self):
|
def test_compare_to_str(self):
|
||||||
"""
|
"""
|
||||||
The value is compared to its str().
|
The value is compared to its str().
|
||||||
@ -491,6 +527,38 @@ class RadioSelectTest(ChoiceWidgetTest):
|
|||||||
html=html,
|
html=html,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_render_as_subwidget_with_option_attrs(self):
|
||||||
|
"""We render option_attrs for the subwidget."""
|
||||||
|
choices = (("", "------"),) + self.beatles
|
||||||
|
widget_instance = self.widget(
|
||||||
|
choices=choices,
|
||||||
|
option_attrs={"class": "special"},
|
||||||
|
)
|
||||||
|
self.check_html(
|
||||||
|
MultiWidget([widget_instance]),
|
||||||
|
"beatle",
|
||||||
|
["J"],
|
||||||
|
html="""
|
||||||
|
<div>
|
||||||
|
<div><label>
|
||||||
|
<input type="radio" name="beatle_0" value=""
|
||||||
|
class="special"> ------</label></div>
|
||||||
|
<div><label>
|
||||||
|
<input checked type="radio" name="beatle_0" value="J"
|
||||||
|
class="special"> John</label></div>
|
||||||
|
<div><label>
|
||||||
|
<input type="radio" name="beatle_0" value="P"
|
||||||
|
class="special"> Paul</label></div>
|
||||||
|
<div><label>
|
||||||
|
<input type="radio" name="beatle_0" value="G"
|
||||||
|
class="special"> George</label></div>
|
||||||
|
<div><label>
|
||||||
|
<input type="radio" name="beatle_0" value="R"
|
||||||
|
class="special"> Ringo</label></div>
|
||||||
|
</div>
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
|
||||||
def test_fieldset(self):
|
def test_fieldset(self):
|
||||||
class TestForm(Form):
|
class TestForm(Form):
|
||||||
template_name = "forms_tests/use_fieldset.html"
|
template_name = "forms_tests/use_fieldset.html"
|
||||||
|
@ -102,6 +102,28 @@ class SelectTest(ChoiceWidgetTest):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_constructor_option_attrs(self):
|
||||||
|
"""
|
||||||
|
Select options shouldn't inherit the parent widget attrs.
|
||||||
|
"""
|
||||||
|
widget = Select(
|
||||||
|
attrs={"class": "super", "id": "super"},
|
||||||
|
option_attrs={"data-test": "custom", "class": "other"},
|
||||||
|
choices=[(1, 1), (2, 2), (3, 3)],
|
||||||
|
)
|
||||||
|
self.check_html(
|
||||||
|
widget,
|
||||||
|
"num",
|
||||||
|
2,
|
||||||
|
html=(
|
||||||
|
"""<select name="num" class="super" id="super">
|
||||||
|
<option value="1" data-test="custom" class="other">1</option>
|
||||||
|
<option value="2" data-test="custom" class="other" selected>2</option>
|
||||||
|
<option value="3" data-test="custom" class="other">3</option>
|
||||||
|
</select>"""
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
def test_compare_to_str(self):
|
def test_compare_to_str(self):
|
||||||
"""
|
"""
|
||||||
The value is compared to its str().
|
The value is compared to its str().
|
||||||
@ -414,6 +436,23 @@ class SelectTest(ChoiceWidgetTest):
|
|||||||
with self.subTest(choices):
|
with self.subTest(choices):
|
||||||
self._test_optgroups(choices)
|
self._test_optgroups(choices)
|
||||||
|
|
||||||
|
def test_options_with_option_attrs(self):
|
||||||
|
options = list(
|
||||||
|
self.widget(choices=self.beatles, option_attrs={"class": "other"}).options(
|
||||||
|
"name",
|
||||||
|
["J"],
|
||||||
|
attrs={"class": "super"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.assertEqual(len(options), 4)
|
||||||
|
for option, (i, (value, label)) in zip(options, enumerate(self.beatles)):
|
||||||
|
self.assertEqual(option["name"], "name")
|
||||||
|
self.assertEqual(option["value"], value)
|
||||||
|
self.assertEqual(option["label"], label)
|
||||||
|
self.assertEqual(option["index"], str(i))
|
||||||
|
self.assertEqual(option["attrs"]["class"], "other")
|
||||||
|
self.assertIs(option["selected"], value == "J")
|
||||||
|
|
||||||
def test_doesnt_render_required_when_impossible_to_select_empty_field(self):
|
def test_doesnt_render_required_when_impossible_to_select_empty_field(self):
|
||||||
widget = self.widget(choices=[("J", "John"), ("P", "Paul")])
|
widget = self.widget(choices=[("J", "John"), ("P", "Paul")])
|
||||||
self.assertIs(widget.use_required_attribute(initial=None), False)
|
self.assertIs(widget.use_required_attribute(initial=None), False)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user