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.
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.
45 def __unicode__(self
):
49 """Returns error list rendered as text inside <span>."""
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
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.
77 def __init__(self
, *args
, **kwargs
):
78 """Fixes label and help_text issues after parent initialization.
81 *args, **kwargs: passed through to parent __init__() constructor
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
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> </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.
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
])
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
))
147 if errors_on_separate_row
and bf_errors
:
148 output
.append(error_row
% force_unicode(bf_errors
))
151 label
= escape(force_unicode(bf
.label
))
152 # Only add the suffix if the label does not end in
154 if self
.label_suffix
:
155 if label
[-1] not in ':?.!':
156 label
+= self
.label_suffix
157 label
= bf
.label_tag(label
) or ''
161 help_text
= help_text_html
% force_unicode(field
.help_text
)
166 field_class_type
= u
'formfielderrorlabel'
168 field_class_type
= u
'formfieldlabel'
171 required
= required_html
175 if errors_on_separate_row
and bf_errors
:
178 errors
= force_unicode(bf_errors
)
180 output
.append(normal_row
% {'field_class_type': field_class_type
,
182 'label': force_unicode(label
),
183 'field': unicode(bf
),
184 'required': required
,
185 'help_text': help_text
})
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
)
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
204 # If there aren't any rows in the output, just append the
206 output
.append(str_hidden
)
207 return mark_safe(u
'\n'.join(output
))
210 """Returns form rendered as HTML <tr> rows -- with no <table></table>."""
212 return self
._html
_output
_with
_required
(self
.DEF_NORMAL_ROW
,
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;
230 var url = location.href
231 var reg = /%(arg_name)s=\d+/
232 url = url.replace(reg, "%(arg_name)s="+value)
234 document.location.href = url
236 document.location.href = "%(page_path)s?%(arg_name)s="+value;
241 def __init__(self
, page_path
, arg_name
, choices
, field_name
,
242 *form_args
, **form_kwargs
):
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
253 *form_kwargs: keyword arguments passed on to the Form base
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
}))
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()
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()
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.
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
306 arg_name: replaced with the arg_name argument
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.
321 form: The form from which the cleaned fields should be collected
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.
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