Some docstring fixes in few modules (soc.views.helper soc.views.models.base).
[Melange.git] / app / soc / views / helper / forms.py
blob1496277813b56590dd9458e1e80befe7df1862b4
1 #!/usr/bin/python2.5
3 # Copyright 2008 the Melange authors.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Helpers used to display various views that are forms.
18 """
20 __authors__ = [
21 '"Chen Lunpeng" <forever.clp@gmail.com>',
22 '"Todd Larsen" <tlarsen@google.com>',
23 '"Pawel Solyga" <pawel.solyga@gmail.com>',
27 from google.appengine.ext.db import djangoforms
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
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
39 class CustomErrorList(util.ErrorList):
40 """A collection of errors that knows how to display itself in various formats.
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()
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}
57 class DbModelForm(djangoforms.ModelForm):
58 """Subclass of Django ModelForm that fixes some label and help_text issues.
60 The default behavior of ModelForm is to use the verbose_name in all
61 lowercase, capitalizing only the first character, as the displayed field
62 label. This class uses verbose_name unaltered as the visible field label
63 instead.
65 The Property classes used by the App Engine Datastore do not have a
66 help_text parameter to their constructor. In a Model class, a help_text
67 attribute *can* be added to the property after it is created, but the
68 help text will not be automatically passed along to the Django ModelForm.
69 This class detects the presence of a help_text attribute and adds it to
70 the corresponding form field object.
72 ugettext_lazy() proxies used for internationalization in the Model will
73 still work correctly with this new behavior, as long as the original
74 strings are used as the translation keys.
75 """
77 def __init__(self, *args, **kwargs):
78 """Fixes label and help_text issues after parent initialization.
80 Args:
81 *args, **kwargs: passed through to parent __init__() constructor
82 """
83 super(DbModelForm, self).__init__(*args, **kwargs)
85 for field_name in self.fields.iterkeys():
86 # Since fields can be added only to the ModelForm subclass, check to
87 # see if the Model has a corresponding field first.
88 if hasattr(self.Meta.model, field_name):
89 model_prop = getattr(self.Meta.model, field_name)
91 # Check if the Model property defined verbose_name, and copy that
92 # verbatim to the corresponding field label.
93 if hasattr(model_prop, 'verbose_name'):
94 self.fields[field_name].label = model_prop.verbose_name
96 # Check if the Model property added help_text, and copy that verbatim
97 # to the corresponding field help_text.
98 if hasattr(model_prop, 'help_text'):
99 self.fields[field_name].help_text = model_prop.help_text
102 class BaseForm(DbModelForm):
103 """Subclass of DbModelForm that extends as_table HTML output.
105 BaseForm has additional class names in HTML tags for label and help text
106 and those can be used in CSS files for look customization. The way the Form
107 prints itself also has changed. Help text is displayed in the same row as
108 label and input.
111 DEF_NORMAL_ROW = u'<tr title="%(help_text)s"><td class=' \
112 '"%(field_class_type)s">%(label)s</td><td>' \
113 '%(errors)s%(field)s%(required)s</td></tr>'
114 DEF_ERROR_ROW = u'<tr><td>&nbsp;</td><td class="formfielderror">%s</td></tr>'
115 DEF_ROW_ENDER = '</td></tr>'
116 DEF_REQUIRED_HTML = u'<td class="formfieldrequired">(required)</td>'
117 DEF_HELP_TEXT_HTML = u'%s'
119 def __init__(self, *args, **kwargs):
120 """Parent class initialization.
122 Args:
123 *args, **kwargs: passed through to parent __init__() constructor
125 super(BaseForm, self).__init__(error_class=CustomErrorList, *args, **kwargs)
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.
131 Used by as_table(), as_ul(), as_p(). Displays information
132 about required fields.
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))
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''
165 if bf_errors:
166 field_class_type = u'formfielderrorlabel'
167 else:
168 field_class_type = u'formfieldlabel'
170 if field.required:
171 required = required_html
172 else:
173 required = u''
175 if errors_on_separate_row and bf_errors:
176 errors = u''
177 else:
178 errors = force_unicode(bf_errors)
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))
209 def as_table(self):
210 """Returns form rendered as HTML <tr> rows -- with no <table></table>."""
212 return self._html_output_with_required(self.DEF_NORMAL_ROW,
213 self.DEF_ERROR_ROW,
214 self.DEF_ROW_ENDER,
215 self.DEF_HELP_TEXT_HTML,
216 self.DEF_REQUIRED_HTML, True)
219 class SelectQueryArgForm(forms.Form):
220 """URL query argument change control implemented as a Django form.
223 ONCHANGE_JAVASCRIPT_FMT = '''
224 <script type="text/javascript">
225 function changeArg_%(arg_name)s(item)
227 var idx=item.selectedIndex;
228 item.selected=true;
229 var value=item.value
230 var url = location.href
231 var reg = /%(arg_name)s=\d+/
232 url = url.replace(reg, "%(arg_name)s="+value)
233 if(url.match(reg))
234 document.location.href = url
235 else
236 document.location.href = "%(page_path)s?%(arg_name)s="+value;
238 </script>
241 def __init__(self, page_path, arg_name, choices, field_name,
242 *form_args, **form_kwargs):
244 Args:
245 page_path: (usually request.path)
246 arg_name: the URL query parameter that determines which choice is
247 selected in the selection control
248 choices: list (or tuple) of value/label string two-tuples, for example:
249 (('10', '10 items per page'), ('25', '25 items per page'))
250 field_name: name of the selection field in the form
251 *form_args: positional arguments passed on to the Form base
252 class __init__()
253 *form_kwargs: keyword arguments passed on to the Form base
254 class __init__()
256 super(SelectQueryArgForm, self).__init__(*form_args, **form_kwargs)
258 self._script = safestring.mark_safe(self.ONCHANGE_JAVASCRIPT_FMT % {
259 'arg_name': arg_name, 'page_path': page_path,})
261 onchange_js_call = 'changeArg_%s(this)' % arg_name
263 self.fields[field_name] = forms.ChoiceField(
264 label='', choices=choices,
265 widget=forms.widgets.Select(attrs={'onchange': onchange_js_call}))
267 def as_table(self):
268 """Returns form rendered as HTML <tr> rows -- with no <table></table>.
270 Prepends <script> section with onchange function included.
272 return self._script + super(SelectQueryArgForm, self).as_table()
274 def as_ul(self):
275 """Returns form rendered as HTML <li> list items -- with no <ul></ul>.
277 Prepends <script> section with onchange function included.
279 return self._script + super(SelectQueryArgForm, self).as_ul()
281 def as_p(self):
282 """Returns form rendered as HTML <p> paragraphs.
284 Prepends <script> section with onchange function included.
286 return self._script + super(SelectQueryArgForm, self).as_p()
289 DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT = 'select_query_arg_%(arg_name)s'
291 def makeSelectQueryArgForm(
292 request, arg_name, initial_value, choices,
293 field_name_fmt=DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT):
294 """Wrapper that creates a customized SelectQueryArgForm.
296 Args:
297 request: the standard Django HTTP request object
298 arg_name: the URL query parameter that determines which choice is
299 selected in the selection control
300 initial_value: the initial value of the selection control
301 choices: list (or tuple) of value/label string two-tuples, for example:
302 (('10', '10 items per page'), ('25', '25 items per page'))
303 field_name_fmt: optional form field name format string; default is
304 DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT; contains these named format
305 specifiers:
306 arg_name: replaced with the arg_name argument
308 Returns:
309 a Django form implementing a query argument selection control, for
310 insertion into a template
312 field_name = field_name_fmt % {'arg_name': arg_name}
313 return SelectQueryArgForm(request.path, arg_name, choices, field_name,
314 initial={field_name: initial_value})
317 def collectCleanedFields(form):
318 """Collects all cleaned fields and returns them with the key_name.
320 Args:
321 form: The form from which the cleaned fields should be collected
323 Returns:
324 All the fields that are in the form's cleaned_data property are returned.
325 If there is a key_name field, it is not included in the returend fields,
326 instead, it is returned as the first element in the returned tuple.
327 If no key_name field is present, None is returned as first value instead.
330 fields = {}
332 key_name = None
333 if 'key_name' in form.cleaned_data:
334 key_name = form.cleaned_data.pop('key_name')
336 for field, value in form.cleaned_data.iteritems():
337 fields[field] = value
339 return key_name, fields