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
This commit is contained in:
Adrian Holovaty 2006-11-29 01:40:27 +00:00
parent bb45c394a6
commit fe4af48ec8
3 changed files with 72 additions and 6 deletions

View File

@ -202,9 +202,10 @@ class BoundField(object):
field's HTML-escaped verbose_name. field's HTML-escaped verbose_name.
""" """
contents = contents or escape(self.verbose_name) contents = contents or escape(self.verbose_name)
id_ = self._field.widget.attrs.get('id') or self.auto_id widget = self._field.widget
id_ = widget.attrs.get('id') or self.auto_id
if id_: if id_:
contents = '<label for="%s">%s</label>' % (id_, contents) contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents)
return contents return contents
def _auto_id(self): def _auto_id(self):

View File

@ -35,6 +35,19 @@ class Widget(object):
attrs.update(extra_attrs) attrs.update(extra_attrs)
return attrs return attrs
def id_for_label(self, id_):
"""
Returns the HTML ID attribute of this Widget for use by a <label>,
given the ID of the field. Returns None if no ID is available.
This hook is necessary because some widgets have multiple HTML
elements and, thus, multiple IDs. In that case, this method should
return an ID value that corresponds to the first ID in the widget's
tags.
"""
return id_
id_for_label = classmethod(id_for_label)
class Input(Widget): class Input(Widget):
""" """
Base class for all <input> widgets (except type='checkbox' and Base class for all <input> widgets (except type='checkbox' and
@ -111,10 +124,11 @@ class SelectMultiple(Widget):
class RadioInput(object): class RadioInput(object):
"An object used by RadioFieldRenderer that represents a single <input type='radio'>." "An object used by RadioFieldRenderer that represents a single <input type='radio'>."
def __init__(self, name, value, attrs, choice): def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value self.name, self.value = name, value
self.attrs = attrs or {} self.attrs = attrs
self.choice_value, self.choice_label = choice self.choice_value, self.choice_label = choice
self.index = index
def __str__(self): def __str__(self):
return u'<label>%s %s</label>' % (self.tag(), self.choice_label) return u'<label>%s %s</label>' % (self.tag(), self.choice_label)
@ -123,6 +137,8 @@ class RadioInput(object):
return self.value == smart_unicode(self.choice_value) return self.value == smart_unicode(self.choice_value)
def tag(self): def tag(self):
if self.attrs.has_key('id'):
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value) final_attrs = dict(self.attrs, type='radio', name=self.name, value=self.choice_value)
if self.is_checked(): if self.is_checked():
final_attrs['checked'] = 'checked' final_attrs['checked'] = 'checked'
@ -135,8 +151,8 @@ class RadioFieldRenderer(object):
self.choices = choices self.choices = choices
def __iter__(self): def __iter__(self):
for choice in self.choices: for i, choice in enumerate(self.choices):
yield RadioInput(self.name, self.value, self.attrs, choice) yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i)
def __str__(self): def __str__(self):
"Outputs a <ul> for this set of radio fields." "Outputs a <ul> for this set of radio fields."
@ -147,7 +163,18 @@ class RadioSelect(Select):
"Returns a RadioFieldRenderer instance rather than a Unicode string." "Returns a RadioFieldRenderer instance rather than a Unicode string."
if value is None: value = '' if value is None: value = ''
str_value = smart_unicode(value) # Normalize to string. str_value = smart_unicode(value) # Normalize to string.
attrs = attrs or {}
return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices))) return RadioFieldRenderer(name, str_value, attrs, list(chain(self.choices, choices)))
def id_for_label(self, id_):
# RadioSelect is represented by multiple <input type="radio"> fields,
# each of which has a distinct ID. The IDs are made distinct by a "_X"
# suffix, where X is the zero-based index of the radio field. Thus,
# the label for a RadioSelect should reference the first one ('_0').
if id_:
id_ += '_0'
return id_
id_for_label = classmethod(id_for_label)
class CheckboxSelectMultiple(Widget): class CheckboxSelectMultiple(Widget):
pass pass

View File

@ -1373,6 +1373,44 @@ For a form with a <select>, use ChoiceField:
<li><label><input type="radio" name="language" value="P" /> Python</label></li> <li><label><input type="radio" name="language" value="P" /> Python</label></li>
<li><label><input type="radio" name="language" value="J" /> Java</label></li> <li><label><input type="radio" name="language" value="J" /> Java</label></li>
</ul> </ul>
>>> print f
<tr><td>Name:</td><td><input type="text" name="name" /></td></tr>
<tr><td>Language:</td><td><ul>
<li><label><input type="radio" name="language" value="P" /> Python</label></li>
<li><label><input type="radio" name="language" value="J" /> Java</label></li>
</ul></td></tr>
>>> print f.as_ul()
<li>Name: <input type="text" name="name" /></li>
<li>Language: <ul>
<li><label><input type="radio" name="language" value="P" /> Python</label></li>
<li><label><input type="radio" name="language" value="J" /> Java</label></li>
</ul></li>
Regarding auto_id and <label>, RadioSelect is a special case. Each radio button
gets a distinct ID, formed by appending an underscore plus the button's
zero-based index.
>>> f = FrameworkForm(auto_id='id_%s')
>>> print f['language']
<ul>
<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
</ul>
When RadioSelect is used with auto_id, and the whole form is printed using
either as_table() or as_ul(), the label for the RadioSelect will point to the
ID of the *first* radio button.
>>> print f
<tr><td><label for="id_name">Name:</label></td><td><input type="text" name="name" id="id_name" /></td></tr>
<tr><td><label for="id_language_0">Language:</label></td><td><ul>
<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
</ul></td></tr>
>>> print f.as_ul()
<li><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li>
<li><label for="id_language_0">Language:</label> <ul>
<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
</ul></li>
MultipleChoiceField is a special case, as its data is required to be a list: MultipleChoiceField is a special case, as its data is required to be a list:
>>> class SongForm(Form): >>> class SongForm(Form):