diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2006-11-29 01:40:27 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2006-11-29 01:40:27 +0000 |
| commit | fe4af48ec8fd16acffa8c148b5c2308d6137f386 (patch) | |
| tree | ff75c2ebbe4b2c6aef1e67eb332a4ca5e60ac7d0 | |
| parent | bb45c394a6d65d3aa1bd1adfd9417a2e4ded532b (diff) | |
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
| -rw-r--r-- | django/newforms/forms.py | 5 | ||||
| -rw-r--r-- | django/newforms/widgets.py | 35 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests.py | 38 |
3 files changed, 72 insertions, 6 deletions
diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 4fffde3b7e..1750a09489 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -202,9 +202,10 @@ class BoundField(object): field's HTML-escaped 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_: - contents = '<label for="%s">%s</label>' % (id_, contents) + contents = '<label for="%s">%s</label>' % (widget.id_for_label(id_), contents) return contents def _auto_id(self): diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index 318c76e55d..b4eac248fc 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -35,6 +35,19 @@ class Widget(object): attrs.update(extra_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): """ Base class for all <input> widgets (except type='checkbox' and @@ -111,10 +124,11 @@ class SelectMultiple(Widget): class RadioInput(object): "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.attrs = attrs or {} + self.attrs = attrs self.choice_value, self.choice_label = choice + self.index = index def __str__(self): 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) 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) if self.is_checked(): final_attrs['checked'] = 'checked' @@ -135,8 +151,8 @@ class RadioFieldRenderer(object): self.choices = choices def __iter__(self): - for choice in self.choices: - yield RadioInput(self.name, self.value, self.attrs, choice) + for i, choice in enumerate(self.choices): + yield RadioInput(self.name, self.value, self.attrs.copy(), choice, i) def __str__(self): "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." if value is None: value = '' str_value = smart_unicode(value) # Normalize to string. + attrs = attrs or {} 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): pass diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index c463f6960a..fed6fe8c5c 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -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="J" /> Java</label></li> </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: >>> class SongForm(Form): |
