Django

Code

root/django/branches/gis/django/oldforms/__init__.py

Revision 8215, 43.9 kB (checked in by jbronn, 4 months ago)

gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.

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