Fixed #36724 -- Removed invalid "for" attribute on <legend> tags.
Some checks failed
Linters / black (push) Has been cancelled
Linters / isort (push) Has been cancelled
Docs / spelling (push) Has been cancelled
Docs / blacken-docs (push) Has been cancelled
Docs / lint-docs (push) Has been cancelled
Linters / flake8 (push) Has been cancelled
Tests / Windows, SQLite, Python 3.14 (push) Has been cancelled
Tests / JavaScript tests (push) Has been cancelled

This commit is contained in:
Kasyap Pentamaraju 2025-11-11 14:59:28 +05:30 committed by Jacob Walls
parent 7e765a6859
commit 0eec2a163a
7 changed files with 52 additions and 34 deletions

View file

@ -192,7 +192,9 @@ class BoundField(RenderableFieldMixin):
if id_: if id_:
id_for_label = widget.id_for_label(id_) id_for_label = widget.id_for_label(id_)
if id_for_label: if id_for_label:
attrs = {**(attrs or {}), "for": id_for_label} attrs = attrs or {}
if tag != "legend":
attrs = {**attrs, "for": id_for_label}
if self.field.required and hasattr(self.form, "required_css_class"): if self.field.required and hasattr(self.form, "required_css_class"):
attrs = attrs or {} attrs = attrs or {}
if "class" in attrs: if "class" in attrs:

View file

@ -3913,7 +3913,7 @@ aria-describedby="id_age_error"></td></tr>""",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field"].legend_tag(), f["field"].legend_tag(),
'<legend for="id_field" class="required">Field:</legend>', '<legend class="required">Field:</legend>',
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field"].label_tag(attrs={"class": "foo"}), f["field"].label_tag(attrs={"class": "foo"}),
@ -3921,14 +3921,14 @@ aria-describedby="id_age_error"></td></tr>""",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field"].legend_tag(attrs={"class": "foo"}), f["field"].legend_tag(attrs={"class": "foo"}),
'<legend for="id_field" class="foo required">Field:</legend>', '<legend class="foo required">Field:</legend>',
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field2"].label_tag(), '<label for="id_field2">Field2:</label>' f["field2"].label_tag(), '<label for="id_field2">Field2:</label>'
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field2"].legend_tag(), f["field2"].legend_tag(),
'<legend for="id_field2">Field2:</legend>', "<legend>Field2:</legend>",
) )
def test_label_split_datetime_not_displayed(self): def test_label_split_datetime_not_displayed(self):
@ -4190,31 +4190,47 @@ aria-describedby="id_age_error"></td></tr>""",
boundfield = SomeForm()["field"] boundfield = SomeForm()["field"]
testcases = [ # (args, kwargs, expected) testcases = [ # (args, kwargs, expected_label, expected_legend)
# without anything: just print the <label> # without anything: just print the <label>/<legend>
((), {}, '<%(tag)s for="id_field">Field:</%(tag)s>'), ((), {}, '<label for="id_field">Field:</label>', "<legend>Field:</legend>"),
# passing just one argument: overrides the field's label # passing just one argument: overrides the field's label
(("custom",), {}, '<%(tag)s for="id_field">custom:</%(tag)s>'), (
("custom",),
{},
'<label for="id_field">custom:</label>',
"<legend>custom:</legend>",
),
# the overridden label is escaped # the overridden label is escaped
(("custom&",), {}, '<%(tag)s for="id_field">custom&amp;:</%(tag)s>'), (
((mark_safe("custom&"),), {}, '<%(tag)s for="id_field">custom&:</%(tag)s>'), ("custom&",),
# Passing attrs to add extra attributes on the <label> {},
'<label for="id_field">custom&amp;:</label>',
"<legend>custom&amp;:</legend>",
),
(
(mark_safe("custom&"),),
{},
'<label for="id_field">custom&:</label>',
"<legend>custom&:</legend>",
),
# Passing attrs to add extra attributes on the <label>/<legend>
( (
(), (),
{"attrs": {"class": "pretty"}}, {"attrs": {"class": "pretty"}},
'<%(tag)s for="id_field" class="pretty">Field:</%(tag)s>', '<label for="id_field" class="pretty">Field:</label>',
'<legend class="pretty">Field:</legend>',
), ),
] ]
for args, kwargs, expected in testcases: for args, kwargs, expected_label, expected_legend in testcases:
with self.subTest(args=args, kwargs=kwargs): with self.subTest(args=args, kwargs=kwargs):
self.assertHTMLEqual( self.assertHTMLEqual(
boundfield.label_tag(*args, **kwargs), boundfield.label_tag(*args, **kwargs),
expected % {"tag": "label"}, expected_label,
) )
self.assertHTMLEqual( self.assertHTMLEqual(
boundfield.legend_tag(*args, **kwargs), boundfield.legend_tag(*args, **kwargs),
expected % {"tag": "legend"}, expected_legend,
) )
def test_boundfield_label_tag_no_id(self): def test_boundfield_label_tag_no_id(self):
@ -4252,7 +4268,7 @@ aria-describedby="id_age_error"></td></tr>""",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
form["custom"].legend_tag(), form["custom"].legend_tag(),
'<legend for="custom_id_custom">Custom:</legend>', "<legend>Custom:</legend>",
) )
self.assertHTMLEqual(form["empty"].label_tag(), "<label>Empty:</label>") self.assertHTMLEqual(form["empty"].label_tag(), "<label>Empty:</label>")
self.assertHTMLEqual(form["empty"].legend_tag(), "<legend>Empty:</legend>") self.assertHTMLEqual(form["empty"].legend_tag(), "<legend>Empty:</legend>")
@ -4266,7 +4282,7 @@ aria-describedby="id_age_error"></td></tr>""",
self.assertHTMLEqual(boundfield.label_tag(), '<label for="id_field"></label>') self.assertHTMLEqual(boundfield.label_tag(), '<label for="id_field"></label>')
self.assertHTMLEqual( self.assertHTMLEqual(
boundfield.legend_tag(), boundfield.legend_tag(),
'<legend for="id_field"></legend>', "<legend></legend>",
) )
def test_boundfield_id_for_label(self): def test_boundfield_id_for_label(self):
@ -4339,7 +4355,7 @@ aria-describedby="id_age_error"></td></tr>""",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
boundfield.legend_tag(label_suffix="$"), boundfield.legend_tag(label_suffix="$"),
'<legend for="id_field">Field$</legend>', "<legend>Field$</legend>",
) )
def test_error_dict(self): def test_error_dict(self):
@ -4879,7 +4895,7 @@ aria-describedby="id_age_error"></td></tr>""",
) )
self.assertEqual( self.assertEqual(
field.legend_tag(), field.legend_tag(),
'<legend for="id_first_name">First name:</legend>', "<legend>First name:</legend>",
) )
@override_settings(USE_THOUSAND_SEPARATOR=True) @override_settings(USE_THOUSAND_SEPARATOR=True)
@ -4892,7 +4908,7 @@ aria-describedby="id_age_error"></td></tr>""",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
field.legend_tag(attrs={"number": 9999}), field.legend_tag(attrs={"number": 9999}),
'<legend number="9999" for="id_first_name">First name:</legend>', '<legend number="9999">First name:</legend>',
) )
def test_remove_cached_field(self): def test_remove_cached_field(self):
@ -5204,12 +5220,12 @@ class TemplateTests(SimpleTestCase):
self.assertHTMLEqual( self.assertHTMLEqual(
t.render(Context({"form": f})), t.render(Context({"form": f})),
"<form>" "<form>"
'<p><legend for="id_username">Username:</legend>' "<p><legend>Username:</legend>"
'<input id="id_username" type="text" name="username" maxlength="10" ' '<input id="id_username" type="text" name="username" maxlength="10" '
'aria-describedby="id_username_helptext" required></p>' 'aria-describedby="id_username_helptext" required></p>'
'<p><legend for="id_password1">Password1:</legend>' "<p><legend>Password1:</legend>"
'<input type="password" name="password1" id="id_password1" required></p>' '<input type="password" name="password1" id="id_password1" required></p>'
'<p><legend for="id_password2">Password2:</legend>' "<p><legend>Password2:</legend>"
'<input type="password" name="password2" id="id_password2" required></p>' '<input type="password" name="password2" id="id_password2" required></p>'
'<input type="submit" required>' '<input type="submit" required>'
"</form>", "</form>",

View file

@ -59,14 +59,14 @@ class FormsI18nTests(SimpleTestCase):
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field_1"].legend_tag(), f["field_1"].legend_tag(),
'<legend for="id_field_1">field_1:</legend>', "<legend>field_1:</legend>",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field_2"].label_tag(), '<label for="field_2_id">field_2:</label>' f["field_2"].label_tag(), '<label for="field_2_id">field_2:</label>'
) )
self.assertHTMLEqual( self.assertHTMLEqual(
f["field_2"].legend_tag(), f["field_2"].legend_tag(),
'<legend for="field_2_id">field_2:</legend>', "<legend>field_2:</legend>",
) )
def test_non_ascii_choices(self): def test_non_ascii_choices(self):

View file

@ -248,12 +248,12 @@ class ClearableFileInputTest(WidgetTest):
form = TestForm() form = TestForm()
self.assertIs(self.widget.use_fieldset, True) self.assertIs(self.widget.use_fieldset, True)
self.assertHTMLEqual( self.assertHTMLEqual(
'<div><fieldset><legend for="id_field">Field:</legend>' "<div><fieldset><legend>Field:</legend>"
'<input id="id_field" name="field" type="file" required></fieldset></div>' '<input id="id_field" name="field" type="file" required></fieldset></div>'
'<div><fieldset><legend for="id_with_file">With file:</legend>Currently: ' "<div><fieldset><legend>With file:</legend>Currently: "
'<a href="something">something</a><br>Change:<input type="file" ' '<a href="something">something</a><br>Change:<input type="file" '
'name="with_file" id="id_with_file"></fieldset></div>' 'name="with_file" id="id_with_file"></fieldset></div>'
'<div><fieldset><legend for="id_clearable_file">Clearable file:</legend>' "<div><fieldset><legend>Clearable file:</legend>"
'Currently: <a href="something">something</a><input ' 'Currently: <a href="something">something</a><input '
'type="checkbox" name="clearable_file-clear" id="clearable_file-clear_id">' 'type="checkbox" name="clearable_file-clear" id="clearable_file-clear_id">'
'<label for="clearable_file-clear_id">Clear</label><br>Change:' '<label for="clearable_file-clear_id">Clear</label><br>Change:'

View file

@ -718,7 +718,7 @@ class SelectDateWidgetTest(WidgetTest):
form = TestForm() form = TestForm()
self.assertIs(self.widget.use_fieldset, True) self.assertIs(self.widget.use_fieldset, True)
self.assertHTMLEqual( self.assertHTMLEqual(
'<div><fieldset><legend for="id_field_month">Field:</legend>' "<div><fieldset><legend>Field:</legend>"
'<select name="field_month" required id="id_field_month">' '<select name="field_month" required id="id_field_month">'
'<option value="1">January</option><option value="2">February</option>' '<option value="1">January</option><option value="2">February</option>'
'<option value="3">March</option><option value="4">April</option>' '<option value="3">March</option><option value="4">April</option>'

View file

@ -977,15 +977,15 @@ class TestFieldOverridesByFormMeta(SimpleTestCase):
) )
self.assertHTMLEqual( self.assertHTMLEqual(
form["name"].legend_tag(), form["name"].legend_tag(),
'<legend for="id_name">Title:</legend>', "<legend>Title:</legend>",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
form["url"].legend_tag(), form["url"].legend_tag(),
'<legend for="id_url">The URL:</legend>', "<legend>The URL:</legend>",
) )
self.assertHTMLEqual( self.assertHTMLEqual(
form["slug"].legend_tag(), form["slug"].legend_tag(),
'<legend for="id_slug">Slug:</legend>', "<legend>Slug:</legend>",
) )
def test_help_text_overrides(self): def test_help_text_overrides(self):

View file

@ -2161,7 +2161,7 @@ class TestModelFormsetOverridesTroughFormMeta(TestCase):
) )
self.assertHTMLEqual( self.assertHTMLEqual(
form["title"].legend_tag(), form["title"].legend_tag(),
'<legend for="id_title">Name:</legend>', "<legend>Name:</legend>",
) )
def test_inlineformset_factory_labels_overrides(self): def test_inlineformset_factory_labels_overrides(self):
@ -2174,7 +2174,7 @@ class TestModelFormsetOverridesTroughFormMeta(TestCase):
) )
self.assertHTMLEqual( self.assertHTMLEqual(
form["title"].legend_tag(), form["title"].legend_tag(),
'<legend for="id_title">Name:</legend>', "<legend>Name:</legend>",
) )
def test_modelformset_factory_help_text_overrides(self): def test_modelformset_factory_help_text_overrides(self):