mirror of
https://github.com/django/django.git
synced 2025-04-22 00:04:43 +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.choices = choices
|
||||
self.attrs = {} if attrs is None else attrs.copy()
|
||||
self.option_attrs = {}
|
||||
self.i18n_name = get_select2_language()
|
||||
|
||||
def get_url(self):
|
||||
|
@ -623,13 +623,15 @@ class ChoiceWidget(Widget):
|
||||
checked_attribute = {"checked": True}
|
||||
option_inherits_attrs = True
|
||||
|
||||
def __init__(self, attrs=None, choices=()):
|
||||
def __init__(self, attrs=None, choices=(), option_attrs=None):
|
||||
super().__init__(attrs)
|
||||
self.choices = choices
|
||||
self.option_attrs = {} if option_attrs is None else option_attrs.copy()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
obj = copy.copy(self)
|
||||
obj.attrs = self.attrs.copy()
|
||||
obj.option_attrs = self.option_attrs.copy()
|
||||
obj.choices = copy.copy(self.choices)
|
||||
memo[id(self)] = obj
|
||||
return obj
|
||||
@ -691,9 +693,14 @@ class ChoiceWidget(Widget):
|
||||
self, name, value, label, selected, index, subindex=None, attrs=None
|
||||
):
|
||||
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:
|
||||
option_attrs.update(self.checked_attribute)
|
||||
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
|
||||
: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
|
||||
|
@ -41,6 +41,8 @@ class ChoiceWidgetTest(WidgetTest):
|
||||
self.assertIsNot(widget.choices, obj.choices)
|
||||
self.assertEqual(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):
|
||||
options = list(
|
||||
|
@ -179,6 +179,42 @@ class RadioSelectTest(ChoiceWidgetTest):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
The value is compared to its str().
|
||||
@ -491,6 +527,38 @@ class RadioSelectTest(ChoiceWidgetTest):
|
||||
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):
|
||||
class TestForm(Form):
|
||||
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):
|
||||
"""
|
||||
The value is compared to its str().
|
||||
@ -414,6 +436,23 @@ class SelectTest(ChoiceWidgetTest):
|
||||
with self.subTest(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):
|
||||
widget = self.widget(choices=[("J", "John"), ("P", "Paul")])
|
||||
self.assertIs(widget.use_required_attribute(initial=None), False)
|
||||
|
Loading…
x
Reference in New Issue
Block a user