Django

Code

root/django/branches/newforms-admin/django/oldforms/__init__.py

Revision 7881, 44.1 kB (checked in by brosner, 5 months ago)

newforms-admin: Merged from trunk up to [7877].

  • Property svn:eol-style set to native
  • Property svn:keywords set to LastChangedRevision
Line 
1 from django.core import validators
2 from django.core.exceptions import PermissionDenied
3 from django.utils.html import escape
4 from django.utils.safestring import mark_safe
5 from django.conf import settings
6 from django.utils.translation import ugettext, ungettext
7 from django.utils.encoding import smart_unicode, force_unicode
8 from django.utils.maxlength import LegacyMaxlength
9
10 FORM_FIELD_ID_PREFIX = 'id_'
11
12 class EmptyValue(Exception):
13     "This is raised when empty data is provided"
14     pass
15
16 class Manipulator(object):
17     # List of permission strings. User must have at least one to manipulate.
18     # None means everybody has permission.
19     required_permission = ''
20
21     def __init__(self):
22         # List of FormField objects
23         self.fields = []
24
25     def __getitem__(self, field_name):
26         "Looks up field by field name; raises KeyError on failure"
27         for field in self.fields:
28             if field.field_name == field_name:
29                 return field
30         raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields))
31
32     def __delitem__(self, field_name):
33         "Deletes the field with the given field name; raises KeyError on failure"
34         for i, field in enumerate(self.fields):
35             if field.field_name == field_name:
36                 del self.fields[i]
37                 return
38         raise KeyError, "Field %s not found" % field_name
39
40     def check_permissions(self, user):
41         """Confirms user has required permissions to use this manipulator; raises
42         PermissionDenied on failure."""
43         if self.required_permission is None:
44             return
45         if user.has_perm(self.required_permission):
46             return
47         raise PermissionDenied
48
49     def prepare(self, new_data):
50         """
51         Makes any necessary preparations to new_data, in place, before data has
52         been validated.
53         """
54         for field in self.fields:
55             field.prepare(new_data)
56
57     def get_validation_errors(self, new_data):
58         "Returns dictionary mapping field_names to error-message lists"
59         errors = {}
60         self.prepare(new_data)
61         for field in self.fields:
62             errors.update(field.get_validation_errors(new_data))
63             val_name = 'validate_%s' % field.field_name
64             if hasattr(self, val_name):
65                 val = getattr(self, val_name)
66                 try:
67                     field.run_validator(new_data, val)
68                 except (validators.ValidationError, validators.CriticalValidationError), e:
69                     errors.setdefault(field.field_name, []).extend(e.messages)
70
71 #            if field.is_required and not new_data.get(field.field_name, False):
72 #                errors.setdefault(field.field_name, []).append(ugettext_lazy('This field is required.'))
73 #                continue
74 #            try:
75 #                validator_list = field.validator_list
76 #                if hasattr(self, 'validate_%s' % field.field_name):
77 #                    validator_list.append(getattr(self, 'validate_%s' % field.field_name))
78 #                for validator in validator_list:
79 #                    if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'):
80 #                        try:
81 #                            if hasattr(field, 'requires_data_list'):
82 #                                validator(new_data.getlist(field.field_name), new_data)
83 #                            else:
84 #                                validator(new_data.get(field.field_name, ''), new_data)
85 #                        except validators.ValidationError, e:
86 #                            errors.setdefault(field.field_name, []).extend(e.messages)
87 #            # If a CriticalValidationError is raised, ignore any other ValidationErrors
88 #            # for this particular field
89 #            except validators.CriticalValidationError, e:
90 #                errors.setdefault(field.field_name, []).extend(e.messages)
91         return errors
92
93     def save(self, new_data):
94         "Saves the changes and returns the new object"
95         # changes is a dictionary-like object keyed by field_name
96         raise NotImplementedError
97
98     def do_html2python(self, new_data):
99         """
100         Convert the data from HTML data types to Python datatypes, changing the
101         object in place. This happens after validation but before storage. This
102         must happen after validation because html2python functions aren't
103         expected to deal with invalid input.
104         """
105         for field in self.fields:
106             field.convert_post_data(new_data)
107
108 class FormWrapper(object):
109     """
110     A wrapper linking a Manipulator to the template system.
111     This allows dictionary-style lookups of formfields. It also handles feeding
112     prepopulated data and validation error messages to the formfield objects.
113     """
114     def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
115         self.manipulator = manipulator
116         if data is None:
117             data = {}
118         if error_dict is None:
119             error_dict = {}
120         self.data = data
121         self.error_dict = error_dict
122         self._inline_collections = None
123         self.edit_inline = edit_inline
124
125     def __repr__(self):
126         return repr(self.__dict__)
127
128     def __getitem__(self, key):
129         for field in self.manipulator.fields:
130             if field.field_name == key:
131                 data = field.extract_data(self.data)
132                 return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
133         if self.edit_inline:
134             self.fill_inline_collections()
135             for inline_collection in self._inline_collections:
136                 # The 'orig_name' comparison is for backwards compatibility
137                 # with hand-crafted forms.
138                 if inline_collection.name == key or (':' not in key and inline_collection.orig_name == key):
139                     return inline_collection
140         raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key
141
142     def fill_inline_collections(self):
143         if not self._inline_collections:
144             ic = []
145             related_objects = self.manipulator.get_related_objects()
146             for rel_obj in related_objects:
147                 data = rel_obj.extract_data(self.data)
148                 inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
149                 ic.append(inline_collection)
150             self._inline_collections = ic
151
152     def has_errors(self):
153         return self.error_dict != {}
154
155     def _get_fields(self):
156         try:
157             return self._fields
158         except AttributeError:
159             self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields]
160             return self._fields
161
162     fields = property(_get_fields)
163
164 class FormFieldWrapper(object):
165     "A bridge between the template system and an individual form field. Used by FormWrapper."
166     def __init__(self, formfield, data, error_list):
167         self.formfield, self.data, self.error_list = formfield, data, error_list
168         self.field_name = self.formfield.field_name # for convenience in templates
169
170     def __str__(self):
171         "Renders the field"
172         return unicode(self).encode('utf-8')
173
174     def __unicode__(self):
175         "Renders the field"
176         return force_unicode(self.formfield.render(self.data))
177
178     def __repr__(self):
179         return '<FormFieldWrapper for "%s">' % self.formfield.field_name
180
181     def field_list(self):
182         """
183         Like __str__(), but returns a list. Use this when the field's render()
184         method returns a list.
185         """
186         return self.formfield.render(self.data)
187
188     def errors(self):
189         return self.error_list
190
191     def html_error_list(self):
192         if self.errors():
193             return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
194         else:
195             return mark_safe('')
196
197     def get_id(self):
198         return self.formfield.get_id()
199
200 class FormFieldCollection(FormFieldWrapper):
201     "A utility class that gives the template access to a dict of FormFieldWrappers"
202     def __init__(self, formfield_dict):
203         self.formfield_dict = formfield_dict
204
205     def __str__(self):
206         return unicode(self).encode('utf-8')
207
208     def __unicode__(self):
209         return unicode(self.formfield_dict)
210
211     def __getitem__(self, template_key):
212         "Look up field by template key; raise KeyError on failure"
213         return self.formfield_dict[template_key]
214
215     def __repr__(self):
216         return "<FormFieldCollection: %s>" % self.formfield_dict
217
218     def errors(self):
219         "Returns list of all errors in this collection's formfields"
220         errors = []
221         for field in self.formfield_dict.values():
222             if hasattr(field, 'errors'):
223                 errors.extend(field.errors())
224         return errors
225
226     def has_errors(self):
227         return bool(len(self.errors()))
228
229     def html_combined_error_list(self):
230         return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
231
232 class InlineObjectCollection(object):
233     "An object that acts like a sparse list of form field collections."
234     def __init__(self, parent_manipulator, rel_obj, data, errors):
235         self.parent_manipulator = parent_manipulator
236         self.rel_obj = rel_obj
237         self.data = data
238         self.errors = errors
239         self._collections = None
240         self.name = rel_obj.name
241         # This is the name used prior to fixing #1839. Needs for backwards
242         # compatibility.
243         self.orig_name = rel_obj.opts.module_name
244
245     def __len__(self):
246         self.fill()
247         return self._collections.__len__()
248
249     def __getitem__(self, k):
250         self.fill()
251         return self._collections.__getitem__(k)
252
253     def __setitem__(self, k, v):
254         self.fill()
255         return self._collections.__setitem__(k,v)
256
257     def __delitem__(self, k):
258         self.fill()
259         return self._collections.__delitem__(k)
260
261     def __iter__(self):
262         self.fill()
263         return iter(self._collections.values())
264
265     def items(self):
266         self.fill()
267         return self._collections.items()
268
269     def fill(self):
270         if self._collections:
271             return
272         else:
273             var_name = self.rel_obj.opts.object_name.lower()
274             collections = {}
275             orig = None
276             if hasattr(self.parent_manipulator, 'original_object'):
277                 orig = self.parent_manipulator.original_object
278             orig_list = self.rel_obj.get_list(orig)
279
280             for i, instance in enumerate(orig_list):
281                 collection = {'original': instance}
282                 for f in self.rel_obj.editable_fields():
283                     for field_name in f.get_manipulator_field_names(''):
284                         full_field_name = '%s.%d.%s' % (var_name, i, field_name)
285                         field = self.parent_manipulator[full_field_name]
286                         data = field.extract_data(self.data)
287                         errors = self.errors.get(full_field_name, [])
288                         collection[field_name] = FormFieldWrapper(field, data, errors)
289                 collections[i] = FormFieldCollection(collection)
290             self._collections = collections
291
292
293 class FormField(object):
294     """Abstract class representing a form field.
295
296     Classes that extend FormField should define the following attributes:
297         field_name
298             The field's name for use by programs.
299         validator_list
300             A list of validation tests (callback functions) that the data for
301             this field must pass in order to be added or changed.
302         is_required
303             A Boolean. Is it a required field?
304     Subclasses should also implement a render(data) method, which is responsible
305     for rending the form field in XHTML.
306     """
307     # Provide backwards compatibility for the maxlength attribute and
308     # argument for this class and all subclasses.
309     __metaclass__ = LegacyMaxlength
310
311     def __str__(self):
312         return unicode(self).encode('utf-8')
313
314     def __unicode__(self):
315         return self.render(u'')
316
317     def __repr__(self):
318         return 'FormField "%s"' % self.field_name
319
320     def prepare(self, new_data):
321         "Hook for doing something to new_data (in place) before validation."
322         pass
323
324     def html2python(data):
325         "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type"
326         return data
327     html2python = staticmethod(html2python)
328
329     def render(self, data):
330         raise NotImplementedError
331
332     def get_member_name(self):
333         if hasattr(self, 'member_name'):
334             return self.member_name
335         else:
336             return self.field_name
337
338     def extract_data(self, data_dict):
339         if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
340             data = data_dict.getlist(self.get_member_name())
341         else:
342             data = data_dict.get(self.get_member_name(), None)
343         if data is None:
344             data = ''
345         return data
346
347     def convert_post_data(self, new_data):
348         name = self.get_member_name()
349         if self.field_name in new_data:
350             d = new_data.getlist(self.field_name)
351             try:
352                 converted_data = [self.__class__.html2python(data) for data in d]
353             except ValueError:
354                 converted_data = d
355             new_data.setlist(name, converted_data)
356         else:
357             try:
358                 #individual fields deal with None values themselves
359                 new_data.setlist(name, [self.__class__.html2python(None)])
360             except EmptyValue:
361                 new_data.setlist(name, [])
362
363
364     def run_validator(self, new_data, validator):
365         if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'):
366             if hasattr(self, 'requires_data_list'):
367                 validator(new_data.getlist(self.field_name), new_data)
368             else:
369                 validator(new_data.get(self.field_name, ''), new_data)
370
371     def get_validation_errors(self, new_data):
372         errors = {}
373         if self.is_required and not new_data.get(self.field_name, False):
374             errors.setdefault(self.field_name, []).append(ugettext('This field is required.'))
375             return errors
376         try:
377             for validator in self.validator_list:
378                 try:
379                     self.run_validator(new_data, validator)
380                 except validators.ValidationError, e:
381                     errors.setdefault(self.field_name, []).extend(e.messages)
382         # If a CriticalValidationError is raised, ignore any other ValidationErrors
383         # for this particular field
384         except validators.CriticalValidationError, e:
385             errors.setdefault(self.field_name, []).extend(e.messages)
386         return errors
387
388     def get_id(self):
389         "Returns the HTML 'id' attribute for this form field."
390         return FORM_FIELD_ID_PREFIX + self.field_name
391
392 ####################
393 # GENERIC WIDGETS  #
394 ####################
395
396 class TextField(FormField):
397     input_type = "text"
398     def __init__(self, field_name, length=30, max_length=None, is_required=False, validator_list=None, member_name=None):
399         if validator_list is None: validator_list = []
400         self.field_name = field_name
401         self.length, self.max_length = length, max_length
402         self.is_required = is_required
403         self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
404         if member_name != None:
405             self.member_name = member_name
406
407     def isValidLength(self, data, form):
408         if data and self.max_length and len(smart_unicode(data)) > self.max_length:
409             raise validators.ValidationError, ungettext("Ensure your text is less than %s character.",
410                 "Ensure your text is less than %s characters.", self.max_length) % self.max_length
411
412     def hasNoNewlines(self, data, form):
413         if data and '\n' in data:
414             raise validators.ValidationError, ugettext("Line breaks are not allowed here.")
415
416     def render(self, data):
417         if data is None:
418             data = u''
419         max_length = u''
420         if self.max_length:
421             max_length = u'maxlength="%s" ' % self.max_length
422         return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
423             (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '',
424             self.field_name, self.length, escape(data), max_length))
425
426     def html2python(data):
427         return data
428     html2python = staticmethod(html2python)
429
430 class PasswordField(TextField):
431     input_type = "password"
432
433 class LargeTextField(TextField):
434     def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, max_length=None):
435         if validator_list is None: validator_list = []
436         self.field_name = field_name
437         self.rows, self.cols, self.is_required = rows, cols, is_required
438         self.validator_list = validator_list[:]
439         if max_length:
440             self.validator_list.append(self.isValidLength)
441             self.max_length = max_length
442
443     def render(self, data):
444         if data is None:
445             data = ''
446         return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
447             (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
448             self.field_name, self.rows, self.cols, escape(data)))
449
450 class HiddenField(FormField):
451     def __init__(self, field_name, is_required=False, validator_list=None, max_length=None):
452         if validator_list is None: validator_list = []
453         self.field_name, self.is_required = field_name, is_required
454         self.validator_list = validator_list[:]
455
456     def render(self, data):
457         return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
458             (self.get_id(), self.field_name, escape(data)))
459
460 class CheckboxField(FormField):
461     def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
462         if validator_list is None: validator_list = []
463         self.field_name = field_name
464         self.checked_by_default = checked_by_default
465         self.is_required = is_required
466         self.validator_list = validator_list[:]
467
468     def render(self, data):
469         checked_html = ''
470         if data or (data is '' and self.checked_by_default):
471             checked_html = ' checked="checked"'
472         return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
473             (self.get_id(), self.__class__.__name__,
474             self.field_name, checked_html))
475
476     def html2python(data):
477         "Convert value from browser ('on' or '') to a Python boolean"
478         if data == 'on':
479             return True
480         return False
481     html2python = staticmethod(html2python)
482
483 class SelectField(FormField):
484     def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
485         if validator_list is None: validator_list = []
486         if choices is None: choices = []
487         choices = [(k, smart_unicode(v, strings_only=True)) for k, v in choices]
488         self.field_name = field_name
489         # choices is a list of (value, human-readable key) tuples because order matters
490         self.choices, self.size, self.is_required = choices, size, is_required
491         self.validator_list = [self.isValidChoice] + validator_list
492         if member_name != None:
493             self.member_name = member_name
494
495     def render(self, data):
496         output = [u'<select id="%s" class="v%s%s" name="%s" size="%s">' % \
497             (self.get_id(), self.__class__.__name__,
498              self.is_required and u' required'