From be0bab1bb8da80402248cd1fa22fd4cc09b34fe7 Mon Sep 17 00:00:00 2001
From: Claude Paroz <claude@2xlibre.net>
Date: Sat, 25 May 2013 15:18:48 +0200
Subject: [PATCH] Fixed #11725 -- Made possible to create widget label tag
 without "for"

Thanks Denis Martinez for the report and initial patch, and
Sergey Kolosov for bringing the patch up to date.
---
 django/forms/forms.py                 |  7 ++++---
 tests/admin_util/tests.py             | 23 +++++++++++------------
 tests/forms_tests/tests/test_forms.py | 17 +++++++++++++++++
 3 files changed, 32 insertions(+), 15 deletions(-)

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('<label for="{0}"{1}>{2}</label>',
-                                   widget.id_for_label(id_), attrs, contents
-                                   )
+            contents = format_html('<label{0}>{1}</label>', 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 = '<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % 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('<i>cb</i>'))
 
         form = MyForm()
-        self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
-                         '<label for="id_text" class="required inline"><i>text</i>:</label>')
-        self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
-                         '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')
+        self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
+                             '<label for="id_text" class="required inline"><i>text</i>:</label>')
+        self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
+                             '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')
 
         # 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(),
-                         '<label for="id_text" class="required inline">&amp;text:</label>')
-        self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
-                         '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>')
+        self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
+                             '<label for="id_text" class="required inline">&amp;text:</label>')
+        self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
+                             '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>')
 
     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&amp;')
+
+    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(), '<label for="custom_id_custom">Custom</label>')
+        self.assertHTMLEqual(form['empty'].label_tag(), '<label>Empty</label>')