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):
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
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.
43
for base in bases[::-1]:
44
if hasattr(base, 'base_fields'):
45
fields = base.base_fields.items() + fields
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):
55
Metaclass that converts Field attributes to a dictionary called
56
'base_fields', taking into account parent class 'base_fields' as well.
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)
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
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
91
self.fields = deepcopy(self.base_fields)
93
def __unicode__(self):
94
return self.as_table()
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."
103
field = self.fields[name]
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:
113
errors = property(_get_errors)
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
127
Subclasses may wish to override.
129
return self.prefix and ('%s-%s' % (self.prefix, field_name)) or field_name
131
def add_initial_prefix(self, field_name):
133
Add a 'initial' prefix for checking dynamic initial values
135
return u'initial-%s' % self.add_prefix(field_name)
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([escape(error) for error in bf.errors]) # Escape and cache in local variable.
146
top_errors.extend([u'(Hidden field %s) %s' % (name, force_unicode(e)) for e in bf_errors])
147
hidden_fields.append(unicode(bf))
149
if errors_on_separate_row and bf_errors:
150
output.append(error_row % force_unicode(bf_errors))
152
label = escape(force_unicode(bf.label))
153
# Only add the suffix if the label does not end in
155
if self.label_suffix:
156
if label[-1] not in ':?.!':
157
label += self.label_suffix
158
label = bf.label_tag(label) or ''
162
help_text = help_text_html % force_unicode(field.help_text)
165
output.append(normal_row % {'errors': force_unicode(bf_errors), 'label': force_unicode(label), 'field': unicode(bf), 'help_text': help_text})
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)
171
last_row = output[-1]
172
# Chop off the trailing row_ender (e.g. '</td></tr>') and
173
# insert the hidden fields.
174
output[-1] = last_row[:-len(row_ender)] + str_hidden + row_ender
176
# If there aren't any rows in the output, just append the
178
output.append(str_hidden)
179
return mark_safe(u'\n'.join(output))
182
"Returns this form rendered as HTML <tr>s -- excluding the <table></table>."
183
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)
186
"Returns this form rendered as HTML <li>s -- excluding the <ul></ul>."
187
return self._html_output(u'<li>%(errors)s%(label)s %(field)s%(help_text)s</li>', u'<li>%s</li>', '</li>', u' %s', False)
190
"Returns this form rendered as HTML <p>s."
191
return self._html_output(u'<p>%(label)s %(field)s%(help_text)s</p>', u'%s', '</p>', u' %s', True)
193
def non_field_errors(self):
195
Returns an ErrorList of errors that aren't associated with a particular
196
field -- i.e., from Form.clean(). Returns an empty ErrorList if there
199
return self.errors.get(NON_FIELD_ERRORS, self.error_class())
201
def full_clean(self):
203
Cleans all of self.data and populates self._errors and
206
self._errors = ErrorDict()
207
if not self.is_bound: # Stop further processing.
209
self.cleaned_data = {}
210
# If the form is permitted to be empty, and none of the form data has
211
# changed from the initial data, short circuit any validation.
212
if self.empty_permitted and not self.has_changed():
214
for name, field in self.fields.items():
215
# value_from_datadict() gets the data from the data dictionaries.
216
# Each widget type knows how to retrieve its own data, because some
217
# widgets split data over several HTML fields.
218
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
220
if isinstance(field, FileField):
221
initial = self.initial.get(name, field.initial)
222
value = field.clean(value, initial)
224
value = field.clean(value)
225
self.cleaned_data[name] = value
226
if hasattr(self, 'clean_%s' % name):
227
value = getattr(self, 'clean_%s' % name)()
228
self.cleaned_data[name] = value
229
except ValidationError, e:
230
self._errors[name] = e.messages
231
if name in self.cleaned_data:
232
del self.cleaned_data[name]
234
self.cleaned_data = self.clean()
235
except ValidationError, e:
236
self._errors[NON_FIELD_ERRORS] = e.messages
238
delattr(self, 'cleaned_data')
242
Hook for doing any extra form-wide cleaning after Field.clean() been
243
called on every field. Any ValidationError raised by this method will
244
not be associated with a particular field; it will have a special-case
245
association with the field named '__all__'.
247
return self.cleaned_data
249
def has_changed(self):
251
Returns True if data differs from initial.
253
return bool(self.changed_data)
255
def _get_changed_data(self):
256
if self._changed_data is None:
257
self._changed_data = []
258
# XXX: For now we're asking the individual widgets whether or not the
259
# data has changed. It would probably be more efficient to hash the
260
# initial data, store it in a hidden field, and compare a hash of the
261
# submitted data, but we'd need a way to easily get the string value
262
# for a given field. Right now, that logic is embedded in the render
263
# method of each widget.
264
for name, field in self.fields.items():
265
prefixed_name = self.add_prefix(name)
266
data_value = field.widget.value_from_datadict(self.data, self.files, prefixed_name)
267
if not field.show_hidden_initial:
268
initial_value = self.initial.get(name, field.initial)
270
initial_prefixed_name = self.add_initial_prefix(name)
271
hidden_widget = field.hidden_widget()
272
initial_value = hidden_widget.value_from_datadict(
273
self.data, self.files, initial_prefixed_name)
274
if field.widget._has_changed(initial_value, data_value):
275
self._changed_data.append(name)
276
return self._changed_data
277
changed_data = property(_get_changed_data)
279
def _get_media(self):
281
Provide a description of all media required to render the widgets on this form
284
for field in self.fields.values():
285
media = media + field.widget.media
287
media = property(_get_media)
289
def is_multipart(self):
291
Returns True if the form needs to be multipart-encrypted, i.e. it has
292
FileInput. Otherwise, False.
294
for field in self.fields.values():
295
if field.widget.needs_multipart_form:
299
class Form(BaseForm):
300
"A collection of Fields, plus their associated data."
301
# This is a separate class from BaseForm in order to abstract the way
302
# self.fields is specified. This class (Form) is the one that does the
303
# fancy metaclass stuff purely for the semantic sugar -- it allows one
304
# to define a form using declarative syntax.
305
# BaseForm itself has no way of designating self.fields.
306
__metaclass__ = DeclarativeFieldsMetaclass
308
class BoundField(StrAndUnicode):
310
def __init__(self, form, field, name):
314
self.html_name = form.add_prefix(name)
315
self.html_initial_name = form.add_initial_prefix(name)
316
if self.field.label is None:
317
self.label = pretty_name(name)
319
self.label = self.field.label
320
self.help_text = field.help_text or ''
322
def __unicode__(self):
323
"""Renders this field as an HTML widget."""
324
if self.field.show_hidden_initial:
325
return self.as_widget() + self.as_hidden(only_initial=True)
326
return self.as_widget()
330
Returns an ErrorList for this field. Returns an empty ErrorList
333
return self.form.errors.get(self.name, self.form.error_class())
334
errors = property(_errors)
336
def as_widget(self, widget=None, attrs=None, only_initial=False):
338
Renders the field by rendering the passed widget, adding any HTML
339
attributes passed as attrs. If no widget is specified, then the
340
field's default widget will be used.
343
widget = self.field.widget
345
auto_id = self.auto_id
346
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
347
attrs['id'] = auto_id
348
if not self.form.is_bound:
349
data = self.form.initial.get(self.name, self.field.initial)
355
name = self.html_name
357
name = self.html_initial_name
358
return widget.render(name, data, attrs=attrs)
360
def as_text(self, attrs=None, **kwargs):
362
Returns a string of HTML for representing this as an <input type="text">.
364
return self.as_widget(TextInput(), attrs, **kwargs)
366
def as_textarea(self, attrs=None, **kwargs):
367
"Returns a string of HTML for representing this as a <textarea>."
368
return self.as_widget(Textarea(), attrs, **kwargs)
370
def as_hidden(self, attrs=None, **kwargs):
372
Returns a string of HTML for representing this as an <input type="hidden">.
374
return self.as_widget(self.field.hidden_widget(), attrs, **kwargs)
378
Returns the data for this BoundField, or None if it wasn't given.
380
return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name)
381
data = property(_data)
383
def label_tag(self, contents=None, attrs=None):
385
Wraps the given contents in a <label>, if the field has an ID attribute.
386
Does not HTML-escape the contents. If contents aren't given, uses the
387
field's HTML-escaped label.
389
If attrs are given, they're used as HTML attributes on the <label> tag.
391
contents = contents or escape(self.label)
392
widget = self.field.widget
393
id_ = widget.attrs.get('id') or self.auto_id
395
attrs = attrs and flatatt(attrs) or ''
396
contents = u'<label for="%s"%s>%s</label>' % (widget.id_for_label(id_), attrs, unicode(contents))
397
return mark_safe(contents)
399
def _is_hidden(self):
400
"Returns True if this BoundField's widget is hidden."
401
return self.field.widget.is_hidden
402
is_hidden = property(_is_hidden)
406
Calculates and returns the ID attribute for this BoundField, if the
407
associated Form has specified auto_id. Returns an empty string otherwise.
409
auto_id = self.form.auto_id
410
if auto_id and '%s' in smart_unicode(auto_id):
411
return smart_unicode(auto_id) % self.html_name
413
return self.html_name
415
auto_id = property(_auto_id)