1 """ |
1 """ |
2 Form classes |
2 Form classes |
3 """ |
3 """ |
4 |
4 |
5 from copy import deepcopy |
5 from django.core.exceptions import ValidationError |
6 |
6 from django.utils.copycompat import deepcopy |
7 from django.utils.datastructures import SortedDict |
7 from django.utils.datastructures import SortedDict |
8 from django.utils.html import conditional_escape |
8 from django.utils.html import conditional_escape |
9 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode |
9 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode |
10 from django.utils.safestring import mark_safe |
10 from django.utils.safestring import mark_safe |
11 |
11 |
12 from fields import Field, FileField |
12 from fields import Field, FileField |
13 from widgets import Media, media_property, TextInput, Textarea |
13 from widgets import Media, media_property, TextInput, Textarea |
14 from util import flatatt, ErrorDict, ErrorList, ValidationError |
14 from util import flatatt, ErrorDict, ErrorList |
15 |
15 |
16 __all__ = ('BaseForm', 'Form') |
16 __all__ = ('BaseForm', 'Form') |
17 |
17 |
18 NON_FIELD_ERRORS = '__all__' |
18 NON_FIELD_ERRORS = '__all__' |
19 |
19 |
20 def pretty_name(name): |
20 def pretty_name(name): |
21 "Converts 'first_name' to 'First name'" |
21 """Converts 'first_name' to 'First name'""" |
22 name = name[0].upper() + name[1:] |
22 if not name: |
23 return name.replace('_', ' ') |
23 return u'' |
|
24 return name.replace('_', ' ').capitalize() |
24 |
25 |
25 def get_declared_fields(bases, attrs, with_base_fields=True): |
26 def get_declared_fields(bases, attrs, with_base_fields=True): |
26 """ |
27 """ |
27 Create a list of form field instances from the passed in 'attrs', plus any |
28 Create a list of form field instances from the passed in 'attrs', plus any |
28 similar fields on the base classes (in 'bases'). This is used by both the |
29 similar fields on the base classes (in 'bases'). This is used by both the |
136 |
137 |
137 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): |
138 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row): |
138 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." |
139 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()." |
139 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. |
140 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. |
140 output, hidden_fields = [], [] |
141 output, hidden_fields = [], [] |
|
142 |
141 for name, field in self.fields.items(): |
143 for name, field in self.fields.items(): |
|
144 html_class_attr = '' |
142 bf = BoundField(self, field, name) |
145 bf = BoundField(self, field, name) |
143 bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. |
146 bf_errors = self.error_class([conditional_escape(error) for error in bf.errors]) # Escape and cache in local variable. |
144 if bf.is_hidden: |
147 if bf.is_hidden: |
145 if bf_errors: |
148 if bf_errors: |
146 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) |
149 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) |
147 hidden_fields.append(unicode(bf)) |
150 hidden_fields.append(unicode(bf)) |
148 else: |
151 else: |
|
152 # Create a 'class="..."' atribute if the row should have any |
|
153 # CSS classes applied. |
|
154 css_classes = bf.css_classes() |
|
155 if css_classes: |
|
156 html_class_attr = ' class="%s"' % css_classes |
|
157 |
149 if errors_on_separate_row and bf_errors: |
158 if errors_on_separate_row and bf_errors: |
150 output.append(error_row % force_unicode(bf_errors)) |
159 output.append(error_row % force_unicode(bf_errors)) |
|
160 |
151 if bf.label: |
161 if bf.label: |
152 label = conditional_escape(force_unicode(bf.label)) |
162 label = conditional_escape(force_unicode(bf.label)) |
153 # Only add the suffix if the label does not end in |
163 # Only add the suffix if the label does not end in |
154 # punctuation. |
164 # punctuation. |
155 if self.label_suffix: |
165 if self.label_suffix: |
156 if label[-1] not in ':?.!': |
166 if label[-1] not in ':?.!': |
157 label += self.label_suffix |
167 label += self.label_suffix |
158 label = bf.label_tag(label) or '' |
168 label = bf.label_tag(label) or '' |
159 else: |
169 else: |
160 label = '' |
170 label = '' |
|
171 |
161 if field.help_text: |
172 if field.help_text: |
162 help_text = help_text_html % force_unicode(field.help_text) |
173 help_text = help_text_html % force_unicode(field.help_text) |
163 else: |
174 else: |
164 help_text = u'' |
175 help_text = u'' |
165 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) |
176 |
|
177 output.append(normal_row % { |
|
178 'errors': force_unicode(bf_errors), |
|
179 'label': force_unicode(label), |
|
180 'field': unicode(bf), |
|
181 'help_text': help_text, |
|
182 'html_class_attr': html_class_attr |
|
183 }) |
|
184 |
166 if top_errors: |
185 if top_errors: |
167 output.insert(0, error_row % force_unicode(top_errors)) |
186 output.insert(0, error_row % force_unicode(top_errors)) |
|
187 |
168 if hidden_fields: # Insert any hidden fields in the last row. |
188 if hidden_fields: # Insert any hidden fields in the last row. |
169 str_hidden = u''.join(hidden_fields) |
189 str_hidden = u''.join(hidden_fields) |
170 if output: |
190 if output: |
171 last_row = output[-1] |
191 last_row = output[-1] |
172 # Chop off the trailing row_ender (e.g. '</td></tr>') and |
192 # Chop off the trailing row_ender (e.g. '</td></tr>') and |
174 if not last_row.endswith(row_ender): |
194 if not last_row.endswith(row_ender): |
175 # This can happen in the as_p() case (and possibly others |
195 # This can happen in the as_p() case (and possibly others |
176 # that users write): if there are only top errors, we may |
196 # that users write): if there are only top errors, we may |
177 # not be able to conscript the last row for our purposes, |
197 # not be able to conscript the last row for our purposes, |
178 # so insert a new, empty row. |
198 # so insert a new, empty row. |
179 last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''} |
199 last_row = (normal_row % {'errors': '', 'label': '', |
|
200 'field': '', 'help_text':'', |
|
201 'html_class_attr': html_class_attr}) |
180 output.append(last_row) |
202 output.append(last_row) |
181 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender |
203 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender |
182 else: |
204 else: |
183 # If there aren't any rows in the output, just append the |
205 # If there aren't any rows in the output, just append the |
184 # hidden fields. |
206 # hidden fields. |
185 output.append(str_hidden) |
207 output.append(str_hidden) |
186 return mark_safe(u'\n'.join(output)) |
208 return mark_safe(u'\n'.join(output)) |
187 |
209 |
188 def as_table(self): |
210 def as_table(self): |
189 "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." |
211 "Returns this form rendered as HTML <tr>s -- excluding the <table></table>." |
190 return self._html_output(u'<tr><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', u'<tr><td colspan="2">%s</td></tr>', '</td></tr>', u'<br />%s', False) |
212 return self._html_output( |
|
213 normal_row = u'<tr%(html_class_attr)s><th>%(label)s</th><td>%(errors)s%(field)s%(help_text)s</td></tr>', |
|
214 error_row = u'<tr><td colspan="2">%s</td></tr>', |
|
215 row_ender = u'</td></tr>', |
|
216 help_text_html = u'<br />%s', |
|
217 errors_on_separate_row = False) |
191 |
218 |
192 def as_ul(self): |
219 def as_ul(self): |
193 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." |
220 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>." |
194 return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False) |
221 return self._html_output( |
|
222 normal_row = u'<li%(html_class_attr)s>%(errors)s%(label)s %(field)s%(help_text)s</li>', |
|
223 error_row = u'<li>%s</li>', |
|
224 row_ender = '</li>', |
|
225 help_text_html = u' %s', |
|
226 errors_on_separate_row = False) |
195 |
227 |
196 def as_p(self): |
228 def as_p(self): |
197 "Returns this form rendered as HTML <p>s." |
229 "Returns this form rendered as HTML <p>s." |
198 return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True) |
230 return self._html_output( |
|
231 normal_row = u'<p%(html_class_attr)s>%(label)s %(field)s%(help_text)s</p>', |
|
232 error_row = u'%s', |
|
233 row_ender = '</p>', |
|
234 help_text_html = u' %s', |
|
235 errors_on_separate_row = True) |
199 |
236 |
200 def non_field_errors(self): |
237 def non_field_errors(self): |
201 """ |
238 """ |
202 Returns an ErrorList of errors that aren't associated with a particular |
239 Returns an ErrorList of errors that aren't associated with a particular |
203 field -- i.e., from Form.clean(). Returns an empty ErrorList if there |
240 field -- i.e., from Form.clean(). Returns an empty ErrorList if there |
225 self.cleaned_data = {} |
262 self.cleaned_data = {} |
226 # If the form is permitted to be empty, and none of the form data has |
263 # If the form is permitted to be empty, and none of the form data has |
227 # changed from the initial data, short circuit any validation. |
264 # changed from the initial data, short circuit any validation. |
228 if self.empty_permitted and not self.has_changed(): |
265 if self.empty_permitted and not self.has_changed(): |
229 return |
266 return |
|
267 self._clean_fields() |
|
268 self._clean_form() |
|
269 self._post_clean() |
|
270 if self._errors: |
|
271 delattr(self, 'cleaned_data') |
|
272 |
|
273 def _clean_fields(self): |
230 for name, field in self.fields.items(): |
274 for name, field in self.fields.items(): |
231 # value_from_datadict() gets the data from the data dictionaries. |
275 # value_from_datadict() gets the data from the data dictionaries. |
232 # Each widget type knows how to retrieve its own data, because some |
276 # Each widget type knows how to retrieve its own data, because some |
233 # widgets split data over several HTML fields. |
277 # widgets split data over several HTML fields. |
234 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) |
278 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) |
244 self.cleaned_data[name] = value |
288 self.cleaned_data[name] = value |
245 except ValidationError, e: |
289 except ValidationError, e: |
246 self._errors[name] = self.error_class(e.messages) |
290 self._errors[name] = self.error_class(e.messages) |
247 if name in self.cleaned_data: |
291 if name in self.cleaned_data: |
248 del self.cleaned_data[name] |
292 del self.cleaned_data[name] |
|
293 |
|
294 def _clean_form(self): |
249 try: |
295 try: |
250 self.cleaned_data = self.clean() |
296 self.cleaned_data = self.clean() |
251 except ValidationError, e: |
297 except ValidationError, e: |
252 self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) |
298 self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) |
253 if self._errors: |
299 |
254 delattr(self, 'cleaned_data') |
300 def _post_clean(self): |
|
301 """ |
|
302 An internal hook for performing additional cleaning after form cleaning |
|
303 is complete. Used for model validation in model forms. |
|
304 """ |
|
305 pass |
255 |
306 |
256 def clean(self): |
307 def clean(self): |
257 """ |
308 """ |
258 Hook for doing any extra form-wide cleaning after Field.clean() been |
309 Hook for doing any extra form-wide cleaning after Field.clean() been |
259 called on every field. Any ValidationError raised by this method will |
310 called on every field. Any ValidationError raised by this method will |
386 data = self.data |
441 data = self.data |
387 if not only_initial: |
442 if not only_initial: |
388 name = self.html_name |
443 name = self.html_name |
389 else: |
444 else: |
390 name = self.html_initial_name |
445 name = self.html_initial_name |
|
446 if self.field.localize: |
|
447 data = self.field.localize_value(data) |
391 return widget.render(name, data, attrs=attrs) |
448 return widget.render(name, data, attrs=attrs) |
392 |
449 |
393 def as_text(self, attrs=None, **kwargs): |
450 def as_text(self, attrs=None, **kwargs): |
394 """ |
451 """ |
395 Returns a string of HTML for representing this as an <input type="text">. |
452 Returns a string of HTML for representing this as an <input type="text">. |
427 if id_: |
484 if id_: |
428 attrs = attrs and flatatt(attrs) or '' |
485 attrs = attrs and flatatt(attrs) or '' |
429 contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents)) |
486 contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents)) |
430 return mark_safe(contents) |
487 return mark_safe(contents) |
431 |
488 |
|
489 def css_classes(self, extra_classes=None): |
|
490 """ |
|
491 Returns a string of space-separated CSS classes for this field. |
|
492 """ |
|
493 if hasattr(extra_classes, 'split'): |
|
494 extra_classes = extra_classes.split() |
|
495 extra_classes = set(extra_classes or []) |
|
496 if self.errors and hasattr(self.form, 'error_css_class'): |
|
497 extra_classes.add(self.form.error_css_class) |
|
498 if self.field.required and hasattr(self.form, 'required_css_class'): |
|
499 extra_classes.add(self.form.required_css_class) |
|
500 return ' '.join(extra_classes) |
|
501 |
432 def _is_hidden(self): |
502 def _is_hidden(self): |
433 "Returns True if this BoundField's widget is hidden." |
503 "Returns True if this BoundField's widget is hidden." |
434 return self.field.widget.is_hidden |
504 return self.field.widget.is_hidden |
435 is_hidden = property(_is_hidden) |
505 is_hidden = property(_is_hidden) |
436 |
506 |