Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

newforms: The <input> tags in a RadioSelect now each have a distinct …

…ID. Also, this plays nicely with auto_id and <label>s for Form.as_table() and Form.as_ul(). Refs #3064

git-svn-id: http://code.djangoproject.com/svn/django/trunk@4131 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit fe4af48ec8fd16acffa8c148b5c2308d6137f386 1 parent bb45c39
Adrian Holovaty authored November 29, 2006
5  django/newforms/forms.py
@@ -202,9 +202,10 @@ def label_tag(self, contents=None):
202 202
         field's HTML-escaped verbose_name.
203 203
         """
204 204
         contents = contents or escape(self.verbose_name)
205  
-        id_ = self._field.widget.attrs.get('id') or self.auto_id
  205
+        widget = self._field.widget
  206
+        id_ = widget.attrs.get('id') or self.auto_id
206 207
         if id_:
207  
-            contents = '<label for="%s">%s</label>' % (id_, contents)
  208
+            contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents)
208 209
         return contents
209 210
 
210 211
     def _auto_id(self):
35  django/newforms/widgets.py
@@ -35,6 +35,19 @@ def build_attrs(self, extra_attrs=None, **kwargs):
35 35
             attrs.update(extra_attrs)
36 36
         return attrs
37 37
 
  38
+    def id_for_label(self, id_):
  39
+        """
  40
+        Returns the HTML ID attribute of this Widget for use by a <label>,
  41
+        given the ID of the field. Returns None if no ID is available.
  42
+
  43
+        This hook is necessary because some widgets have multiple HTML
  44
+        elements and, thus, multiple IDs. In that case, this method should
  45
+        return an ID value that corresponds to the first ID in the widget's
  46
+        tags.
  47
+        """
  48
+        return id_
  49
+    id_for_label = classmethod(id_for_label)
  50
+
38 51
 class Input(Widget):
39 52
     """
40 53
     Base class for all <input> widgets (except type='checkbox' and
@@ -111,10 +124,11 @@ def render(self, name, value, attrs=None, choices=()):
111 124
 
112 125
 class RadioInput(object):
113 126
     "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
114  
-    def __init__(self, name, value, attrs, choice):
  127
+    def __init__(self, name, value, attrs, choice, index):
115 128
         self.name, self.value = name, value
116  
-        self.attrs = attrs or {}
  129
+        self.attrs = attrs
117 130
         self.choice_value, self.choice_label = choice
  131
+        self.index = index
118 132
 
119 133
     def __str__(self):
120 134
         return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
@@ -123,6 +137,8 @@ def is_checked(self):
123 137
         return self.value == smart_unicode(self.choice_value)
124 138
 
125 139
     def tag(self):
  140
+        if self.attrs.has_key('id'):
  141
+            self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
126 142
         final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
127 143
         if self.is_checked():
128 144
             final_attrs['checked'] = 'checked'
@@ -135,8 +151,8 @@ def __init__(self, name, value, attrs, choices):
135 151
         self.choices = choices
136 152
 
137 153
     def __iter__(self):
138  
-        for choice in self.choices:
139  
-            yield RadioInput(self.name, self.value, self.attrs, choice)
  154
+        for i, choice in enumerate(self.choices):
  155
+            yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
140 156
 
141 157
     def __str__(self):
142 158
         "Outputs a <ul> for this set of radio fields."
@@ -147,7 +163,18 @@ def render(self, name, value, attrs=None, choices=()):
147 163
         "Returns a RadioFieldRenderer instance rather than a Unicode string."
148 164
         if value is None: value = ''
149 165
         str_value = smart_unicode(value) # Normalize to string.
  166
+        attrs = attrs or {}
150 167
         return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
151 168
 
  169
+    def id_for_label(self, id_):
  170
+        # RadioSelect is represented by multiple <input type="radio"> fields,
  171
+        # each of which has a distinct ID. The IDs are made distinct by a "_X"
  172
+        # suffix, where X is the zero-based index of the radio field. Thus,
  173
+        # the label for a RadioSelect should reference the first one ('_0').
  174
+        if id_:
  175
+            id_ += '_0'
  176
+        return id_
  177
+    id_for_label = classmethod(id_for_label)
  178
+
152 179
 class CheckboxSelectMultiple(Widget):
153 180
     pass
38  tests/regressiontests/forms/tests.py
@@ -1373,6 +1373,44 @@
1373 1373
 <li><label><input type="radio" name="language" value="P" /> Python</label></li>
1374 1374
 <li><label><input type="radio" name="language" value="J" /> Java</label></li>
1375 1375
 </ul>
  1376
+>>> print f
  1377
+<tr><td>Name:</td><td><input type="text" name="name" /></td></tr>
  1378
+<tr><td>Language:</td><td><ul>
  1379
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
  1380
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
  1381
+</ul></td></tr>
  1382
+>>> print f.as_ul()
  1383
+<li>Name: <input type="text" name="name" /></li>
  1384
+<li>Language: <ul>
  1385
+<li><label><input type="radio" name="language" value="P" /> Python</label></li>
  1386
+<li><label><input type="radio" name="language" value="J" /> Java</label></li>
  1387
+</ul></li>
  1388
+
  1389
+Regarding auto_id and <label>, RadioSelect is a special case. Each radio button
  1390
+gets a distinct ID, formed by appending an underscore plus the button's
  1391
+zero-based index.
  1392
+>>> f = FrameworkForm(auto_id='id_%s')
  1393
+>>> print f['language']
  1394
+<ul>
  1395
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
  1396
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
  1397
+</ul>
  1398
+
  1399
+When RadioSelect is used with auto_id, and the whole form is printed using
  1400
+either as_table() or as_ul(), the label for the RadioSelect will point to the
  1401
+ID of the *first* radio button.
  1402
+>>> print f
  1403
+<tr><td><label for="id_name">Name:</label></td><td><input type="text" name="name" id="id_name" /></td></tr>
  1404
+<tr><td><label for="id_language_0">Language:</label></td><td><ul>
  1405
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
  1406
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
  1407
+</ul></td></tr>
  1408
+>>> print f.as_ul()
  1409
+<li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
  1410
+<li><label for="id_language_0">Language:</label> <ul>
  1411
+<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
  1412
+<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
  1413
+</ul></li>
1376 1414
 
1377 1415
 MultipleChoiceField is a special case, as its data is required to be a list:
1378 1416
 >>> class SongForm(Form):

0 notes on commit fe4af48

Please sign in to comment.
Something went wrong with that request. Please try again.