Move "(required)" text to third column instead of second column in templatetags, it's much more user friendly that way. Add new version of as_table that support our current error messages format, information about required fields and tooltips.
authorPawel Solyga <Pawel.Solyga@gmail.com>
Tue, 11 Nov 2008 01:02:56 +0000
changeset 472 519c298a4f87
parent 471 dcb1f7821b39
child 473 d01d5e3b0b5c
Move "(required)" text to third column instead of second column in templatetags, it's much more user friendly that way. Add new version of as_table that support our current error messages format, information about required fields and tooltips. Patch by: Pawel Solyga
app/soc/content/css/soc-081108.css
app/soc/content/css/soc-081111.css
app/soc/templates/soc/base.html
app/soc/templates/soc/templatetags/_field_as_table_row.html
app/soc/templates/soc/templatetags/_readonly_field_as_table_row.html
app/soc/views/helper/forms.py
--- a/app/soc/content/css/soc-081108.css	Mon Nov 10 23:18:06 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,457 +0,0 @@
-/*
-Copyright 2008 the Melange authors.
-
-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.
- */
- 
-/* 
- * STANDARD HTML TAGS 
- */ 
-
-body {
-  background-color: white;
-  color: black;
-
-  font-family: Arial, sans-serif;
-  font-size: medium;
-
-  margin: 8px;
-  margin-top: 3px;
-}
-
-img {
-  border: 0;
-}
-
-form {
-  margin: 0;
-  padding: 0;
-}
-
-li {
-  margin-bottom: 0.25em;
-}
-
-/* HEADERS */
-h1 {
-  font-size: x-large;
-  margin-top: 0px;
-}
-
-h2 {
-  font-size: large;
-}
-
-h3 {
-  font-size: medium;
-}
-
-h4 {
-  font-size: small;
-}
-
-/* PRE-FORMATTED TEXT */
-pre, code {
-  color: #007000;
-  font-family: "bogus font here", monospace;
-  font-size: 100%;
-}
-
-pre {
-  border: 1px solid silver;
-  background-color: #f5f5f5;
-  padding: 0.5em;
-  overflow: auto;
-  margin: 2em;
-}
-
-pre ins {
-  color: #cc0000;
-  font-weight: bold;
-  text-decoration: none;
-}
-
-/* LINKS AND ANCHORS */ 
-a:link {
-  color: #0000cc;
-}
-
-a:active {
-  color: #cc0000;
-}
-
-a:visited {
-  color: #551a8b;
-}
-
-a.selected, .selected a, .selected {
-  color: black;
-  font-weight: bold;
-  text-decoration: none;
-}
-
-a.novisit {
-  color: #2a55a3;
-}
-
-a.noul, a.noulv {
-  color: #4182fa; /* #93b7fa; */
-  text-decoration: none;
-}
-
-a:hover.noul {
-  text-decoration: underline;
-}
-
-a:visited.noul {
-  color: #a32a91; /* #2a55a3; */
-}
-
-
-/* Styles used by Django Forms */
-ul.errorlist {
-  color: #FF0000;
-  font-size: small;
-}
-
-
-/* TABLES */
-table {
-  border-collapse: collapse;
-}
-
-th, td {
-  /*padding: 0;*/
-  padding:2px 5px;
-  vertical-align: top;
-  text-align: left;
-}
-
-/* FORM TABLE FIELDS */ 
-td.formfieldrequired {
-  font-style: italic;
-  font-size: small;
-}
-
-td.formfieldhelptext {
-  font-style: italic;
-  font-size: small;
-}
-
-td.formfielderror {
-  color: #FF0000;
-  font-size: small;
-}
-
-td.formfieldheading {
-  font-weight: bold;
-  font-size: small;
-}
-
-td.formfieldlabel {
-  font-weight: bold;
-  font-size: small;
-}
-
-td.formfielderrorlabel {
-  font-weight: bold;
-  color: #FF0000;
-  font-size: small;
-}
-
-/* TABLE QUEUES (used with .list) */
-table#queues {
-  border-collapse: collapse;
-  width: 100%;
-}
-
-table#queues tr {
-  border-bottom: thin solid lightgray;
-}
-
-table#queues td {
-  padding: 2px;
-}  
-
-/* 
- * PAGE ELEMENTS 
- */
-
-#title {
-  border-top: 1px solid #3366cc;
-  background-color: #e5ecf9;
-  font-size: large;
-  font-weight: bold;
-  margin: 0;
-  padding: 0;
-  padding-top: 1px;
-  padding-bottom: 1px;
-  margin-top: 5px;
-  margin-left: 200px;
-  padding-left: 3px;
-}
-
-#notice {
-  margin-left: 200px;
-  padding: 3px;
-}
-
-#logo {
-  padding-right: 18px;
-  position: absolute;
-  left: 0;
-  top: -5px;
-}
-
-#login {
-  text-align: right;
-}
-
-#badge {
-  clear: both;
-  margin-top: 3.5em;
-  margin-bottom: 1em;
-  height: 53px;
-  font-style: italic;
-}
-
-#body {
-  border-left: 1px dotted silver;
-  margin-left: 200px;
-  margin-right: 25px;
-  padding-left: 18px;
-  padding-bottom: 25px;
-}
-
-#body .buttons {
-  margin-right: 4px;
-  margin-top: 20px;
-}
-  
-#body a.button, input[type^="submit"], input[type^="button"] {
-  margin: 0;
-  padding: 2px 5px 2px 5px;
-  font-family: Arial, Sans-serif;
-  font-size: 12px;
-  text-decoration: none;
-  color: #222;
-  cursor: default;
-  background: #ddd url("/soc/content/images/button-background.gif") repeat-x 0 0;
-  border: 1px solid #aaa;
-}
-
-#body a.button:hover, input[type^="submit"]:hover, input[type^="button"]:hover {
-  border-color: #9cf #69e #69e #7af;
-}
-
-#header {
-  height: 50px;
-  margin-bottom: 11px;
-  position: relative;
-}
-
-#footer {
-  clear: both;
-  text-align: center;
-  margin-top: 3.5em;
-  margin-bottom: 1em;
-  background-image: url("http://www.google.com/images/art.gif");
-  height: 53px;
-  background-repeat: no-repeat;
-  background-position: left center;
-}
-
-#footer .text {
-  padding-top: 20px;
-}
-
-#created {
-  font-size: small;
-}
-
-/* SIDEBAR MENU */
-#side {
-  width: 200px;
-  margin-bottom: 3em;
-  float: left;
-  font-size: small;
-}
-
-#menu ul {
-  margin: 0;
-  padding: 0;
-  list-style-type: none;
-  margin-bottom: 1em;
-  font-size: 95%;
-}
-
-#menu ul ul {
-  margin-left: 10px;
-  margin-bottom: 0;
-}
-
-#menu li {
-  margin-top: 4px;
-  list-style-type: none;
-  list-style-image: none;
-}
-
-#menu img {
-  margin-right: 4px;
-}
-
-#menu li.leaf {
-  padding-left: 14px;
-}
-
-#menu h4 {
-  margin: 0;
-  padding: 0;
-  margin-bottom: 1em;
-}
-
- 
-/* SEARCH FIELD */ 
-#search {
-  margin-top: 2em;
-}
-
-#search .header {
-  font-weight: bold;
-  font-size: 90%;
-  margin-bottom: 1px;
-}
-
-#search .button {
-  margin-top: 1px;
-}
-
-#search .input input {
-  width: 125px;
-}
-
-/* BLOG FEEDS */
-.blog {
-  border: 10px solid #e5ecf9;
-  border-top: 1px solid #3366cc;
-}
-
-.blog h2 {
-  margin-top: 0.1em;
-  background-color: #e5ecf9;
-}
-
-.blog h2 a {
-  text-decoration: none;
-  color: black;
-}
-
-.blog h2 a:visited {
-  text-decoration: none;
-  color: black;
-}
-
-
-.blog .entry {
-  margin-bottom: 1em;
-}
-
-.blog .title {
-   font-size: medium;
-}
-
-.blog .author {
-  color: gray;
-  margin-bottom: 0.5em;
-}
-
-.blog .snippet {
-  background-color: white;
-}
-
-/* LIST */
-.list {
-  background-color: #E5ECF9;
-  border: 1px solid  #93b7fa;
-  border-bottom: 2px solid #93b7fa;
-  padding: 3px;
-  -moz-border-radius: 5px 5px 0px 0px;
-}
-
-.list .pagination {
-  text-align: right;
-  padding: 3px;
-}
-
-.list table{
-  background-color: white;
-}
-  
-.list table th {
-  background-color: #eeeeec;
-  border-right: 1px solid lightgray;
-  border-top: 1px solid lightgray;
-}
-
-.list table tr.on {
-  background-color: #ff9;
-}
-
-.list table tr.off {
-  background-color: #fff;
-}
-
-.list table td.last {
-  border-right: 1px solid lightgray;
-}
-
-.list table .first {
-  border-left: 1px solid lightgray;
-}
-
-/* 
- * CUSTOM CLASSES 
- */
-
-.todo {
-  color: #cc0000;
-  font-size: 80%;
-}
-
-.newmark {
-  color: red;
-  font-size: 80%;
-  vertical-align: top;
-}
-
-.error {
-  color: red;
-}
-
-.notice { 
-  background:#fad163; 
-  font-size: small;
-  font-weight: bold;
-}
-
-.rounded_ul { background: url(/soc/content/images/ul.gif) no-repeat top left; }
-.rounded_ur { background: url(/soc/content/images/ur.gif) no-repeat top right; }
-.rounded_ll { background: url(/soc/content/images/ll.gif) no-repeat bottom left; }
-.rounded_lr { background: url(/soc/content/images/lr.gif) no-repeat bottom right; }
-
-/* Disabled text. */
-.disabled {
-  color: gray;
-}
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/content/css/soc-081111.css	Tue Nov 11 01:02:56 2008 +0000
@@ -0,0 +1,457 @@
+/*
+Copyright 2008 the Melange authors.
+
+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.
+ */
+ 
+/* 
+ * STANDARD HTML TAGS 
+ */ 
+
+body {
+  background-color: white;
+  color: black;
+
+  font-family: Arial, sans-serif;
+  font-size: medium;
+
+  margin: 8px;
+  margin-top: 3px;
+}
+
+img {
+  border: 0;
+}
+
+form {
+  margin: 0;
+  padding: 0;
+}
+
+li {
+  margin-bottom: 0.25em;
+}
+
+/* HEADERS */
+h1 {
+  font-size: x-large;
+  margin-top: 0px;
+}
+
+h2 {
+  font-size: large;
+}
+
+h3 {
+  font-size: medium;
+}
+
+h4 {
+  font-size: small;
+}
+
+/* PRE-FORMATTED TEXT */
+pre, code {
+  color: #007000;
+  font-family: "bogus font here", monospace;
+  font-size: 100%;
+}
+
+pre {
+  border: 1px solid silver;
+  background-color: #f5f5f5;
+  padding: 0.5em;
+  overflow: auto;
+  margin: 2em;
+}
+
+pre ins {
+  color: #cc0000;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+/* LINKS AND ANCHORS */ 
+a:link {
+  color: #0000cc;
+}
+
+a:active {
+  color: #cc0000;
+}
+
+a:visited {
+  color: #551a8b;
+}
+
+a.selected, .selected a, .selected {
+  color: black;
+  font-weight: bold;
+  text-decoration: none;
+}
+
+a.novisit {
+  color: #2a55a3;
+}
+
+a.noul, a.noulv {
+  color: #4182fa; /* #93b7fa; */
+  text-decoration: none;
+}
+
+a:hover.noul {
+  text-decoration: underline;
+}
+
+a:visited.noul {
+  color: #a32a91; /* #2a55a3; */
+}
+
+
+/* Styles used by Django Forms */
+ul.errorlist {
+  color: #FF0000;
+  font-size: small;
+}
+
+
+/* TABLES */
+table {
+  border-collapse: collapse;
+}
+
+th, td {
+  /*padding: 0;*/
+  padding:2px 5px;
+  vertical-align: top;
+  text-align: left;
+}
+
+/* FORM FIELDS */ 
+td.formfieldrequired {
+  font-style: italic;
+  font-size: small;
+}
+
+td.formfieldhelptext {
+  font-style: italic;
+  font-size: small;
+}
+
+td.formfielderror {
+  color: #FF0000;
+  font-size: small;
+}
+
+td.formfieldheading {
+  font-weight: bold;
+  font-size: small;
+}
+
+td.formfieldlabel {
+  font-weight: bold;
+  font-size: small;
+}
+
+td.formfielderrorlabel, span.formfielderrorlabel {
+  font-weight: bold;
+  color: #FF0000;
+  font-size: small;
+}
+
+/* TABLE QUEUES (used with .list) */
+table#queues {
+  border-collapse: collapse;
+  width: 100%;
+}
+
+table#queues tr {
+  border-bottom: thin solid lightgray;
+}
+
+table#queues td {
+  padding: 2px;
+}  
+
+/* 
+ * PAGE ELEMENTS 
+ */
+
+#title {
+  border-top: 1px solid #3366cc;
+  background-color: #e5ecf9;
+  font-size: large;
+  font-weight: bold;
+  margin: 0;
+  padding: 0;
+  padding-top: 1px;
+  padding-bottom: 1px;
+  margin-top: 5px;
+  margin-left: 200px;
+  padding-left: 3px;
+}
+
+#notice {
+  margin-left: 200px;
+  padding: 3px;
+}
+
+#logo {
+  padding-right: 18px;
+  position: absolute;
+  left: 0;
+  top: -5px;
+}
+
+#login {
+  text-align: right;
+}
+
+#badge {
+  clear: both;
+  margin-top: 3.5em;
+  margin-bottom: 1em;
+  height: 53px;
+  font-style: italic;
+}
+
+#body {
+  border-left: 1px dotted silver;
+  margin-left: 200px;
+  margin-right: 25px;
+  padding-left: 18px;
+  padding-bottom: 25px;
+}
+
+#body .buttons {
+  margin-right: 4px;
+  margin-top: 20px;
+}
+  
+#body a.button, input[type^="submit"], input[type^="button"] {
+  margin: 0;
+  padding: 2px 5px 2px 5px;
+  font-family: Arial, Sans-serif;
+  font-size: 12px;
+  text-decoration: none;
+  color: #222;
+  cursor: default;
+  background: #ddd url("/soc/content/images/button-background.gif") repeat-x 0 0;
+  border: 1px solid #aaa;
+}
+
+#body a.button:hover, input[type^="submit"]:hover, input[type^="button"]:hover {
+  border-color: #9cf #69e #69e #7af;
+}
+
+#header {
+  height: 50px;
+  margin-bottom: 11px;
+  position: relative;
+}
+
+#footer {
+  clear: both;
+  text-align: center;
+  margin-top: 3.5em;
+  margin-bottom: 1em;
+  background-image: url("http://www.google.com/images/art.gif");
+  height: 53px;
+  background-repeat: no-repeat;
+  background-position: left center;
+}
+
+#footer .text {
+  padding-top: 20px;
+}
+
+#created {
+  font-size: small;
+}
+
+/* SIDEBAR MENU */
+#side {
+  width: 200px;
+  margin-bottom: 3em;
+  float: left;
+  font-size: small;
+}
+
+#menu ul {
+  margin: 0;
+  padding: 0;
+  list-style-type: none;
+  margin-bottom: 1em;
+  font-size: 95%;
+}
+
+#menu ul ul {
+  margin-left: 10px;
+  margin-bottom: 0;
+}
+
+#menu li {
+  margin-top: 4px;
+  list-style-type: none;
+  list-style-image: none;
+}
+
+#menu img {
+  margin-right: 4px;
+}
+
+#menu li.leaf {
+  padding-left: 14px;
+}
+
+#menu h4 {
+  margin: 0;
+  padding: 0;
+  margin-bottom: 1em;
+}
+
+ 
+/* SEARCH FIELD */ 
+#search {
+  margin-top: 2em;
+}
+
+#search .header {
+  font-weight: bold;
+  font-size: 90%;
+  margin-bottom: 1px;
+}
+
+#search .button {
+  margin-top: 1px;
+}
+
+#search .input input {
+  width: 125px;
+}
+
+/* BLOG FEEDS */
+.blog {
+  border: 10px solid #e5ecf9;
+  border-top: 1px solid #3366cc;
+}
+
+.blog h2 {
+  margin-top: 0.1em;
+  background-color: #e5ecf9;
+}
+
+.blog h2 a {
+  text-decoration: none;
+  color: black;
+}
+
+.blog h2 a:visited {
+  text-decoration: none;
+  color: black;
+}
+
+
+.blog .entry {
+  margin-bottom: 1em;
+}
+
+.blog .title {
+   font-size: medium;
+}
+
+.blog .author {
+  color: gray;
+  margin-bottom: 0.5em;
+}
+
+.blog .snippet {
+  background-color: white;
+}
+
+/* LIST */
+.list {
+  background-color: #E5ECF9;
+  border: 1px solid  #93b7fa;
+  border-bottom: 2px solid #93b7fa;
+  padding: 3px;
+  -moz-border-radius: 5px 5px 0px 0px;
+}
+
+.list .pagination {
+  text-align: right;
+  padding: 3px;
+}
+
+.list table{
+  background-color: white;
+}
+  
+.list table th {
+  background-color: #eeeeec;
+  border-right: 1px solid lightgray;
+  border-top: 1px solid lightgray;
+}
+
+.list table tr.on {
+  background-color: #ff9;
+}
+
+.list table tr.off {
+  background-color: #fff;
+}
+
+.list table td.last {
+  border-right: 1px solid lightgray;
+}
+
+.list table .first {
+  border-left: 1px solid lightgray;
+}
+
+/* 
+ * CUSTOM CLASSES 
+ */
+
+.todo {
+  color: #cc0000;
+  font-size: 80%;
+}
+
+.newmark {
+  color: red;
+  font-size: 80%;
+  vertical-align: top;
+}
+
+.error {
+  color: red;
+}
+
+.notice { 
+  background:#fad163; 
+  font-size: small;
+  font-weight: bold;
+}
+
+.rounded_ul { background: url(/soc/content/images/ul.gif) no-repeat top left; }
+.rounded_ur { background: url(/soc/content/images/ur.gif) no-repeat top right; }
+.rounded_ll { background: url(/soc/content/images/ll.gif) no-repeat bottom left; }
+.rounded_lr { background: url(/soc/content/images/lr.gif) no-repeat bottom right; }
+
+/* Disabled text. */
+.disabled {
+  color: gray;
+}
+
+
--- a/app/soc/templates/soc/base.html	Mon Nov 10 23:18:06 2008 +0000
+++ b/app/soc/templates/soc/base.html	Tue Nov 11 01:02:56 2008 +0000
@@ -16,7 +16,7 @@
  <head>
   <meta content="text/html; charset=ISO-8859-1" http-equiv="content-type">
 	{% block stylesheet %}
-  <link rel="stylesheet" href="/soc/content/css/soc-081108.css" type="text/css"/>
+  <link rel="stylesheet" href="/soc/content/css/soc-081111.css" type="text/css"/>
 	{% endblock %}
   <title>
     {% block page_title %}
--- a/app/soc/templates/soc/templatetags/_field_as_table_row.html	Mon Nov 10 23:18:06 2008 +0000
+++ b/app/soc/templates/soc/templatetags/_field_as_table_row.html	Tue Nov 11 01:02:56 2008 +0000
@@ -28,12 +28,12 @@
   <label for="{{ field.auto_id }}">{{ field.label }}:
   </label> 
  </td>
+ <td>
+  {{ field }}
+ </td>
  <td class="formfieldrequired">
    {% if field.field.required %}(required){% endif %}
  </td>
- <td>
-  {{ field }}
- </td>
 </tr>
 
 
--- a/app/soc/templates/soc/templatetags/_readonly_field_as_table_row.html	Mon Nov 10 23:18:06 2008 +0000
+++ b/app/soc/templates/soc/templatetags/_readonly_field_as_table_row.html	Tue Nov 11 01:02:56 2008 +0000
@@ -15,7 +15,6 @@
  <td class="formfieldlabel">
 	{{ field_label }}:
  </td>
- <td>&nbsp;</td>
  <td class="formfieldvalue">
 	{{ field_value }}
  </td>
--- a/app/soc/views/helper/forms.py	Mon Nov 10 23:18:06 2008 +0000
+++ b/app/soc/views/helper/forms.py	Tue Nov 11 01:02:56 2008 +0000
@@ -20,13 +20,38 @@
 __authors__ = [
   '"Chen Lunpeng" <forever.clp@gmail.com>',
   '"Todd Larsen" <tlarsen@google.com>',
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
   ]
 
 
 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):
@@ -82,13 +107,14 @@
   prints itself also has changed. Help text is displayed in the same row as 
   label and input.
   """
-  # TODO(pawel.solyga): Add class names for form errors and required fields.
   
-  DEF_NORMAL_ROW = u'<tr><td class="formfieldlabel">%(label)s</td>' \
-      '<td>%(errors)s%(field)s%(help_text)s</td></tr>'
-  DEF_ERROR_ROW = u'<tr><td colspan="2">%s</td></tr>'
+  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</td></tr>'
+  DEF_ERROR_ROW = u'<tr><td>&nbsp;</td><td class="formfielderror">%s</td></tr>'
   DEF_ROW_ENDER = '</td></tr>'
-  DEF_HELP_TEXT_HTML = u'<td class="formfieldhelptext">%s</td>'
+  DEF_REQUIRED_HTML = u'<td class="formfieldrequired">(required)</td>'
+  DEF_HELP_TEXT_HTML = u'%s'
 
   def __init__(self, *args, **kwargs):
     """Parent class initialization.
@@ -96,14 +122,98 @@
     Args:
       *args, **kwargs:  passed through to parent __init__() constructor
     """
-    super(BaseForm, self).__init__(*args, **kwargs)
+    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, 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''
+        
+        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})
+    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': ''}
+          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(self.DEF_NORMAL_ROW, 
-                             self.DEF_ERROR_ROW, 
-                             self.DEF_ROW_ENDER, 
-                             self.DEF_HELP_TEXT_HTML, False)
+    
+    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, True)
 
 
 class SelectQueryArgForm(forms.Form):