Added name column to all role lists.
[Melange.git] / app / django / forms / forms.py
blob3a6182611e207e2c8552a2f8aa9f114a8d674775
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 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.
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 = 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))
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)
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)
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)
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
204 are none.
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
211 self.cleaned_data.
213 self._errors = ErrorDict()
214 if not self.is_bound: # Stop further processing.
215 return
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():
220 return
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))
226 try:
227 if isinstance(field, FileField):
228 initial = self.initial.get(name, field.initial)
229 value = field.clean(value, initial)
230 else:
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]
240 try:
241 self.cleaned_data = self.clean()
242 except ValidationError, e:
243 self._errors[NON_FIELD_ERRORS] = e.messages
244 if self._errors:
245 delattr(self, 'cleaned_data')
247 def clean(self):
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)
276 else:
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
290 media = Media()
291 for field in self.fields.values():
292 media = media + field.widget.media
293 return 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:
303 return True
304 return False
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):
316 "A Field plus data"
317 def __init__(self, form, field, name):
318 self.form = form
319 self.field = field
320 self.name = 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)
325 else:
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()
335 def _errors(self):
337 Returns an ErrorList for this field. Returns an empty ErrorList
338 if there are none.
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.
349 if not widget:
350 widget = self.field.widget
351 attrs = attrs or {}
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)
357 if callable(data):
358 data = data()
359 else:
360 data = self.data
361 if not only_initial:
362 name = self.html_name
363 else:
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)
383 def _data(self):
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
401 if 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)
411 def _auto_id(self):
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
419 elif auto_id:
420 return self.html_name
421 return ''
422 auto_id = property(_auto_id)