Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 92803205cbcaaee16ac0eb724c45019a9d896aac 1 parent 85ccad4
Jacob Kaplan-Moss authored December 12, 2009
59  django/forms/forms.py
@@ -138,6 +138,8 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
138 138
         "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
139 139
         top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
140 140
         output, hidden_fields = [], []
  141
+        html_class_attr = ''
  142
+
141 143
         for name, field in self.fields.items():
142 144
             bf = BoundField(self, field, name)
143 145
             bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable.
@@ -146,8 +148,15 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
146 148
                     top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
147 149
                 hidden_fields.append(unicode(bf))
148 150
             else:
  151
+                # Create a 'class="..."' atribute if the row should have any
  152
+                # CSS classes applied.
  153
+                css_classes = bf.css_classes()
  154
+                if css_classes:
  155
+                    html_class_attr = ' class="%s"' % css_classes
  156
+
149 157
                 if errors_on_separate_row and bf_errors:
150 158
                     output.append(error_row % force_unicode(bf_errors))
  159
+
151 160
                 if bf.label:
152 161
                     label = conditional_escape(force_unicode(bf.label))
153 162
                     # Only add the suffix if the label does not end in
@@ -158,13 +167,23 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
158 167
                     label = bf.label_tag(label) or ''
159 168
                 else:
160 169
                     label = ''
  170
+
161 171
                 if field.help_text:
162 172
                     help_text = help_text_html % force_unicode(field.help_text)
163 173
                 else:
164 174
                     help_text = u''
165  
-                output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
  175
+
  176
+                output.append(normal_row % {
  177
+                    'errors': force_unicode(bf_errors),
  178
+                    'label': force_unicode(label),
  179
+                    'field': unicode(bf),
  180
+                    'help_text': help_text,
  181
+                    'html_class_attr': html_class_attr
  182
+                })
  183
+
166 184
         if top_errors:
167 185
             output.insert(0, error_row % force_unicode(top_errors))
  186
+
168 187
         if hidden_fields: # Insert any hidden fields in the last row.
169 188
             str_hidden = u''.join(hidden_fields)
170 189
             if output:
@@ -176,7 +195,9 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
176 195
                     # that users write): if there are only top errors, we may
177 196
                     # not be able to conscript the last row for our purposes,
178 197
                     # so insert a new, empty row.
179  
-                    last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''}
  198
+                    last_row = (normal_row % {'errors': '', 'label': '',
  199
+                                              'field': '', 'help_text':'',
  200
+                                              'html_class_attr': html_class_attr})
180 201
                     output.append(last_row)
181 202
                 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
182 203
             else:
@@ -187,15 +208,30 @@ def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_
187 208
 
188 209
     def as_table(self):
189 210
         "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
190  
-        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)
  211
+        return self._html_output(
  212
+            normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>',
  213
+            error_row = u'<tr><td colspan="2">%s</td></tr>',
  214
+            row_ender = u'</td></tr>',
  215
+            help_text_html = u'<br />%s',
  216
+            errors_on_separate_row = False)
191 217
 
192 218
     def as_ul(self):
193 219
         "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
194  
-        return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
  220
+        return self._html_output(
  221
+            normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>',
  222
+            error_row = u'<li>%s</li>',
  223
+            row_ender = '</li>',
  224
+            help_text_html = u' %s',
  225
+            errors_on_separate_row = False)
195 226
 
196 227
     def as_p(self):
197 228
         "Returns this form rendered as HTML <p>s."
198  
-        return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
  229
+        return self._html_output(
  230
+            normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>',
  231
+            error_row = u'%s',
  232
+            row_ender = '</p>',
  233
+            help_text_html = u' %s',
  234
+            errors_on_separate_row = True)
199 235
 
200 236
     def non_field_errors(self):
201 237
         """
@@ -433,6 +469,19 @@ def label_tag(self, contents=None, attrs=None):
433 469
             contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
434 470
         return mark_safe(contents)
435 471
 
  472
+    def css_classes(self, extra_classes=None):
  473
+        """
  474
+        Returns a string of space-separated CSS classes for this field.
  475
+        """
  476
+        if hasattr(extra_classes, 'split'):
  477
+            extra_classes = extra_classes.split()
  478
+        extra_classes = set(extra_classes or [])
  479
+        if self.errors and hasattr(self.form, 'error_css_class'):
  480
+            extra_classes.add(self.form.error_css_class)
  481
+        if self.field.required and hasattr(self.form, 'required_css_class'):
  482
+            extra_classes.add(self.form.required_css_class)
  483
+        return ' '.join(extra_classes)
  484
+
436 485
     def _is_hidden(self):
437 486
         "Returns True if this BoundField's widget is hidden."
438 487
         return self.field.widget.is_hidden
30  docs/ref/forms/api.txt
@@ -366,6 +366,36 @@ calls its ``as_table()`` method behind the scenes::
366 366
     <tr><th><label for="id_sender">Sender:</label></th><td><input type="text" name="sender" id="id_sender" /></td></tr>
367 367
     <tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself" /></td></tr>
368 368
 
  369
+Styling required or erroneous form rows
  370
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  371
+
  372
+.. versionadded:: 1.2
  373
+
  374
+It's pretty common to style form rows and fields that are required or have
  375
+errors. For example, you might want to present required form rows in bold and
  376
+highlight errors in red.
  377
+
  378
+The :class:`Form` class has a couple of hooks you can use to add ``class``
  379
+attributes to required rows or to rows with errors: simple set the
  380
+:attr:`Form.error_css_class` and/or :attr:`Form.required_css_class`
  381
+attributes::
  382
+
  383
+    class ContactForm(Form):
  384
+        error_css_class = 'error'
  385
+        required_css_class = 'required'
  386
+        
  387
+        # ... and the rest of your fields here
  388
+        
  389
+Once you've done that, rows will be given ``"error"`` and/or ``"required"``
  390
+classes, as needed. The HTML will look something like::
  391
+
  392
+    >>> f = ContactForm(data)
  393
+    >>> print f.as_table()
  394
+    <tr class="required"><th><label for="id_subject">Subject:</label>    ...
  395
+    <tr class="required"><th><label for="id_message">Message:</label>    ...
  396
+    <tr class="required error"><th><label for="id_sender">Sender:</label>      ...
  397
+    <tr><th><label for="id_cc_myself">Cc myself:<label> ...
  398
+
369 399
 .. _ref-forms-api-configuring-label:
370 400
 
371 401
 Configuring HTML ``<label>`` tags
32  tests/regressiontests/forms/forms.py
@@ -1814,4 +1814,36 @@
1814 1814
 >>> print MyForm()
1815 1815
 <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>
1816 1816
 
  1817
+# The error_html_class and required_html_class attributes ####################
  1818
+
  1819
+>>> p = Person({})
  1820
+>>> p.error_css_class = 'error'
  1821
+>>> p.required_css_class = 'required'
  1822
+
  1823
+>>> print p.as_ul()
  1824
+<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>
  1825
+<li class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool">
  1826
+<option value="1" selected="selected">Unknown</option>
  1827
+<option value="2">Yes</option>
  1828
+<option value="3">No</option>
  1829
+</select></li>
  1830
+
  1831
+>>> print p.as_p()
  1832
+<ul class="errorlist"><li>This field is required.</li></ul>
  1833
+<p class="required error"><label for="id_name">Name:</label> <input type="text" name="name" id="id_name" /></p>
  1834
+<p class="required"><label for="id_is_cool">Is cool:</label> <select name="is_cool" id="id_is_cool">
  1835
+<option value="1" selected="selected">Unknown</option>
  1836
+<option value="2">Yes</option>
  1837
+<option value="3">No</option>
  1838
+</select></p>
  1839
+
  1840
+>>> print p.as_table()
  1841
+<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>
  1842
+<tr class="required"><th><label for="id_is_cool">Is cool:</label></th><td><select name="is_cool" id="id_is_cool">
  1843
+<option value="1" selected="selected">Unknown</option>
  1844
+<option value="2">Yes</option>
  1845
+<option value="3">No</option>
  1846
+</select></td></tr>
  1847
+
  1848
+
1817 1849
 """

0 notes on commit 9280320

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