app/soc/views/helper/forms.py
changeset 472 519c298a4f87
parent 459 2cfcedaf7c16
child 612 3cca81b1e5a1
equal deleted inserted replaced
471:dcb1f7821b39 472:519c298a4f87
    18 """
    18 """
    19 
    19 
    20 __authors__ = [
    20 __authors__ = [
    21   '"Chen Lunpeng" <forever.clp@gmail.com>',
    21   '"Chen Lunpeng" <forever.clp@gmail.com>',
    22   '"Todd Larsen" <tlarsen@google.com>',
    22   '"Todd Larsen" <tlarsen@google.com>',
       
    23   '"Pawel Solyga" <pawel.solyga@gmail.com>',
    23   ]
    24   ]
    24 
    25 
    25 
    26 
    26 from google.appengine.ext.db import djangoforms
    27 from google.appengine.ext.db import djangoforms
    27 
    28 
    28 from django import forms
    29 from django import forms
       
    30 from django.forms import forms as forms_in
       
    31 from django.forms import util
       
    32 from django.utils import encoding
    29 from django.utils import safestring
    33 from django.utils import safestring
       
    34 from django.utils.encoding import force_unicode
       
    35 from django.utils.html import escape
       
    36 from django.utils.safestring import mark_safe
       
    37 
       
    38 
       
    39 class CustomErrorList(util.ErrorList):
       
    40   """A collection of errors that knows how to display itself in various formats.
       
    41   
       
    42   This class has customized as_text method output which puts errors inside <span>
       
    43   with formfielderrorlabel class.
       
    44   """
       
    45   def __unicode__(self):
       
    46     return self.as_text()
       
    47   
       
    48   def as_text(self):
       
    49     """Returns error list rendered as text inside <span>."""
       
    50     if not self:
       
    51       return u''
       
    52     errors_text = u'\n'.join([u'%s' % encoding.force_unicode(e) for e in self])
       
    53     return u'<span class="formfielderrorlabel">%(errors)s</span><br />' % \
       
    54         {'errors': errors_text}
    30 
    55 
    31 
    56 
    32 class DbModelForm(djangoforms.ModelForm):
    57 class DbModelForm(djangoforms.ModelForm):
    33   """Subclass of Django ModelForm that fixes some label and help_text issues.
    58   """Subclass of Django ModelForm that fixes some label and help_text issues.
    34 
    59 
    80   BaseForm has additional class names in HTML tags for label and help text
   105   BaseForm has additional class names in HTML tags for label and help text
    81   and those can be used in CSS files for look customization. The way the Form
   106   and those can be used in CSS files for look customization. The way the Form
    82   prints itself also has changed. Help text is displayed in the same row as 
   107   prints itself also has changed. Help text is displayed in the same row as 
    83   label and input.
   108   label and input.
    84   """
   109   """
    85   # TODO(pawel.solyga): Add class names for form errors and required fields.
   110   
    86   
   111   DEF_NORMAL_ROW = u'<tr title="%(help_text)s"><td class=' \
    87   DEF_NORMAL_ROW = u'<tr><td class="formfieldlabel">%(label)s</td>' \
   112       '"%(field_class_type)s">%(label)s</td><td>' \
    88       '<td>%(errors)s%(field)s%(help_text)s</td></tr>'
   113       '%(errors)s%(field)s%(required)s</td></tr>'
    89   DEF_ERROR_ROW = u'<tr><td colspan="2">%s</td></tr>'
   114   DEF_ERROR_ROW = u'<tr><td>&nbsp;</td><td class="formfielderror">%s</td></tr>'
    90   DEF_ROW_ENDER = '</td></tr>'
   115   DEF_ROW_ENDER = '</td></tr>'
    91   DEF_HELP_TEXT_HTML = u'<td class="formfieldhelptext">%s</td>'
   116   DEF_REQUIRED_HTML = u'<td class="formfieldrequired">(required)</td>'
       
   117   DEF_HELP_TEXT_HTML = u'%s'
    92 
   118 
    93   def __init__(self, *args, **kwargs):
   119   def __init__(self, *args, **kwargs):
    94     """Parent class initialization.
   120     """Parent class initialization.
    95 
   121 
    96     Args:
   122     Args:
    97       *args, **kwargs:  passed through to parent __init__() constructor
   123       *args, **kwargs:  passed through to parent __init__() constructor
    98     """
   124     """
    99     super(BaseForm, self).__init__(*args, **kwargs)
   125     super(BaseForm, self).__init__(error_class=CustomErrorList, *args, **kwargs)
       
   126   
       
   127   def _html_output_with_required(self, normal_row, error_row, row_ender, 
       
   128           help_text_html, required_html, errors_on_separate_row):
       
   129     """Helper function for outputting HTML.
       
   130     
       
   131     Used by as_table(), as_ul(), as_p(). Displays information
       
   132     about required fields.
       
   133     """
       
   134     # Errors that should be displayed above all fields.
       
   135     top_errors = self.non_field_errors()
       
   136     output, hidden_fields = [], []
       
   137     for name, field in self.fields.items():
       
   138       bf = forms_in.BoundField(self, field, name)
       
   139       # Escape and cache in local variable.
       
   140       bf_errors = self.error_class([escape(error) for error in bf.errors])
       
   141       if bf.is_hidden:
       
   142         if bf_errors:
       
   143           top_errors.extend([u'(Hidden field %s) %s' % \
       
   144               (name, force_unicode(e)) for e in bf_errors])
       
   145         hidden_fields.append(unicode(bf))
       
   146       else:
       
   147         if errors_on_separate_row and bf_errors:
       
   148           output.append(error_row % force_unicode(bf_errors))
       
   149 
       
   150         if bf.label:
       
   151           label = escape(force_unicode(bf.label))
       
   152           # Only add the suffix if the label does not end in
       
   153           # punctuation.
       
   154           if self.label_suffix:
       
   155             if label[-1] not in ':?.!':
       
   156               label += self.label_suffix
       
   157           label = bf.label_tag(label) or ''
       
   158         else:
       
   159           label = ''
       
   160         if field.help_text:
       
   161           help_text = help_text_html % force_unicode(field.help_text)
       
   162         else:
       
   163           help_text = u''
       
   164 
       
   165         if bf_errors:
       
   166           field_class_type = u'formfielderrorlabel'
       
   167         else:
       
   168           field_class_type = u'formfieldlabel'
       
   169 
       
   170         if field.required:
       
   171           required = required_html
       
   172         else:
       
   173           required = u''
       
   174         
       
   175         if errors_on_separate_row and bf_errors:
       
   176           errors = u''
       
   177         else:
       
   178           errors = force_unicode(bf_errors)
       
   179         
       
   180         output.append(normal_row % {'field_class_type': field_class_type,
       
   181                                     'errors': errors, 
       
   182                                     'label': force_unicode(label), 
       
   183                                     'field': unicode(bf),
       
   184                                     'required': required,
       
   185                                     'help_text': help_text})
       
   186     if top_errors:
       
   187       output.insert(0, error_row % force_unicode(top_errors))
       
   188     if hidden_fields: # Insert any hidden fields in the last row.
       
   189       str_hidden = u''.join(hidden_fields)
       
   190       if output:
       
   191         last_row = output[-1]
       
   192         # Chop off the trailing row_ender (e.g. '</td></tr>') and
       
   193         # insert the hidden fields.
       
   194         if not last_row.endswith(row_ender):
       
   195           # This can happen in the as_p() case (and possibly others
       
   196           # that users write): if there are only top errors, we may
       
   197           # not be able to conscript the last row for our purposes,
       
   198           # so insert a new, empty row.
       
   199           last_row = normal_row % {'errors': '', 'label': '',
       
   200                                    'field': '', 'help_text': ''}
       
   201           output.append(last_row)
       
   202         output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
       
   203       else:
       
   204         # If there aren't any rows in the output, just append the
       
   205         # hidden fields.
       
   206         output.append(str_hidden)
       
   207     return mark_safe(u'\n'.join(output))
   100 
   208 
   101   def as_table(self):
   209   def as_table(self):
   102     """Returns form rendered as HTML <tr> rows -- with no <table></table>."""
   210     """Returns form rendered as HTML <tr> rows -- with no <table></table>."""
   103     return self._html_output(self.DEF_NORMAL_ROW, 
   211     
   104                              self.DEF_ERROR_ROW, 
   212     return self._html_output_with_required(self.DEF_NORMAL_ROW,
   105                              self.DEF_ROW_ENDER, 
   213                                            self.DEF_ERROR_ROW,
   106                              self.DEF_HELP_TEXT_HTML, False)
   214                                            self.DEF_ROW_ENDER,
       
   215                                            self.DEF_HELP_TEXT_HTML,
       
   216                                            self.DEF_REQUIRED_HTML, True)
   107 
   217 
   108 
   218 
   109 class SelectQueryArgForm(forms.Form):
   219 class SelectQueryArgForm(forms.Form):
   110   """URL query argument change control implemented as a Django form.
   220   """URL query argument change control implemented as a Django form.
   111   """
   221   """