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 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
183 # If there aren't any rows in the output, just append the
185 output
.append(str_hidden
)
186 return mark_safe(u
'\n'.join(output
))
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)
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)
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)
200 def non_field_errors(self
):
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
206 return self
.errors
.get(NON_FIELD_ERRORS
, self
.error_class())
208 def full_clean(self
):
210 Cleans all of self.data and populates self._errors and
213 self
._errors
= ErrorDict()
214 if not self
.is_bound
: # Stop further processing.
216 self
.cleaned_data
= {}
217 # If the form is permitted to be empty, and none of the form data has
218 # changed from the initial data, short circuit any validation.
219 if self
.empty_permitted
and not self
.has_changed():
221 for name
, field
in self
.fields
.items():
222 # value_from_datadict() gets the data from the data dictionaries.
223 # Each widget type knows how to retrieve its own data, because some
224 # widgets split data over several HTML fields.
225 value
= field
.widget
.value_from_datadict(self
.data
, self
.files
, self
.add_prefix(name
))
227 if isinstance(field
, FileField
):
228 initial
= self
.initial
.get(name
, field
.initial
)
229 value
= field
.clean(value
, initial
)
231 value
= field
.clean(value
)
232 self
.cleaned_data
[name
] = value
233 if hasattr(self
, 'clean_%s' % name
):
234 value
= getattr(self
, 'clean_%s' % name
)()
235 self
.cleaned_data
[name
] = value
236 except ValidationError
, e
:
237 self
._errors
[name
] = e
.messages
238 if name
in self
.cleaned_data
:
239 del self
.cleaned_data
[name
]
241 self
.cleaned_data
= self
.clean()
242 except ValidationError
, e
:
243 self
._errors
[NON_FIELD_ERRORS
] = e
.messages
245 delattr(self
, 'cleaned_data')
249 Hook for doing any extra form-wide cleaning after Field.clean() been
250 called on every field. Any ValidationError raised by this method will
251 not be associated with a particular field; it will have a special-case
252 association with the field named '__all__'.
254 return self
.cleaned_data
256 def has_changed(self
):
258 Returns True if data differs from initial.
260 return bool(self
.changed_data
)
262 def _get_changed_data(self
):
263 if self
._changed
_data
is None:
264 self
._changed
_data
= []
265 # XXX: For now we're asking the individual widgets whether or not the
266 # data has changed. It would probably be more efficient to hash the
267 # initial data, store it in a hidden field, and compare a hash of the
268 # submitted data, but we'd need a way to easily get the string value
269 # for a given field. Right now, that logic is embedded in the render
270 # method of each widget.
271 for name
, field
in self
.fields
.items():
272 prefixed_name
= self
.add_prefix(name
)
273 data_value
= field
.widget
.value_from_datadict(self
.data
, self
.files
, prefixed_name
)
274 if not field
.show_hidden_initial
:
275 initial_value
= self
.initial
.get(name
, field
.initial
)
277 initial_prefixed_name
= self
.add_initial_prefix(name
)
278 hidden_widget
= field
.hidden_widget()
279 initial_value
= hidden_widget
.value_from_datadict(
280 self
.data
, self
.files
, initial_prefixed_name
)
281 if field
.widget
._has
_changed
(initial_value
, data_value
):
282 self
._changed
_data
.append(name
)
283 return self
._changed
_data
284 changed_data
= property(_get_changed_data
)
286 def _get_media(self
):
288 Provide a description of all media required to render the widgets on this form
291 for field
in self
.fields
.values():
292 media
= media
+ field
.widget
.media
294 media
= property(_get_media
)
296 def is_multipart(self
):
298 Returns True if the form needs to be multipart-encrypted, i.e. it has
299 FileInput. Otherwise, False.
301 for field
in self
.fields
.values():
302 if field
.widget
.needs_multipart_form
:
306 class Form(BaseForm
):
307 "A collection of Fields, plus their associated data."
308 # This is a separate class from BaseForm in order to abstract the way
309 # self.fields is specified. This class (Form) is the one that does the
310 # fancy metaclass stuff purely for the semantic sugar -- it allows one
311 # to define a form using declarative syntax.
312 # BaseForm itself has no way of designating self.fields.
313 __metaclass__
= DeclarativeFieldsMetaclass
315 class BoundField(StrAndUnicode
):
317 def __init__(self
, form
, field
, name
):
321 self
.html_name
= form
.add_prefix(name
)
322 self
.html_initial_name
= form
.add_initial_prefix(name
)
323 if self
.field
.label
is None:
324 self
.label
= pretty_name(name
)
326 self
.label
= self
.field
.label
327 self
.help_text
= field
.help_text
or ''
329 def __unicode__(self
):
330 """Renders this field as an HTML widget."""
331 if self
.field
.show_hidden_initial
:
332 return self
.as_widget() + self
.as_hidden(only_initial
=True)
333 return self
.as_widget()
337 Returns an ErrorList for this field. Returns an empty ErrorList
340 return self
.form
.errors
.get(self
.name
, self
.form
.error_class())
341 errors
= property(_errors
)
343 def as_widget(self
, widget
=None, attrs
=None, only_initial
=False):
345 Renders the field by rendering the passed widget, adding any HTML
346 attributes passed as attrs. If no widget is specified, then the
347 field's default widget will be used.
350 widget
= self
.field
.widget
352 auto_id
= self
.auto_id
353 if auto_id
and 'id' not in attrs
and 'id' not in widget
.attrs
:
354 attrs
['id'] = auto_id
355 if not self
.form
.is_bound
:
356 data
= self
.form
.initial
.get(self
.name
, self
.field
.initial
)
362 name
= self
.html_name
364 name
= self
.html_initial_name
365 return widget
.render(name
, data
, attrs
=attrs
)
367 def as_text(self
, attrs
=None, **kwargs
):
369 Returns a string of HTML for representing this as an <input type="text">.
371 return self
.as_widget(TextInput(), attrs
, **kwargs
)
373 def as_textarea(self
, attrs
=None, **kwargs
):
374 "Returns a string of HTML for representing this as a <textarea>."
375 return self
.as_widget(Textarea(), attrs
, **kwargs
)
377 def as_hidden(self
, attrs
=None, **kwargs
):
379 Returns a string of HTML for representing this as an <input type="hidden">.
381 return self
.as_widget(self
.field
.hidden_widget(), attrs
, **kwargs
)
385 Returns the data for this BoundField, or None if it wasn't given.
387 return self
.field
.widget
.value_from_datadict(self
.form
.data
, self
.form
.files
, self
.html_name
)
388 data
= property(_data
)
390 def label_tag(self
, contents
=None, attrs
=None):
392 Wraps the given contents in a <label>, if the field has an ID attribute.
393 Does not HTML-escape the contents. If contents aren't given, uses the
394 field's HTML-escaped label.
396 If attrs are given, they're used as HTML attributes on the <label> tag.
398 contents
= contents
or escape(self
.label
)
399 widget
= self
.field
.widget
400 id_
= widget
.attrs
.get('id') or self
.auto_id
402 attrs
= attrs
and flatatt(attrs
) or ''
403 contents
= u
'<label for="%s"%s>%s</label>' % (widget
.id_for_label(id_
), attrs
, unicode(contents
))
404 return mark_safe(contents
)
406 def _is_hidden(self
):
407 "Returns True if this BoundField's widget is hidden."
408 return self
.field
.widget
.is_hidden
409 is_hidden
= property(_is_hidden
)
413 Calculates and returns the ID attribute for this BoundField, if the
414 associated Form has specified auto_id. Returns an empty string otherwise.
416 auto_id
= self
.form
.auto_id
417 if auto_id
and '%s' in smart_unicode(auto_id
):
418 return smart_unicode(auto_id
) % self
.html_name
420 return self
.html_name
422 auto_id
= property(_auto_id
)