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> </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 """ |