Converted as_table to be a template tag
authorSverre Rabbelier <srabbelier@gmail.com>
Sun, 14 Dec 2008 00:33:59 +0000
changeset 741 2dc2c65c5f76
parent 740 caa143c799a7
child 742 3e34b45cafcc
Converted as_table to be a template tag This allows us to move all the HTML from python code (which made it extremely icky and unreadable) to Django templates, which is _a lot_ more readable, and makes it feasable for HTML/CSS experts to change the way these tables are generated. Patch by: Sverre Rabbelier
app/soc/templates/soc/models/edit.html
app/soc/templates/soc/templatetags/_as_table.html
app/soc/templates/soc/templatetags/_as_table_row.html
app/soc/views/helper/forms.py
app/soc/views/helper/templatetags/forms_helpers.py
--- a/app/soc/templates/soc/models/edit.html	Sun Dec 14 00:33:41 2008 +0000
+++ b/app/soc/templates/soc/models/edit.html	Sun Dec 14 00:33:59 2008 +0000
@@ -12,6 +12,7 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 {% endcomment %}
+{% load forms_helpers %}
 
 {% block scripts %}
 	<script type="text/javascript" src="/tiny_mce/tiny_mce_src.js"></script>
@@ -33,13 +34,13 @@
 {% endblock %}
 </p>
 <form method="POST">
- <table>
-  {{ form.as_table }}
+  <table>
+  {% as_table form %}
+  </table>
+  <table>
   <tr>
    <td colspan="4">&nbsp;</td>
   </tr>
-  </table>
-  <table>
   <tr>
     {% block submit_buttons %}
    <td> 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/templates/soc/templatetags/_as_table.html	Sun Dec 14 00:33:59 2008 +0000
@@ -0,0 +1,25 @@
+{% comment %}
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+{% endcomment %}
+{% load forms_helpers %}
+
+{{ top_errors }}
+{{ hidden_field_errors }}
+
+{% for field, required, example_text in fields %}
+  {% as_table_row form field required example_text %}
+{% endfor %}
+
+{% for field in  hidden_fields %}
+  {{ field }}
+{% endfor %}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/templates/soc/templatetags/_as_table_row.html	Sun Dec 14 00:33:59 2008 +0000
@@ -0,0 +1,45 @@
+{% comment %}
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+{% endcomment %}
+
+{% if errors %}
+<tr>
+  <td>&nbsp;</td>
+  <td class="formfielderror">
+  {% for error in errors %}
+    <span class="formfielderrorlabel">{{ error }}</span> <br />
+  {% endfor %}
+  </td>
+</tr>
+{% endif %}
+
+<tr title="{{ help_text }}">
+  <td class="{{ field_class_type }}">
+    {{ label }}
+  </td>
+  <td>
+    {{ field }}
+
+    {% if required %}
+    <td class="formfieldrequired">(required)</td>
+    {% else %}
+    <td></td>
+    {% endif %}
+
+    {% if example_text %}
+      <td class="formfieldexample">e.g. {{ example_text }}</td>
+    {% else %}
+      <td></td>
+    {% endif %}
+  </td>
+</tr>
--- a/app/soc/views/helper/forms.py	Sun Dec 14 00:33:41 2008 +0000
+++ b/app/soc/views/helper/forms.py	Sun Dec 14 00:33:59 2008 +0000
@@ -27,34 +27,11 @@
 from google.appengine.ext.db import djangoforms
 
 from django import forms
-from django.forms import forms as forms_in
-from django.forms import util
-from django.utils import encoding
 from django.utils import safestring
-from django.utils.encoding import force_unicode
-from django.utils.html import escape
 from django.utils.safestring import mark_safe
 
 
-class CustomErrorList(util.ErrorList):
-  """A collection of errors that knows how to display itself in various formats.
-  
-  This class has customized as_text method output which puts errors inside <span>
-  with formfielderrorlabel class.
-  """
-  def __unicode__(self):
-    return self.as_text()
-  
-  def as_text(self):
-    """Returns error list rendered as text inside <span>."""
-    if not self:
-      return u''
-    errors_text = u'\n'.join([u'%s' % encoding.force_unicode(e) for e in self])
-    return u'<span class="formfielderrorlabel">%(errors)s</span><br />' % \
-        {'errors': errors_text}
-
-
-class DbModelForm(djangoforms.ModelForm):
+class BaseForm(djangoforms.ModelForm):
   """Subclass of Django ModelForm that fixes some label and help_text issues.
 
   The default behavior of ModelForm is to use the verbose_name in all
@@ -80,7 +57,7 @@
     Args:
       *args, **kwargs:  passed through to parent __init__() constructor
     """
-    super(DbModelForm, self).__init__(*args, **kwargs)
+    super(BaseForm, self).__init__(*args, **kwargs)
 
     for field_name in self.fields.iterkeys():
       # Since fields can be added only to the ModelForm subclass, check to
@@ -103,132 +80,6 @@
         if hasattr(model_prop, 'example_text'):
           self.fields[field_name].example_text = model_prop.example_text
 
-class BaseForm(DbModelForm):
-  """Subclass of DbModelForm that extends as_table HTML output.
-  
-  BaseForm has additional class names in HTML tags for label and help text
-  and those can be used in CSS files for look customization. The way the Form
-  prints itself also has changed. Help text is displayed in the same row as 
-  label and input.
-  """
-  
-  DEF_NORMAL_ROW = u'<tr title="%(help_text)s"><td class=' \
-      '"%(field_class_type)s">%(label)s</td><td>' \
-      '%(errors)s%(field)s%(required)s%(example_text)s</td></tr>'
-  DEF_ERROR_ROW = u'<tr><td>&nbsp;</td><td class="formfielderror">%s</td></tr>'
-  DEF_ROW_ENDER = '</td></tr>'
-  DEF_REQUIRED_HTML = u'<td class="formfieldrequired">(required)</td>'
-  DEF_HELP_TEXT_HTML = u'%s'
-  DEF_EXAMPLE_TEXT_HTML = u'<td class="formfieldexample">e.g. %s</td>'
-
-
-  def __init__(self, *args, **kwargs):
-    """Parent class initialization.
-
-    Args:
-      *args, **kwargs:  passed through to parent __init__() constructor
-    """
-    super(BaseForm, self).__init__(error_class=CustomErrorList, *args, **kwargs)
-
-  def _html_output_with_required(self, normal_row, error_row, row_ender,
-          help_text_html, required_html, example_text_html, errors_on_separate_row):
-    """Helper function for outputting HTML.
-
-    Used by as_table(), as_ul(), as_p(). Displays information
-    about required fields.
-    """
-    # Errors that should be displayed above all fields.
-    top_errors = self.non_field_errors()
-    output, hidden_fields = [], []
-    for name, field in self.fields.items():
-      bf = forms_in.BoundField(self, field, name)
-      # Escape and cache in local variable.
-      bf_errors = self.error_class([escape(error) for error in bf.errors])
-      if bf.is_hidden:
-        if bf_errors:
-          top_errors.extend([u'(Hidden field %s) %s' % \
-              (name, force_unicode(e)) for e in bf_errors])
-        hidden_fields.append(unicode(bf))
-      else:
-        if errors_on_separate_row and bf_errors:
-          output.append(error_row % force_unicode(bf_errors))
-
-        if bf.label:
-          label = escape(force_unicode(bf.label))
-          # Only add the suffix if the label does not end in
-          # punctuation.
-          if self.label_suffix:
-            if label[-1] not in ':?.!':
-              label += self.label_suffix
-          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''
-
-        if bf_errors:
-          field_class_type = u'formfielderrorlabel'
-        else:
-          field_class_type = u'formfieldlabel'
-
-        if field.required:
-          required = required_html
-        else:
-          required = u'<td></td>'
-
-        if hasattr(field, 'example_text'):
-          example_text = example_text_html % force_unicode(field.example_text)
-        else:
-          example_text = u'<td></td>'
-
-        if errors_on_separate_row and bf_errors:
-          errors = u''
-        else:
-          errors = force_unicode(bf_errors)
-
-        output.append(normal_row % {'field_class_type': field_class_type,
-                                    'errors': errors,
-                                    'label': force_unicode(label),
-                                    'field': unicode(bf),
-                                    'required': required,
-                                    'help_text': help_text,
-                                    'example_text': example_text})
-    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:
-        last_row = output[-1]
-        # Chop off the trailing row_ender (e.g. '</td></tr>') and
-        # insert the hidden fields.
-        if not last_row.endswith(row_ender):
-          # This can happen in the as_p() case (and possibly others
-          # 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': '', 'example_text': ''}
-          output.append(last_row)
-        output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
-      else:
-        # If there aren't any rows in the output, just append the
-        # hidden fields.
-        output.append(str_hidden)
-
-    return mark_safe(u'\n'.join(output))
-
-  def as_table(self):
-    """Returns form rendered as HTML <tr> rows -- with no <table></table>."""
-
-    return self._html_output_with_required(self.DEF_NORMAL_ROW,
-                                           self.DEF_ERROR_ROW,
-                                           self.DEF_ROW_ENDER,
-                                           self.DEF_HELP_TEXT_HTML,
-                                           self.DEF_REQUIRED_HTML,
-                                           self.DEF_EXAMPLE_TEXT_HTML, True)
-
 
 class SelectQueryArgForm(forms.Form):
   """URL query argument change control implemented as a Django form.
--- a/app/soc/views/helper/templatetags/forms_helpers.py	Sun Dec 14 00:33:41 2008 +0000
+++ b/app/soc/views/helper/templatetags/forms_helpers.py	Sun Dec 14 00:33:59 2008 +0000
@@ -20,10 +20,15 @@
 __authors__ = [
   '"Todd Larsen" <tlarsen@google.com>',
   '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  '"Sverre Rabbelier" <sverre@rabbelier.nl>',
   ]
 
 
 from django import template
+from django.forms import forms as forms_in
+from django.utils import encoding
+from django.utils.encoding import force_unicode
+from django.utils.html import escape
 
 
 register = template.Library()
@@ -97,3 +102,81 @@
   """
   return {'field_label': field_label,
           'field_value': field_value}
+
+
+@register.inclusion_tag('soc/templatetags/_as_table.html')
+def as_table(form):
+  """Outputs a form as a properly formatted html table
+
+  Args:
+    form: the form that should be converted to a table
+  """
+
+  fields = []
+  hidden_fields = []
+  hidden_fields_errors = []
+
+  # Iterate over all fields and prepare it for adding 
+  for name, field in form.fields.items():
+    bf = forms_in.BoundField(form, field, name)
+
+    # If the field is hidden we display it elsewhere
+    if not bf.is_hidden:
+      example_text = ''
+      if hasattr(field, 'example_text'):
+        example_text = force_unicode(field.example_text)
+
+      item = (bf, field.required, example_text)
+      fields.append(item)
+    else:
+      hidden_fields.append(unicode(bf))
+
+      for e in bf.errors:
+        item = (name, force_unicode(e))
+        hidden_fields_errors.append(item)
+
+  return {
+      'top_errors': form.non_field_errors() or '',
+      'hidden_field_errors': hidden_fields_errors or '',
+      'form': form,
+      'fields': fields or '',
+      'hidden_fields': hidden_fields or '',
+      }
+
+
+@register.inclusion_tag('soc/templatetags/_as_table_row.html')
+def as_table_row(form, field, required, example_text):
+  """Outputs a field as a properly formatted html row
+
+  Args:
+    form: the form that the row belongs to
+    field: the field that should be converted to a row
+    required: whether the field is required
+    example_text: the example_text for this row
+  """
+
+  # Escape and cache in local variable.
+  errors = [force_unicode(escape(error)) for error in field.errors]
+
+  if field.label:
+    label = escape(force_unicode(field.label))
+
+    # Only add the suffix if the label does not end in punctuation.
+    if form.label_suffix and (label[-1] not in ':?.!'):
+      label += form.label_suffix
+
+    label = field.label_tag(label) or ''
+
+  field_class_type = 'formfield%slabel' % ('error' if errors else '')
+
+  help_text = field.help_text
+
+  return {
+      'help_text': force_unicode(help_text) if help_text else '',
+      'field_class_type': field_class_type,
+      'label': force_unicode(label) if field.label else '',
+      'field': unicode(field),
+      'required': required,
+      'example_text': example_text,
+      'errors': errors,
+      }