Skip to content

Commit

Permalink
Fixed #3512: it's now possible to add CSS hooks to required/erroneous…
Browse files Browse the repository at this point in the history
… form rows. Thanks, SmileyChris.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@11830 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
jacobian committed Dec 12, 2009
1 parent 85ccad4 commit 9280320
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 5 deletions.
59 changes: 54 additions & 5 deletions django/forms/forms.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
"Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." "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. top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
output, hidden_fields = [], [] output, hidden_fields = [], []
html_class_attr = ''

for name, field in self.fields.items(): for name, field in self.fields.items():
bf = BoundField(self, field, name) bf = BoundField(self, field, name)
bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
Expand All @@ -146,8 +148,15 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
hidden_fields.append(unicode(bf)) hidden_fields.append(unicode(bf))
else: 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: if errors_on_separate_row and bf_errors:
output.append(error_row % force_unicode(bf_errors)) output.append(error_row % force_unicode(bf_errors))

if bf.label: if bf.label:
label = conditional_escape(force_unicode(bf.label)) label = conditional_escape(force_unicode(bf.label))
# Only add the suffix if the label does not end in # Only add the suffix if the label does not end in
Expand All @@ -158,13 +167,23 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
label = bf.label_tag(label) or '' label = bf.label_tag(label) or ''
else: else:
label = '' label = ''

if field.help_text: if field.help_text:
help_text = help_text_html % force_unicode(field.help_text) help_text = help_text_html % force_unicode(field.help_text)
else: else:
help_text = u'' 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: if top_errors:
output.insert(0, error_row % force_unicode(top_errors)) output.insert(0, error_row % force_unicode(top_errors))

if hidden_fields: # Insert any hidden fields in the last row. if hidden_fields: # Insert any hidden fields in the last row.
str_hidden = u''.join(hidden_fields) str_hidden = u''.join(hidden_fields)
if output: if output:
Expand All @@ -176,7 +195,9 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
# that users write): if there are only top errors, we may # that users write): if there are only top errors, we may
# not be able to conscript the last row for our purposes, # not be able to conscript the last row for our purposes,
# so insert a new, empty row. # 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.append(last_row)
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
else: else:
Expand All @@ -187,15 +208,30 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_


def as_table(self): def as_table(self):
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>." "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): def as_ul(self):
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." "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): def as_p(self):
"Returns this form rendered as HTML <p>s." "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): def non_field_errors(self):
""" """
Expand Down Expand Up @@ -433,6 +469,19 @@ def label_tag(self, contents=None, attrs=None):
contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents)) contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
return mark_safe(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): def _is_hidden(self):
"Returns True if this BoundField's widget is hidden." "Returns True if this BoundField's widget is hidden."
return self.field.widget.is_hidden return self.field.widget.is_hidden
Expand Down
30 changes: 30 additions & 0 deletions docs/ref/forms/api.txt
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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_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> <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: .. _ref-forms-api-configuring-label:


Configuring HTML ``<label>`` tags Configuring HTML ``<label>`` tags
Expand Down
32 changes: 32 additions & 0 deletions tests/regressiontests/forms/forms.py
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1814,4 +1814,36 @@
>>> print MyForm() >>> 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> <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>
""" """

0 comments on commit 9280320

Please sign in to comment.