diff --git a/django/forms/forms.py b/django/forms/forms.py
index fece2158ac..2c173a45dc 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -525,10 +525,11 @@ class BoundField(object):
widget = self.field.widget
id_ = widget.attrs.get('id') or self.auto_id
if id_:
+ id_for_label = widget.id_for_label(id_)
+ if id_for_label:
+ attrs = dict(attrs or {}, **{'for': id_for_label})
attrs = flatatt(attrs) if attrs else ''
- contents = format_html('',
- widget.id_for_label(id_), attrs, contents
- )
+ contents = format_html('', attrs, contents)
else:
contents = conditional_escape(contents)
return mark_safe(contents)
diff --git a/tests/admin_util/tests.py b/tests/admin_util/tests.py
index 35b7681cbb..4a9a203f50 100644
--- a/tests/admin_util/tests.py
+++ b/tests/admin_util/tests.py
@@ -11,8 +11,7 @@ from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
from django.contrib.sites.models import Site
from django.db import models, DEFAULT_DB_ALIAS
from django import forms
-from django.test import TestCase
-from django.utils import unittest
+from django.test import SimpleTestCase, TestCase
from django.utils.formats import localize
from django.utils.safestring import mark_safe
from django.utils import six
@@ -82,7 +81,7 @@ class NestedObjectsTests(TestCase):
# One for Location, one for Guest, and no query for EventGuide
n.collect(objs)
-class UtilTests(unittest.TestCase):
+class UtilTests(SimpleTestCase):
def test_values_from_lookup_field(self):
"""
Regression test for #12654: lookup_field
@@ -151,7 +150,7 @@ class UtilTests(unittest.TestCase):
# handling.
display_value = display_for_field(None, models.NullBooleanField())
expected = '
' % settings.STATIC_URL
- self.assertEqual(display_value, expected)
+ self.assertHTMLEqual(display_value, expected)
display_value = display_for_field(None, models.DecimalField())
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
@@ -299,10 +298,10 @@ class UtilTests(unittest.TestCase):
cb = forms.BooleanField(label=mark_safe('cb'))
form = MyForm()
- self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
- '')
- self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
- '')
+ self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
+ '')
+ self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
+ '')
# normal strings needs to be escaped
class MyForm(forms.Form):
@@ -310,10 +309,10 @@ class UtilTests(unittest.TestCase):
cb = forms.BooleanField(label='&cb')
form = MyForm()
- self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
- '')
- self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
- '')
+ self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
+ '')
+ self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
+ '')
def test_flatten_fieldsets(self):
"""
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 45e62a492c..f7eb46522f 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -1846,3 +1846,20 @@ class FormsTestCase(TestCase):
self.assertHTMLEqual(boundfield.label_tag(), 'Field')
self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&')
+
+ def test_boundfield_label_tag_custom_widget_id_for_label(self):
+ class CustomIdForLabelTextInput(TextInput):
+ def id_for_label(self, id):
+ return 'custom_' + id
+
+ class EmptyIdForLabelTextInput(TextInput):
+ def id_for_label(self, id):
+ return None
+
+ class SomeForm(Form):
+ custom = CharField(widget=CustomIdForLabelTextInput)
+ empty = CharField(widget=EmptyIdForLabelTextInput)
+
+ form = SomeForm()
+ self.assertHTMLEqual(form['custom'].label_tag(), '')
+ self.assertHTMLEqual(form['empty'].label_tag(), '')