Fixed the admin_scripts tests to check for the right output with Python 2.4.
[django.git] / django / forms / forms.py
blob753ee254bca6055ad90ab158d3f2f1a23908f8fe
1 """
2 Form classes
3 """
5 from copy import deepcopy
7 from django.utils.datastructures import SortedDict
8 from django.utils.html import escape
9 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
10 from django.utils.safestring import mark_safe
12 from fields import Field, FileField
13 from widgets import Media, media_property, TextInput, Textarea
14 from util import flatatt, ErrorDict, ErrorList, ValidationError
16 __all__ = ('BaseForm', 'Form')
18 NON_FIELD_ERRORS = '__all__'
20 def pretty_name(name):
21 "Converts 'first_name' to 'First name'"
22 name = name[0].upper() + name[1:]
23 return name.replace('_', ' ')
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.
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))
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
51 return SortedDict(fields)
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
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
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)
93 def __unicode__(self):
94 return self.as_table()
96 def __iter__(self):
97 for name, field in self.fields.items():
98 yield BoundField(self, field, name)
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)
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)
115 def is_valid(self):
117 Returns True if the form has no errors. Otherwise, False. If errors are
118 being ignored, returns False.
120 return self.is_bound and not bool(self.errors)
122 def add_prefix(self, field_name):
124 Returns the field name with a prefix appended, if this Form has a
125 prefix set.
127 Subclasses may wish to override.
129 return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
131 def _html_output(self, normal_row, error_row, row_ender, help_text_html, errors_on_separate_row):
132 "Helper function for outputting HTML. Used by as_table(), as_ul(), as_p()."
133 top_errors = self.non_field_errors() # Errors that should be displayed above all fields.
134 output, hidden_fields = [], []
135 for name, field in self.fields.items():
136 bf = BoundField(self, field, name)
137 bf_errors = self.error_class([escape(error) for error in bf.errors]) # Escape and cache in local variable.
138 if bf.is_hidden:
139 if bf_errors:
140 top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
141 hidden_fields.append(unicode(bf))
142 else:
143 if errors_on_separate_row and bf_errors:
144 output.append(error_row % force_unicode(bf_errors))
145 if bf.label:
146 label = escape(force_unicode(bf.label))
147 # Only add the suffix if the label does not end in
148 # punctuation.
149 if self.label_suffix:
150 if label[-1] not in ':?.!':
151 label += self.label_suffix
152 label = bf.label_tag(label) or ''
153 else:
154 label = ''
155 if field.help_text:
156 help_text = help_text_html % force_unicode(field.help_text)
157 else:
158 help_text = u''
159 output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
160 if top_errors:
161 output.insert(0, error_row % force_unicode(top_errors))
162 if hidden_fields: # Insert any hidden fields in the last row.
163 str_hidden = u''.join(hidden_fields)
164 if output:
165 last_row = output[-1]
166 # Chop off the trailing row_ender (e.g. '</td></tr>') and
167 # insert the hidden fields.
168 output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
169 else:
170 # If there aren't any rows in the output, just append the
171 # hidden fields.
172 output.append(str_hidden)
173 return mark_safe(u'\n'.join(output))
175 def as_table(self):
176 "Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
177 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)
179 def as_ul(self):
180 "Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
181 return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
183 def as_p(self):
184 "Returns this form rendered as HTML <p>s."
185 return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
187 def non_field_errors(self):
189 Returns an ErrorList of errors that aren't associated with a particular
190 field -- i.e., from Form.clean(). Returns an empty ErrorList if there
191 are none.
193 return self.errors.get(NON_FIELD_ERRORS, self.error_class())
195 def full_clean(self):
197 Cleans all of self.data and populates self._errors and
198 self.cleaned_data.
200 self._errors = ErrorDict()
201 if not self.is_bound: # Stop further processing.
202 return
203 self.cleaned_data = {}
204 # If the form is permitted to be empty, and none of the form data has
205 # changed from the initial data, short circuit any validation.
206 if self.empty_permitted and not self.has_changed():
207 return
208 for name, field in self.fields.items():
209 # value_from_datadict() gets the data from the data dictionaries.
210 # Each widget type knows how to retrieve its own data, because some
211 # widgets split data over several HTML fields.
212 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
213 try:
214 if isinstance(field, FileField):
215 initial = self.initial.get(name, field.initial)
216 value = field.clean(value, initial)
217 else:
218 value = field.clean(value)
219 self.cleaned_data[name] = value
220 if hasattr(self, 'clean_%s' % name):
221 value = getattr(self, 'clean_%s' % name)()
222 self.cleaned_data[name] = value
223 except ValidationError, e:
224 self._errors[name] = e.messages
225 if name in self.cleaned_data:
226 del self.cleaned_data[name]
227 try:
228 self.cleaned_data = self.clean()
229 except ValidationError, e:
230 self._errors[NON_FIELD_ERRORS] = e.messages
231 if self._errors:
232 delattr(self, 'cleaned_data')
234 def clean(self):
236 Hook for doing any extra form-wide cleaning after Field.clean() been
237 called on every field. Any ValidationError raised by this method will
238 not be associated with a particular field; it will have a special-case
239 association with the field named '__all__'.
241 return self.cleaned_data
243 def has_changed(self):
245 Returns True if data differs from initial.
247 return bool(self.changed_data)
249 def _get_changed_data(self):
250 if self._changed_data is None:
251 self._changed_data = []
252 # XXX: For now we're asking the individual widgets whether or not the
253 # data has changed. It would probably be more efficient to hash the
254 # initial data, store it in a hidden field, and compare a hash of the
255 # submitted data, but we'd need a way to easily get the string value
256 # for a given field. Right now, that logic is embedded in the render
257 # method of each widget.
258 for name, field in self.fields.items():
259 prefixed_name = self.add_prefix(name)
260 data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
261 initial_value = self.initial.get(name, field.initial)
262 if field.widget._has_changed(initial_value, data_value):
263 self._changed_data.append(name)
264 return self._changed_data
265 changed_data = property(_get_changed_data)
267 def _get_media(self):
269 Provide a description of all media required to render the widgets on this form
271 media = Media()
272 for field in self.fields.values():
273 media = media + field.widget.media
274 return media
275 media = property(_get_media)
277 def is_multipart(self):
279 Returns True if the form needs to be multipart-encrypted, i.e. it has
280 FileInput. Otherwise, False.
282 for field in self.fields.values():
283 if field.widget.needs_multipart_form:
284 return True
285 return False
287 class Form(BaseForm):
288 "A collection of Fields, plus their associated data."
289 # This is a separate class from BaseForm in order to abstract the way
290 # self.fields is specified. This class (Form) is the one that does the
291 # fancy metaclass stuff purely for the semantic sugar -- it allows one
292 # to define a form using declarative syntax.
293 # BaseForm itself has no way of designating self.fields.
294 __metaclass__ = DeclarativeFieldsMetaclass
296 class BoundField(StrAndUnicode):
297 "A Field plus data"
298 def __init__(self, form, field, name):
299 self.form = form
300 self.field = field
301 self.name = name
302 self.html_name = form.add_prefix(name)
303 if self.field.label is None:
304 self.label = pretty_name(name)
305 else:
306 self.label = self.field.label
307 self.help_text = field.help_text or ''
309 def __unicode__(self):
310 """Renders this field as an HTML widget."""
311 return self.as_widget()
313 def _errors(self):
315 Returns an ErrorList for this field. Returns an empty ErrorList
316 if there are none.
318 return self.form.errors.get(self.name, self.form.error_class())
319 errors = property(_errors)
321 def as_widget(self, widget=None, attrs=None):
323 Renders the field by rendering the passed widget, adding any HTML
324 attributes passed as attrs. If no widget is specified, then the
325 field's default widget will be used.
327 if not widget:
328 widget = self.field.widget
329 attrs = attrs or {}
330 auto_id = self.auto_id
331 if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
332 attrs['id'] = auto_id
333 if not self.form.is_bound:
334 data = self.form.initial.get(self.name, self.field.initial)
335 if callable(data):
336 data = data()
337 else:
338 data = self.data
339 return widget.render(self.html_name, data, attrs=attrs)
341 def as_text(self, attrs=None):
343 Returns a string of HTML for representing this as an <input type="text">.
345 return self.as_widget(TextInput(), attrs)
347 def as_textarea(self, attrs=None):
348 "Returns a string of HTML for representing this as a <textarea>."
349 return self.as_widget(Textarea(), attrs)
351 def as_hidden(self, attrs=None):
353 Returns a string of HTML for representing this as an <input type="hidden">.
355 return self.as_widget(self.field.hidden_widget(), attrs)
357 def _data(self):
359 Returns the data for this BoundField, or None if it wasn't given.
361 return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
362 data = property(_data)
364 def label_tag(self, contents=None, attrs=None):
366 Wraps the given contents in a <label>, if the field has an ID attribute.
367 Does not HTML-escape the contents. If contents aren't given, uses the
368 field's HTML-escaped label.
370 If attrs are given, they're used as HTML attributes on the <label> tag.
372 contents = contents or escape(self.label)
373 widget = self.field.widget
374 id_ = widget.attrs.get('id') or self.auto_id
375 if id_:
376 attrs = attrs and flatatt(attrs) or ''
377 contents = '<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, contents)
378 return mark_safe(contents)
380 def _is_hidden(self):
381 "Returns True if this BoundField's widget is hidden."
382 return self.field.widget.is_hidden
383 is_hidden = property(_is_hidden)
385 def _auto_id(self):
387 Calculates and returns the ID attribute for this BoundField, if the
388 associated Form has specified auto_id. Returns an empty string otherwise.
390 auto_id = self.form.auto_id
391 if auto_id and '%s' in smart_unicode(auto_id):
392 return smart_unicode(auto_id) % self.html_name
393 elif auto_id:
394 return self.html_name
395 return ''
396 auto_id = property(_auto_id)