diff options
| author | Jacob Kaplan-Moss <jacob@jacobian.org> | 2009-12-12 18:52:12 +0000 |
|---|---|---|
| committer | Jacob Kaplan-Moss <jacob@jacobian.org> | 2009-12-12 18:52:12 +0000 |
| commit | 92803205cbcaaee16ac0eb724c45019a9d896aac (patch) | |
| tree | 1d81f7dd9920f2de69d7e163560b6529457d9d56 | |
| parent | 85ccad4d61982fad61aee5a24d82e832b3616b63 (diff) | |
Fixed #3512: it's now possible to add CSS hooks to required/erroneous form rows. Thanks, SmileyChris.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@11830 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | django/forms/forms.py | 59 | ||||
| -rw-r--r-- | docs/ref/forms/api.txt | 30 | ||||
| -rw-r--r-- | tests/regressiontests/forms/forms.py | 32 |
3 files changed, 116 insertions, 5 deletions
diff --git a/django/forms/forms.py b/django/forms/forms.py index 705058a6e6..e854de8a7a 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -138,6 +138,8 @@ class BaseForm(StrAndUnicode): "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." top_errors = self.non_field_errors() # Errors that should be displayed above all fields. output, hidden_fields = [], [] + html_class_attr = '' + for name, field in self.fields.items(): bf = BoundField(self, field, name) bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. @@ -146,8 +148,15 @@ class BaseForm(StrAndUnicode): top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) hidden_fields.append(unicode(bf)) else: + # Create a 'class="..."' atribute if the row should have any + # CSS classes applied. + css_classes = bf.css_classes() + if css_classes: + html_class_attr = ' class="%s"' % css_classes + if errors_on_separate_row and bf_errors: output.append(error_row % force_unicode(bf_errors)) + if bf.label: label = conditional_escape(force_unicode(bf.label)) # Only add the suffix if the label does not end in @@ -158,13 +167,23 @@ class BaseForm(StrAndUnicode): label = bf.label_tag(label) or '' else: label = '' + if field.help_text: help_text = help_text_html % force_unicode(field.help_text) else: help_text = u'' - output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) + + output.append(normal_row % { + 'errors': force_unicode(bf_errors), + 'label': force_unicode(label), + 'field': unicode(bf), + 'help_text': help_text, + 'html_class_attr': html_class_attr + }) + if top_errors: output.insert(0, error_row % force_unicode(top_errors)) + if hidden_fields: # Insert any hidden fields in the last row. str_hidden = u''.join(hidden_fields) if output: @@ -176,7 +195,9 @@ class BaseForm(StrAndUnicode): # that users write): if there are only top errors, we may # not be able to conscript the last row for our purposes, # so insert a new, empty row. - last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''} + last_row = (normal_row % {'errors': '', 'label': '', + 'field': '', 'help_text':'', + 'html_class_attr': html_class_attr}) output.append(last_row) output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender else: @@ -187,15 +208,30 @@ class BaseForm(StrAndUnicode): def as_table(self): "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." - return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False) + return self._html_output( + normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', + error_row = u'<tr><td colspan="2">%s</td></tr>', + row_ender = u'</td></tr>', + help_text_html = u'<br />%s', + errors_on_separate_row = False) def as_ul(self): "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." - return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False) + return self._html_output( + normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', + error_row = u'<li>%s</li>', + row_ender = '</li>', + help_text_html = u' %s', + errors_on_separate_row = False) def as_p(self): "Returns this form rendered as HTML <p>s." - return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True) + return self._html_output( + normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', + error_row = u'%s', + row_ender = '</p>', + help_text_html = u' %s', + errors_on_separate_row = True) def non_field_errors(self): """ @@ -433,6 +469,19 @@ class BoundField(StrAndUnicode): contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents)) return mark_safe(contents) + def css_classes(self, extra_classes=None): + """ + Returns a string of space-separated CSS classes for this field. + """ + if hasattr(extra_classes, 'split'): + extra_classes = extra_classes.split() + extra_classes = set(extra_classes or []) + if self.errors and hasattr(self.form, 'error_css_class'): + extra_classes.add(self.form.error_css_class) + if self.field.required and hasattr(self.form, 'required_css_class'): + extra_classes.add(self.form.required_css_class) + return ' '.join(extra_classes) + def _is_hidden(self): "Returns True if this BoundField's widget is hidden." return self.field.widget.is_hidden diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 7a2341f69b..26934f07a3 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -366,6 +366,36 @@ calls its ``as_table()`` method behind the scenes:: <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr> <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr> +Styling required or erroneous form rows +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 1.2 + +It's pretty common to style form rows and fields that are required or have +errors. For example, you might want to present required form rows in bold and +highlight errors in red. + +The :class:`Form` class has a couple of hooks you can use to add ``class`` +attributes to required rows or to rows with errors: simple set the +:attr:`Form.error_css_class` and/or :attr:`Form.required_css_class` +attributes:: + + class ContactForm(Form): + error_css_class = 'error' + required_css_class = 'required' + + # ... and the rest of your fields here + +Once you've done that, rows will be given ``"error"`` and/or ``"required"`` +classes, as needed. The HTML will look something like:: + + >>> f = ContactForm(data) + >>> print f.as_table() + <tr class="required"><th><label for="id_subject">Subject:</label> ... + <tr class="required"><th><label for="id_message">Message:</label> ... + <tr class="required error"><th><label for="id_sender">Sender:</label> ... + <tr><th><label for="id_cc_myself">Cc myself:<label> ... + .. _ref-forms-api-configuring-label: Configuring HTML ``<label>`` tags diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index b204580131..627f50a6fe 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -1814,4 +1814,36 @@ True >>> print MyForm() <tr><th><label for="id_field1">Field1:</label></th><td><input id="id_field1" type="text" name="field1" maxlength="50" /><input type="hidden" name="initial-field1" id="initial-id_field1" /></td></tr> +# The error_html_class and required_html_class attributes #################### + +>>> p = Person({}) +>>> p.error_css_class = 'error' +>>> p.required_css_class = 'required' + +>>> print p.as_ul() +<li class="required error"><ul class="errorlist"><li>This field is required.</li></ul><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></li> +<li class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select></li> + +>>> print p.as_p() +<ul class="errorlist"><li>This field is required.</li></ul> +<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p> +<p class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select></p> + +>>> print p.as_table() +<tr class="required error"><th><label for="id_name">Name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="name" id="id_name" /></td></tr> +<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool"> +<option value="1" selected="selected">Unknown</option> +<option value="2">Yes</option> +<option value="3">No</option> +</select></td></tr> + + """ |
