|
1 """ |
|
2 Form classes |
|
3 """ |
|
4 |
|
5 from copy import deepcopy |
|
6 |
|
7 from django.utils.datastructures import SortedDict |
|
8 from django.utils.html import conditional_escape |
|
9 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode |
|
10 from django.utils.safestring import mark_safe |
|
11 |
|
12 from fields import Field, FileField |
|
13 from widgets import Media, media_property, TextInput, Textarea |
|
14 from util import flatatt, ErrorDict, ErrorList, ValidationError |
|
15 |
|
16 __all__ = ('BaseForm', 'Form') |
|
17 |
|
18 NON_FIELD_ERRORS = '__all__' |
|
19 |
|
20 def pretty_name(name): |
|
21 "Converts 'first_name' to 'First name'" |
|
22 name = name[0].upper() + name[1:] |
|
23 return name.replace('_', ' ') |
|
24 |
|
25 def get_declared_fields(bases, attrs, with_base_fields=True): |
|
26 """ |
|
27 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 Form and ModelForm metclasses. |
|
30 |
|
31 If 'with_base_fields' is True, all fields from the bases are used. |
|
32 Otherwise, only fields in the 'declared_fields' attribute on the bases are |
|
33 used. The distinction is useful in ModelForm subclassing. |
|
34 Also integrates any additional media definitions |
|
35 """ |
|
36 fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)] |
|
37 fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter)) |
|
38 |
|
39 # If this class is subclassing another Form, add that Form's fields. |
|
40 # Note that we loop over the bases in *reverse*. This is necessary in |
|
41 # order to preserve the correct order of fields. |
|
42 if with_base_fields: |
|
43 for base in bases[::-1]: |
|
44 if hasattr(base, 'base_fields'): |
|
45 fields = base.base_fields.items() + fields |
|
46 else: |
|
47 for base in bases[::-1]: |
|
48 if hasattr(base, 'declared_fields'): |
|
49 fields = base.declared_fields.items() + fields |
|
50 |
|
51 return SortedDict(fields) |
|
52 |
|
53 class DeclarativeFieldsMetaclass(type): |
|
54 """ |
|
55 Metaclass that converts Field attributes to a dictionary called |
|
56 'base_fields', taking into account parent class 'base_fields' as well. |
|
57 """ |
|
58 def __new__(cls, name, bases, attrs): |
|
59 attrs['base_fields'] = get_declared_fields(bases, attrs) |
|
60 new_class = super(DeclarativeFieldsMetaclass, |
|
61 cls).__new__(cls, name, bases, attrs) |
|
62 if 'media' not in attrs: |
|
63 new_class.media = media_property(new_class) |
|
64 return new_class |
|
65 |
|
66 class BaseForm(StrAndUnicode): |
|
67 # This is the main implementation of all the Form logic. Note that this |
|
68 # class is different than Form. See the comments by the Form class for more |
|
69 # information. Any improvements to the form API should be made to *this* |
|
70 # class, not to the Form class. |
|
71 def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, |
|
72 initial=None, error_class=ErrorList, label_suffix=':', |
|
73 empty_permitted=False): |
|
74 self.is_bound = data is not None or files is not None |
|
75 self.data = data or {} |
|
76 self.files = files or {} |
|
77 self.auto_id = auto_id |
|
78 self.prefix = prefix |
|
79 self.initial = initial or {} |
|
80 self.error_class = error_class |
|
81 self.label_suffix = label_suffix |
|
82 self.empty_permitted = empty_permitted |
|
83 self._errors = None # Stores the errors after clean() has been called. |
|
84 self._changed_data = None |
|
85 |
|
86 # The base_fields class attribute is the *class-wide* definition of |
|
87 # fields. Because a particular *instance* of the class might want to |
|
88 # alter self.fields, we create self.fields here by copying base_fields. |
|
89 # Instances should always modify self.fields; they should not modify |
|
90 # self.base_fields. |
|
91 self.fields = deepcopy(self.base_fields) |
|
92 |
|
93 def __unicode__(self): |
|
94 return self.as_table() |
|
95 |
|
96 def __iter__(self): |
|
97 for name, field in self.fields.items(): |
|
98 yield BoundField(self, field, name) |
|
99 |
|
100 def __getitem__(self, name): |
|
101 "Returns a BoundField with the given name." |
|
102 try: |
|
103 field = self.fields[name] |
|
104 except KeyError: |
|
105 raise KeyError('Key %r not found in Form' % name) |
|
106 return BoundField(self, field, name) |
|
107 |
|
108 def _get_errors(self): |
|
109 "Returns an ErrorDict for the data provided for the form" |
|
110 if self._errors is None: |
|
111 self.full_clean() |
|
112 return self._errors |
|
113 errors = property(_get_errors) |
|
114 |
|
115 def is_valid(self): |
|
116 """ |
|
117 Returns True if the form has no errors. Otherwise, False. If errors are |
|
118 being ignored, returns False. |
|
119 """ |
|
120 return self.is_bound and not bool(self.errors) |
|
121 |
|
122 def add_prefix(self, field_name): |
|
123 """ |
|
124 Returns the field name with a prefix appended, if this Form has a |
|
125 prefix set. |
|
126 |
|
127 Subclasses may wish to override. |
|
128 """ |
|
129 return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name |
|
130 |
|
131 def add_initial_prefix(self, field_name): |
|
132 """ |
|
133 Add a 'initial' prefix for checking dynamic initial values |
|
134 """ |
|
135 return u'initial-%s' % self.add_prefix(field_name) |
|
136 |
|
137 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 top_errors = self.non_field_errors() # Errors that should be displayed above all fields. |
|
140 output, hidden_fields = [], [] |
|
141 for name, field in self.fields.items(): |
|
142 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. |
|
144 if bf.is_hidden: |
|
145 if bf_errors: |
|
146 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors]) |
|
147 hidden_fields.append(unicode(bf)) |
|
148 else: |
|
149 if errors_on_separate_row and bf_errors: |
|
150 output.append(error_row % force_unicode(bf_errors)) |
|
151 if bf.label: |
|
152 label = conditional_escape(force_unicode(bf.label)) |
|
153 # Only add the suffix if the label does not end in |
|
154 # punctuation. |
|
155 if self.label_suffix: |
|
156 if label[-1] not in ':?.!': |
|
157 label += self.label_suffix |
|
158 label = bf.label_tag(label) or '' |
|
159 else: |
|
160 label = '' |
|
161 if field.help_text: |
|
162 help_text = help_text_html % force_unicode(field.help_text) |
|
163 else: |
|
164 help_text = u'' |
|
165 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text}) |
|
166 if top_errors: |
|
167 output.insert(0, error_row % force_unicode(top_errors)) |
|
168 if hidden_fields: # Insert any hidden fields in the last row. |
|
169 str_hidden = u''.join(hidden_fields) |
|
170 if output: |
|
171 last_row = output[-1] |
|
172 # Chop off the trailing row_ender (e.g. '</td></tr>') and |
|
173 # insert the hidden fields. |
|
174 if not last_row.endswith(row_ender): |
|
175 # This can happen in the as_p() case (and possibly others |
|
176 # that users write): if there are only top errors, we may |
|
177 # not be able to conscript the last row for our purposes, |
|
178 # so insert a new, empty row. |
|
179 last_row = normal_row % {'errors': '', 'label': '', 'field': '', 'help_text': ''} |
|
180 output.append(last_row) |
|
181 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender |
|
182 else: |
|
183 # If there aren't any rows in the output, just append the |
|
184 # hidden fields. |
|
185 output.append(str_hidden) |
|
186 return mark_safe(u'\n'.join(output)) |
|
187 |
|
188 def as_table(self): |
|
189 "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) |
|
191 |
|
192 def as_ul(self): |
|
193 "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) |
|
195 |
|
196 def as_p(self): |
|
197 "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) |
|
199 |
|
200 def non_field_errors(self): |
|
201 """ |
|
202 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 |
|
204 are none. |
|
205 """ |
|
206 return self.errors.get(NON_FIELD_ERRORS, self.error_class()) |
|
207 |
|
208 def _raw_value(self, fieldname): |
|
209 """ |
|
210 Returns the raw_value for a particular field name. This is just a |
|
211 convenient wrapper around widget.value_from_datadict. |
|
212 """ |
|
213 field = self.fields[fieldname] |
|
214 prefix = self.add_prefix(fieldname) |
|
215 return field.widget.value_from_datadict(self.data, self.files, prefix) |
|
216 |
|
217 def full_clean(self): |
|
218 """ |
|
219 Cleans all of self.data and populates self._errors and |
|
220 self.cleaned_data. |
|
221 """ |
|
222 self._errors = ErrorDict() |
|
223 if not self.is_bound: # Stop further processing. |
|
224 return |
|
225 self.cleaned_data = {} |
|
226 # 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. |
|
228 if self.empty_permitted and not self.has_changed(): |
|
229 return |
|
230 for name, field in self.fields.items(): |
|
231 # value_from_datadict() gets the data from the data dictionaries. |
|
232 # Each widget type knows how to retrieve its own data, because some |
|
233 # widgets split data over several HTML fields. |
|
234 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) |
|
235 try: |
|
236 if isinstance(field, FileField): |
|
237 initial = self.initial.get(name, field.initial) |
|
238 value = field.clean(value, initial) |
|
239 else: |
|
240 value = field.clean(value) |
|
241 self.cleaned_data[name] = value |
|
242 if hasattr(self, 'clean_%s' % name): |
|
243 value = getattr(self, 'clean_%s' % name)() |
|
244 self.cleaned_data[name] = value |
|
245 except ValidationError, e: |
|
246 self._errors[name] = self.error_class(e.messages) |
|
247 if name in self.cleaned_data: |
|
248 del self.cleaned_data[name] |
|
249 try: |
|
250 self.cleaned_data = self.clean() |
|
251 except ValidationError, e: |
|
252 self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages) |
|
253 if self._errors: |
|
254 delattr(self, 'cleaned_data') |
|
255 |
|
256 def clean(self): |
|
257 """ |
|
258 Hook for doing any extra form-wide cleaning after Field.clean() been |
|
259 called on every field. Any ValidationError raised by this method will |
|
260 not be associated with a particular field; it will have a special-case |
|
261 association with the field named '__all__'. |
|
262 """ |
|
263 return self.cleaned_data |
|
264 |
|
265 def has_changed(self): |
|
266 """ |
|
267 Returns True if data differs from initial. |
|
268 """ |
|
269 return bool(self.changed_data) |
|
270 |
|
271 def _get_changed_data(self): |
|
272 if self._changed_data is None: |
|
273 self._changed_data = [] |
|
274 # XXX: For now we're asking the individual widgets whether or not the |
|
275 # data has changed. It would probably be more efficient to hash the |
|
276 # initial data, store it in a hidden field, and compare a hash of the |
|
277 # submitted data, but we'd need a way to easily get the string value |
|
278 # for a given field. Right now, that logic is embedded in the render |
|
279 # method of each widget. |
|
280 for name, field in self.fields.items(): |
|
281 prefixed_name = self.add_prefix(name) |
|
282 data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name) |
|
283 if not field.show_hidden_initial: |
|
284 initial_value = self.initial.get(name, field.initial) |
|
285 else: |
|
286 initial_prefixed_name = self.add_initial_prefix(name) |
|
287 hidden_widget = field.hidden_widget() |
|
288 initial_value = hidden_widget.value_from_datadict( |
|
289 self.data, self.files, initial_prefixed_name) |
|
290 if field.widget._has_changed(initial_value, data_value): |
|
291 self._changed_data.append(name) |
|
292 return self._changed_data |
|
293 changed_data = property(_get_changed_data) |
|
294 |
|
295 def _get_media(self): |
|
296 """ |
|
297 Provide a description of all media required to render the widgets on this form |
|
298 """ |
|
299 media = Media() |
|
300 for field in self.fields.values(): |
|
301 media = media + field.widget.media |
|
302 return media |
|
303 media = property(_get_media) |
|
304 |
|
305 def is_multipart(self): |
|
306 """ |
|
307 Returns True if the form needs to be multipart-encrypted, i.e. it has |
|
308 FileInput. Otherwise, False. |
|
309 """ |
|
310 for field in self.fields.values(): |
|
311 if field.widget.needs_multipart_form: |
|
312 return True |
|
313 return False |
|
314 |
|
315 def hidden_fields(self): |
|
316 """ |
|
317 Returns a list of all the BoundField objects that are hidden fields. |
|
318 Useful for manual form layout in templates. |
|
319 """ |
|
320 return [field for field in self if field.is_hidden] |
|
321 |
|
322 def visible_fields(self): |
|
323 """ |
|
324 Returns a list of BoundField objects that aren't hidden fields. |
|
325 The opposite of the hidden_fields() method. |
|
326 """ |
|
327 return [field for field in self if not field.is_hidden] |
|
328 |
|
329 class Form(BaseForm): |
|
330 "A collection of Fields, plus their associated data." |
|
331 # This is a separate class from BaseForm in order to abstract the way |
|
332 # self.fields is specified. This class (Form) is the one that does the |
|
333 # fancy metaclass stuff purely for the semantic sugar -- it allows one |
|
334 # to define a form using declarative syntax. |
|
335 # BaseForm itself has no way of designating self.fields. |
|
336 __metaclass__ = DeclarativeFieldsMetaclass |
|
337 |
|
338 class BoundField(StrAndUnicode): |
|
339 "A Field plus data" |
|
340 def __init__(self, form, field, name): |
|
341 self.form = form |
|
342 self.field = field |
|
343 self.name = name |
|
344 self.html_name = form.add_prefix(name) |
|
345 self.html_initial_name = form.add_initial_prefix(name) |
|
346 if self.field.label is None: |
|
347 self.label = pretty_name(name) |
|
348 else: |
|
349 self.label = self.field.label |
|
350 self.help_text = field.help_text or '' |
|
351 |
|
352 def __unicode__(self): |
|
353 """Renders this field as an HTML widget.""" |
|
354 if self.field.show_hidden_initial: |
|
355 return self.as_widget() + self.as_hidden(only_initial=True) |
|
356 return self.as_widget() |
|
357 |
|
358 def _errors(self): |
|
359 """ |
|
360 Returns an ErrorList for this field. Returns an empty ErrorList |
|
361 if there are none. |
|
362 """ |
|
363 return self.form.errors.get(self.name, self.form.error_class()) |
|
364 errors = property(_errors) |
|
365 |
|
366 def as_widget(self, widget=None, attrs=None, only_initial=False): |
|
367 """ |
|
368 Renders the field by rendering the passed widget, adding any HTML |
|
369 attributes passed as attrs. If no widget is specified, then the |
|
370 field's default widget will be used. |
|
371 """ |
|
372 if not widget: |
|
373 widget = self.field.widget |
|
374 attrs = attrs or {} |
|
375 auto_id = self.auto_id |
|
376 if auto_id and 'id' not in attrs and 'id' not in widget.attrs: |
|
377 attrs['id'] = auto_id |
|
378 if not self.form.is_bound: |
|
379 data = self.form.initial.get(self.name, self.field.initial) |
|
380 if callable(data): |
|
381 data = data() |
|
382 else: |
|
383 if isinstance(self.field, FileField) and self.data is None: |
|
384 data = self.form.initial.get(self.name, self.field.initial) |
|
385 else: |
|
386 data = self.data |
|
387 if not only_initial: |
|
388 name = self.html_name |
|
389 else: |
|
390 name = self.html_initial_name |
|
391 return widget.render(name, data, attrs=attrs) |
|
392 |
|
393 def as_text(self, attrs=None, **kwargs): |
|
394 """ |
|
395 Returns a string of HTML for representing this as an <input type="text">. |
|
396 """ |
|
397 return self.as_widget(TextInput(), attrs, **kwargs) |
|
398 |
|
399 def as_textarea(self, attrs=None, **kwargs): |
|
400 "Returns a string of HTML for representing this as a <textarea>." |
|
401 return self.as_widget(Textarea(), attrs, **kwargs) |
|
402 |
|
403 def as_hidden(self, attrs=None, **kwargs): |
|
404 """ |
|
405 Returns a string of HTML for representing this as an <input type="hidden">. |
|
406 """ |
|
407 return self.as_widget(self.field.hidden_widget(), attrs, **kwargs) |
|
408 |
|
409 def _data(self): |
|
410 """ |
|
411 Returns the data for this BoundField, or None if it wasn't given. |
|
412 """ |
|
413 return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) |
|
414 data = property(_data) |
|
415 |
|
416 def label_tag(self, contents=None, attrs=None): |
|
417 """ |
|
418 Wraps the given contents in a <label>, if the field has an ID attribute. |
|
419 Does not HTML-escape the contents. If contents aren't given, uses the |
|
420 field's HTML-escaped label. |
|
421 |
|
422 If attrs are given, they're used as HTML attributes on the <label> tag. |
|
423 """ |
|
424 contents = contents or conditional_escape(self.label) |
|
425 widget = self.field.widget |
|
426 id_ = widget.attrs.get('id') or self.auto_id |
|
427 if id_: |
|
428 attrs = attrs and flatatt(attrs) or '' |
|
429 contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents)) |
|
430 return mark_safe(contents) |
|
431 |
|
432 def _is_hidden(self): |
|
433 "Returns True if this BoundField's widget is hidden." |
|
434 return self.field.widget.is_hidden |
|
435 is_hidden = property(_is_hidden) |
|
436 |
|
437 def _auto_id(self): |
|
438 """ |
|
439 Calculates and returns the ID attribute for this BoundField, if the |
|
440 associated Form has specified auto_id. Returns an empty string otherwise. |
|
441 """ |
|
442 auto_id = self.form.auto_id |
|
443 if auto_id and '%s' in smart_unicode(auto_id): |
|
444 return smart_unicode(auto_id) % self.html_name |
|
445 elif auto_id: |
|
446 return self.html_name |
|
447 return '' |
|
448 auto_id = property(_auto_id) |