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 _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.
140 top_errors
.extend([u
'(Hidden field %s) %s' % (name
, force_unicode(e
)) for e
in bf_errors
])
141 hidden_fields
.append(unicode(bf
))
143 if errors_on_separate_row
and bf_errors
:
144 output
.append(error_row
% force_unicode(bf_errors
))
146 label
= escape(force_unicode(bf
.label
))
147 # Only add the suffix if the label does not end in
149 if self
.label_suffix
:
150 if label
[-1] not in ':?.!':
151 label
+= self
.label_suffix
152 label
= bf
.label_tag(label
) or ''
156 help_text
= help_text_html
% force_unicode(field
.help_text
)
159 output
.append(normal_row
% {'errors': force_unicode(bf_errors
), 'label': force_unicode(label
), 'field': unicode(bf
), 'help_text': help_text
})
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
)
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
170 # If there aren't any rows in the output, just append the
172 output
.append(str_hidden
)
173 return mark_safe(u
'\n'.join(output
))
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)
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)
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
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
200 self
._errors
= ErrorDict()
201 if not self
.is_bound
: # Stop further processing.
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():
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
))
214 if isinstance(field
, FileField
):
215 initial
= self
.initial
.get(name
, field
.initial
)
216 value
= field
.clean(value
, initial
)
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
]
228 self
.cleaned_data
= self
.clean()
229 except ValidationError
, e
:
230 self
._errors
[NON_FIELD_ERRORS
] = e
.messages
232 delattr(self
, 'cleaned_data')
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
272 for field
in self
.fields
.values():
273 media
= media
+ field
.widget
.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
:
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
):
298 def __init__(self
, form
, field
, name
):
302 self
.html_name
= form
.add_prefix(name
)
303 if self
.field
.label
is None:
304 self
.label
= pretty_name(name
)
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()
315 Returns an ErrorList for this field. Returns an empty ErrorList
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.
328 widget
= self
.field
.widget
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
)
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
)
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
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
)
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
394 return self
.html_name
396 auto_id
= property(_auto_id
)