App Engine Python SDK version 1.9.12
[gae.git] / python / lib / django-0.96 / django / oldforms / __init__.py
blob56101984f5332e8832ac14c49d7a7a1488a90ef0
1 from django.core import validators
2 from django.core.exceptions import PermissionDenied
3 from django.utils.html import escape
4 from django.conf import settings
5 from django.utils.translation import gettext, ngettext
7 FORM_FIELD_ID_PREFIX = 'id_'
9 class EmptyValue(Exception):
10 "This is raised when empty data is provided"
11 pass
13 class Manipulator(object):
14 # List of permission strings. User must have at least one to manipulate.
15 # None means everybody has permission.
16 required_permission = ''
18 def __init__(self):
19 # List of FormField objects
20 self.fields = []
22 def __getitem__(self, field_name):
23 "Looks up field by field name; raises KeyError on failure"
24 for field in self.fields:
25 if field.field_name == field_name:
26 return field
27 raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields))
29 def __delitem__(self, field_name):
30 "Deletes the field with the given field name; raises KeyError on failure"
31 for i, field in enumerate(self.fields):
32 if field.field_name == field_name:
33 del self.fields[i]
34 return
35 raise KeyError, "Field %s not found" % field_name
37 def check_permissions(self, user):
38 """Confirms user has required permissions to use this manipulator; raises
39 PermissionDenied on failure."""
40 if self.required_permission is None:
41 return
42 if user.has_perm(self.required_permission):
43 return
44 raise PermissionDenied
46 def prepare(self, new_data):
47 """
48 Makes any necessary preparations to new_data, in place, before data has
49 been validated.
50 """
51 for field in self.fields:
52 field.prepare(new_data)
54 def get_validation_errors(self, new_data):
55 "Returns dictionary mapping field_names to error-message lists"
56 errors = {}
57 self.prepare(new_data)
58 for field in self.fields:
59 errors.update(field.get_validation_errors(new_data))
60 val_name = 'validate_%s' % field.field_name
61 if hasattr(self, val_name):
62 val = getattr(self, val_name)
63 try:
64 field.run_validator(new_data, val)
65 except (validators.ValidationError, validators.CriticalValidationError), e:
66 errors.setdefault(field.field_name, []).extend(e.messages)
68 # if field.is_required and not new_data.get(field.field_name, False):
69 # errors.setdefault(field.field_name, []).append(gettext_lazy('This field is required.'))
70 # continue
71 # try:
72 # validator_list = field.validator_list
73 # if hasattr(self, 'validate_%s' % field.field_name):
74 # validator_list.append(getattr(self, 'validate_%s' % field.field_name))
75 # for validator in validator_list:
76 # if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'):
77 # try:
78 # if hasattr(field, 'requires_data_list'):
79 # validator(new_data.getlist(field.field_name), new_data)
80 # else:
81 # validator(new_data.get(field.field_name, ''), new_data)
82 # except validators.ValidationError, e:
83 # errors.setdefault(field.field_name, []).extend(e.messages)
84 # # If a CriticalValidationError is raised, ignore any other ValidationErrors
85 # # for this particular field
86 # except validators.CriticalValidationError, e:
87 # errors.setdefault(field.field_name, []).extend(e.messages)
88 return errors
90 def save(self, new_data):
91 "Saves the changes and returns the new object"
92 # changes is a dictionary-like object keyed by field_name
93 raise NotImplementedError
95 def do_html2python(self, new_data):
96 """
97 Convert the data from HTML data types to Python datatypes, changing the
98 object in place. This happens after validation but before storage. This
99 must happen after validation because html2python functions aren't
100 expected to deal with invalid input.
102 for field in self.fields:
103 field.convert_post_data(new_data)
105 class FormWrapper(object):
107 A wrapper linking a Manipulator to the template system.
108 This allows dictionary-style lookups of formfields. It also handles feeding
109 prepopulated data and validation error messages to the formfield objects.
111 def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True):
112 self.manipulator = manipulator
113 if data is None:
114 data = {}
115 if error_dict is None:
116 error_dict = {}
117 self.data = data
118 self.error_dict = error_dict
119 self._inline_collections = None
120 self.edit_inline = edit_inline
122 def __repr__(self):
123 return repr(self.__dict__)
125 def __getitem__(self, key):
126 for field in self.manipulator.fields:
127 if field.field_name == key:
128 data = field.extract_data(self.data)
129 return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, []))
130 if self.edit_inline:
131 self.fill_inline_collections()
132 for inline_collection in self._inline_collections:
133 # The 'orig_name' comparison is for backwards compatibility
134 # with hand-crafted forms.
135 if inline_collection.name == key or (':' not in key and inline_collection.orig_name == key):
136 return inline_collection
137 raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key
139 def fill_inline_collections(self):
140 if not self._inline_collections:
141 ic = []
142 related_objects = self.manipulator.get_related_objects()
143 for rel_obj in related_objects:
144 data = rel_obj.extract_data(self.data)
145 inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict)
146 ic.append(inline_collection)
147 self._inline_collections = ic
149 def has_errors(self):
150 return self.error_dict != {}
152 def _get_fields(self):
153 try:
154 return self._fields
155 except AttributeError:
156 self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields]
157 return self._fields
159 fields = property(_get_fields)
161 class FormFieldWrapper(object):
162 "A bridge between the template system and an individual form field. Used by FormWrapper."
163 def __init__(self, formfield, data, error_list):
164 self.formfield, self.data, self.error_list = formfield, data, error_list
165 self.field_name = self.formfield.field_name # for convenience in templates
167 def __str__(self):
168 "Renders the field"
169 return str(self.formfield.render(self.data))
171 def __repr__(self):
172 return '<FormFieldWrapper for "%s">' % self.formfield.field_name
174 def field_list(self):
176 Like __str__(), but returns a list. Use this when the field's render()
177 method returns a list.
179 return self.formfield.render(self.data)
181 def errors(self):
182 return self.error_list
184 def html_error_list(self):
185 if self.errors():
186 return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
187 else:
188 return ''
190 def get_id(self):
191 return self.formfield.get_id()
193 class FormFieldCollection(FormFieldWrapper):
194 "A utility class that gives the template access to a dict of FormFieldWrappers"
195 def __init__(self, formfield_dict):
196 self.formfield_dict = formfield_dict
198 def __str__(self):
199 return str(self.formfield_dict)
201 def __getitem__(self, template_key):
202 "Look up field by template key; raise KeyError on failure"
203 return self.formfield_dict[template_key]
205 def __repr__(self):
206 return "<FormFieldCollection: %s>" % self.formfield_dict
208 def errors(self):
209 "Returns list of all errors in this collection's formfields"
210 errors = []
211 for field in self.formfield_dict.values():
212 if hasattr(field, 'errors'):
213 errors.extend(field.errors())
214 return errors
216 def has_errors(self):
217 return bool(len(self.errors()))
219 def html_combined_error_list(self):
220 return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
222 class InlineObjectCollection(object):
223 "An object that acts like a sparse list of form field collections."
224 def __init__(self, parent_manipulator, rel_obj, data, errors):
225 self.parent_manipulator = parent_manipulator
226 self.rel_obj = rel_obj
227 self.data = data
228 self.errors = errors
229 self._collections = None
230 self.name = rel_obj.name
231 # This is the name used prior to fixing #1839. Needs for backwards
232 # compatibility.
233 self.orig_name = rel_obj.opts.module_name
235 def __len__(self):
236 self.fill()
237 return self._collections.__len__()
239 def __getitem__(self, k):
240 self.fill()
241 return self._collections.__getitem__(k)
243 def __setitem__(self, k, v):
244 self.fill()
245 return self._collections.__setitem__(k,v)
247 def __delitem__(self, k):
248 self.fill()
249 return self._collections.__delitem__(k)
251 def __iter__(self):
252 self.fill()
253 return iter(self._collections.values())
255 def items(self):
256 self.fill()
257 return self._collections.items()
259 def fill(self):
260 if self._collections:
261 return
262 else:
263 var_name = self.rel_obj.opts.object_name.lower()
264 collections = {}
265 orig = None
266 if hasattr(self.parent_manipulator, 'original_object'):
267 orig = self.parent_manipulator.original_object
268 orig_list = self.rel_obj.get_list(orig)
270 for i, instance in enumerate(orig_list):
271 collection = {'original': instance}
272 for f in self.rel_obj.editable_fields():
273 for field_name in f.get_manipulator_field_names(''):
274 full_field_name = '%s.%d.%s' % (var_name, i, field_name)
275 field = self.parent_manipulator[full_field_name]
276 data = field.extract_data(self.data)
277 errors = self.errors.get(full_field_name, [])
278 collection[field_name] = FormFieldWrapper(field, data, errors)
279 collections[i] = FormFieldCollection(collection)
280 self._collections = collections
283 class FormField(object):
284 """Abstract class representing a form field.
286 Classes that extend FormField should define the following attributes:
287 field_name
288 The field's name for use by programs.
289 validator_list
290 A list of validation tests (callback functions) that the data for
291 this field must pass in order to be added or changed.
292 is_required
293 A Boolean. Is it a required field?
294 Subclasses should also implement a render(data) method, which is responsible
295 for rending the form field in XHTML.
297 def __str__(self):
298 return self.render('')
300 def __repr__(self):
301 return 'FormField "%s"' % self.field_name
303 def prepare(self, new_data):
304 "Hook for doing something to new_data (in place) before validation."
305 pass
307 def html2python(data):
308 "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type"
309 return data
310 html2python = staticmethod(html2python)
312 def render(self, data):
313 raise NotImplementedError
315 def get_member_name(self):
316 if hasattr(self, 'member_name'):
317 return self.member_name
318 else:
319 return self.field_name
321 def extract_data(self, data_dict):
322 if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'):
323 data = data_dict.getlist(self.get_member_name())
324 else:
325 data = data_dict.get(self.get_member_name(), None)
326 if data is None:
327 data = ''
328 return data
330 def convert_post_data(self, new_data):
331 name = self.get_member_name()
332 if new_data.has_key(self.field_name):
333 d = new_data.getlist(self.field_name)
334 try:
335 converted_data = [self.__class__.html2python(data) for data in d]
336 except ValueError:
337 converted_data = d
338 new_data.setlist(name, converted_data)
339 else:
340 try:
341 #individual fields deal with None values themselves
342 new_data.setlist(name, [self.__class__.html2python(None)])
343 except EmptyValue:
344 new_data.setlist(name, [])
347 def run_validator(self, new_data, validator):
348 if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'):
349 if hasattr(self, 'requires_data_list'):
350 validator(new_data.getlist(self.field_name), new_data)
351 else:
352 validator(new_data.get(self.field_name, ''), new_data)
354 def get_validation_errors(self, new_data):
355 errors = {}
356 if self.is_required and not new_data.get(self.field_name, False):
357 errors.setdefault(self.field_name, []).append(gettext('This field is required.'))
358 return errors
359 try:
360 for validator in self.validator_list:
361 try:
362 self.run_validator(new_data, validator)
363 except validators.ValidationError, e:
364 errors.setdefault(self.field_name, []).extend(e.messages)
365 # If a CriticalValidationError is raised, ignore any other ValidationErrors
366 # for this particular field
367 except validators.CriticalValidationError, e:
368 errors.setdefault(self.field_name, []).extend(e.messages)
369 return errors
371 def get_id(self):
372 "Returns the HTML 'id' attribute for this form field."
373 return FORM_FIELD_ID_PREFIX + self.field_name
375 ####################
376 # GENERIC WIDGETS #
377 ####################
379 class TextField(FormField):
380 input_type = "text"
381 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None, member_name=None):
382 if validator_list is None: validator_list = []
383 self.field_name = field_name
384 self.length, self.maxlength = length, maxlength
385 self.is_required = is_required
386 self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list
387 if member_name != None:
388 self.member_name = member_name
390 def isValidLength(self, data, form):
391 if data and self.maxlength and len(data.decode(settings.DEFAULT_CHARSET)) > self.maxlength:
392 raise validators.ValidationError, ngettext("Ensure your text is less than %s character.",
393 "Ensure your text is less than %s characters.", self.maxlength) % self.maxlength
395 def hasNoNewlines(self, data, form):
396 if data and '\n' in data:
397 raise validators.ValidationError, gettext("Line breaks are not allowed here.")
399 def render(self, data):
400 if data is None:
401 data = ''
402 maxlength = ''
403 if self.maxlength:
404 maxlength = 'maxlength="%s" ' % self.maxlength
405 if isinstance(data, unicode):
406 data = data.encode(settings.DEFAULT_CHARSET)
407 return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
408 (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
409 self.field_name, self.length, escape(data), maxlength)
411 def html2python(data):
412 return data
413 html2python = staticmethod(html2python)
415 class PasswordField(TextField):
416 input_type = "password"
418 class LargeTextField(TextField):
419 def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, maxlength=None):
420 if validator_list is None: validator_list = []
421 self.field_name = field_name
422 self.rows, self.cols, self.is_required = rows, cols, is_required
423 self.validator_list = validator_list[:]
424 if maxlength:
425 self.validator_list.append(self.isValidLength)
426 self.maxlength = maxlength
428 def render(self, data):
429 if data is None:
430 data = ''
431 if isinstance(data, unicode):
432 data = data.encode(settings.DEFAULT_CHARSET)
433 return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
434 (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
435 self.field_name, self.rows, self.cols, escape(data))
437 class HiddenField(FormField):
438 def __init__(self, field_name, is_required=False, validator_list=None):
439 if validator_list is None: validator_list = []
440 self.field_name, self.is_required = field_name, is_required
441 self.validator_list = validator_list[:]
443 def render(self, data):
444 return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
445 (self.get_id(), self.field_name, escape(data))
447 class CheckboxField(FormField):
448 def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
449 if validator_list is None: validator_list = []
450 self.field_name = field_name
451 self.checked_by_default = checked_by_default
452 self.is_required = is_required
453 self.validator_list = validator_list[:]
455 def render(self, data):
456 checked_html = ''
457 if data or (data is '' and self.checked_by_default):
458 checked_html = ' checked="checked"'
459 return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
460 (self.get_id(), self.__class__.__name__,
461 self.field_name, checked_html)
463 def html2python(data):
464 "Convert value from browser ('on' or '') to a Python boolean"
465 if data == 'on':
466 return True
467 return False
468 html2python = staticmethod(html2python)
470 class SelectField(FormField):
471 def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None):
472 if validator_list is None: validator_list = []
473 if choices is None: choices = []
474 self.field_name = field_name
475 # choices is a list of (value, human-readable key) tuples because order matters
476 self.choices, self.size, self.is_required = choices, size, is_required
477 self.validator_list = [self.isValidChoice] + validator_list
478 if member_name != None:
479 self.member_name = member_name
481 def render(self, data):
482 output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \
483 (self.get_id(), self.__class__.__name__,
484 self.is_required and ' required' or '', self.field_name, self.size)]
485 str_data = str(data) # normalize to string
486 for value, display_name in self.choices:
487 selected_html = ''
488 if str(value) == str_data:
489 selected_html = ' selected="selected"'
490 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
491 output.append(' </select>')
492 return '\n'.join(output)
494 def isValidChoice(self, data, form):
495 str_data = str(data)
496 str_choices = [str(item[0]) for item in self.choices]
497 if str_data not in str_choices:
498 raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices}
500 class NullSelectField(SelectField):
501 "This SelectField converts blank fields to None"
502 def html2python(data):
503 if not data:
504 return None
505 return data
506 html2python = staticmethod(html2python)
508 class RadioSelectField(FormField):
509 def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None):
510 if validator_list is None: validator_list = []
511 if choices is None: choices = []
512 self.field_name = field_name
513 # choices is a list of (value, human-readable key) tuples because order matters
514 self.choices, self.is_required = choices, is_required
515 self.validator_list = [self.isValidChoice] + validator_list
516 self.ul_class = ul_class
517 if member_name != None:
518 self.member_name = member_name
520 def render(self, data):
522 Returns a special object, RadioFieldRenderer, that is iterable *and*
523 has a default str() rendered output.
525 This allows for flexible use in templates. You can just use the default
526 rendering:
528 {{ field_name }}
530 ...which will output the radio buttons in an unordered list.
531 Or, you can manually traverse each radio option for special layout:
533 {% for option in field_name.field_list %}
534 {{ option.field }} {{ option.label }}<br />
535 {% endfor %}
537 class RadioFieldRenderer:
538 def __init__(self, datalist, ul_class):
539 self.datalist, self.ul_class = datalist, ul_class
540 def __str__(self):
541 "Default str() output for this radio field -- a <ul>"
542 output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
543 output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
544 output.append('</ul>')
545 return ''.join(output)
546 def __iter__(self):
547 for d in self.datalist:
548 yield d
549 def __len__(self):
550 return len(self.datalist)
551 datalist = []
552 str_data = str(data) # normalize to string
553 for i, (value, display_name) in enumerate(self.choices):
554 selected_html = ''
555 if str(value) == str_data:
556 selected_html = ' checked="checked"'
557 datalist.append({
558 'value': value,
559 'name': display_name,
560 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
561 (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
562 'label': '<label for="%s">%s</label>' % \
563 (self.get_id() + '_' + str(i), display_name),
565 return RadioFieldRenderer(datalist, self.ul_class)
567 def isValidChoice(self, data, form):
568 str_data = str(data)
569 str_choices = [str(item[0]) for item in self.choices]
570 if str_data not in str_choices:
571 raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices}
573 class NullBooleanField(SelectField):
574 "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None"
575 def __init__(self, field_name, is_required=False, validator_list=None):
576 if validator_list is None: validator_list = []
577 SelectField.__init__(self, field_name, choices=[('1', _('Unknown')), ('2', _('Yes')), ('3', _('No'))],
578 is_required=is_required, validator_list=validator_list)
580 def render(self, data):
581 if data is None: data = '1'
582 elif data == True: data = '2'
583 elif data == False: data = '3'
584 return SelectField.render(self, data)
586 def html2python(data):
587 return {None: None, '1': None, '2': True, '3': False}[data]
588 html2python = staticmethod(html2python)
590 class SelectMultipleField(SelectField):
591 requires_data_list = True
592 def render(self, data):
593 output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \
594 (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
595 self.field_name, self.size)]
596 str_data_list = map(str, data) # normalize to strings
597 for value, choice in self.choices:
598 selected_html = ''
599 if str(value) in str_data_list:
600 selected_html = ' selected="selected"'
601 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
602 output.append(' </select>')
603 return '\n'.join(output)
605 def isValidChoice(self, field_data, all_data):
606 # data is something like ['1', '2', '3']
607 str_choices = [str(item[0]) for item in self.choices]
608 for val in map(str, field_data):
609 if val not in str_choices:
610 raise validators.ValidationError, gettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices}
612 def html2python(data):
613 if data is None:
614 raise EmptyValue
615 return data
616 html2python = staticmethod(html2python)
618 class CheckboxSelectMultipleField(SelectMultipleField):
620 This has an identical interface to SelectMultipleField, except the rendered
621 widget is different. Instead of a <select multiple>, this widget outputs a
622 <ul> of <input type="checkbox">es.
624 Of course, that results in multiple form elements for the same "single"
625 field, so this class's prepare() method flattens the split data elements
626 back into the single list that validators, renderers and save() expect.
628 requires_data_list = True
629 def __init__(self, field_name, choices=None, ul_class='', validator_list=None):
630 if validator_list is None: validator_list = []
631 if choices is None: choices = []
632 self.ul_class = ul_class
633 SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list)
635 def prepare(self, new_data):
636 # new_data has "split" this field into several fields, so flatten it
637 # back into a single list.
638 data_list = []
639 for value, readable_value in self.choices:
640 if new_data.get('%s%s' % (self.field_name, value), '') == 'on':
641 data_list.append(value)
642 new_data.setlist(self.field_name, data_list)
644 def render(self, data):
645 output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
646 str_data_list = map(str, data) # normalize to strings
647 for value, choice in self.choices:
648 checked_html = ''
649 if str(value) in str_data_list:
650 checked_html = ' checked="checked"'
651 field_name = '%s%s' % (self.field_name, value)
652 output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" /> <label for="%s">%s</label></li>' % \
653 (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
654 self.get_id() + escape(value), choice))
655 output.append('</ul>')
656 return '\n'.join(output)
658 ####################
659 # FILE UPLOADS #
660 ####################
662 class FileUploadField(FormField):
663 def __init__(self, field_name, is_required=False, validator_list=None):
664 if validator_list is None: validator_list = []
665 self.field_name, self.is_required = field_name, is_required
666 self.validator_list = [self.isNonEmptyFile] + validator_list
668 def isNonEmptyFile(self, field_data, all_data):
669 try:
670 content = field_data['content']
671 except TypeError:
672 raise validators.CriticalValidationError, gettext("No file was submitted. Check the encoding type on the form.")
673 if not content:
674 raise validators.CriticalValidationError, gettext("The submitted file is empty.")
676 def render(self, data):
677 return '<input type="file" id="%s" class="v%s" name="%s" />' % \
678 (self.get_id(), self.__class__.__name__, self.field_name)
680 def html2python(data):
681 if data is None:
682 raise EmptyValue
683 return data
684 html2python = staticmethod(html2python)
686 class ImageUploadField(FileUploadField):
687 "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image."
688 def __init__(self, *args, **kwargs):
689 FileUploadField.__init__(self, *args, **kwargs)
690 self.validator_list.insert(0, self.isValidImage)
692 def isValidImage(self, field_data, all_data):
693 try:
694 validators.isValidImage(field_data, all_data)
695 except validators.ValidationError, e:
696 raise validators.CriticalValidationError, e.messages
698 ####################
699 # INTEGERS/FLOATS #
700 ####################
702 class IntegerField(TextField):
703 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None, member_name=None):
704 if validator_list is None: validator_list = []
705 validator_list = [self.isInteger] + validator_list
706 if member_name is not None:
707 self.member_name = member_name
708 TextField.__init__(self, field_name, length, maxlength, is_required, validator_list)
710 def isInteger(self, field_data, all_data):
711 try:
712 validators.isInteger(field_data, all_data)
713 except validators.ValidationError, e:
714 raise validators.CriticalValidationError, e.messages
716 def html2python(data):
717 if data == '' or data is None:
718 return None
719 return int(data)
720 html2python = staticmethod(html2python)
722 class SmallIntegerField(IntegerField):
723 def __init__(self, field_name, length=5, maxlength=5, is_required=False, validator_list=None):
724 if validator_list is None: validator_list = []
725 validator_list = [self.isSmallInteger] + validator_list
726 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
728 def isSmallInteger(self, field_data, all_data):
729 if not -32768 <= int(field_data) <= 32767:
730 raise validators.CriticalValidationError, gettext("Enter a whole number between -32,768 and 32,767.")
732 class PositiveIntegerField(IntegerField):
733 def __init__(self, field_name, length=10, maxlength=None, is_required=False, validator_list=None):
734 if validator_list is None: validator_list = []
735 validator_list = [self.isPositive] + validator_list
736 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
738 def isPositive(self, field_data, all_data):
739 if int(field_data) < 0:
740 raise validators.CriticalValidationError, gettext("Enter a positive number.")
742 class PositiveSmallIntegerField(IntegerField):
743 def __init__(self, field_name, length=5, maxlength=None, is_required=False, validator_list=None):
744 if validator_list is None: validator_list = []
745 validator_list = [self.isPositiveSmall] + validator_list
746 IntegerField.__init__(self, field_name, length, maxlength, is_required, validator_list)
748 def isPositiveSmall(self, field_data, all_data):
749 if not 0 <= int(field_data) <= 32767:
750 raise validators.CriticalValidationError, gettext("Enter a whole number between 0 and 32,767.")
752 class FloatField(TextField):
753 def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None):
754 if validator_list is None: validator_list = []
755 self.max_digits, self.decimal_places = max_digits, decimal_places
756 validator_list = [self.isValidFloat] + validator_list
757 TextField.__init__(self, field_name, max_digits+2, max_digits+2, is_required, validator_list)
759 def isValidFloat(self, field_data, all_data):
760 v = validators.IsValidFloat(self.max_digits, self.decimal_places)
761 try:
762 v(field_data, all_data)
763 except validators.ValidationError, e:
764 raise validators.CriticalValidationError, e.messages
766 def html2python(data):
767 if data == '' or data is None:
768 return None
769 return float(data)
770 html2python = staticmethod(html2python)
772 ####################
773 # DATES AND TIMES #
774 ####################
776 class DatetimeField(TextField):
777 """A FormField that automatically converts its data to a datetime.datetime object.
778 The data should be in the format YYYY-MM-DD HH:MM:SS."""
779 def __init__(self, field_name, length=30, maxlength=None, is_required=False, validator_list=None):
780 if validator_list is None: validator_list = []
781 self.field_name = field_name
782 self.length, self.maxlength = length, maxlength
783 self.is_required = is_required
784 self.validator_list = [validators.isValidANSIDatetime] + validator_list
786 def html2python(data):
787 "Converts the field into a datetime.datetime object"
788 import datetime
789 try:
790 date, time = data.split()
791 y, m, d = date.split('-')
792 timebits = time.split(':')
793 h, mn = timebits[:2]
794 if len(timebits) > 2:
795 s = int(timebits[2])
796 else:
797 s = 0
798 return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s)
799 except ValueError:
800 return None
801 html2python = staticmethod(html2python)
803 class DateField(TextField):
804 """A FormField that automatically converts its data to a datetime.date object.
805 The data should be in the format YYYY-MM-DD."""
806 def __init__(self, field_name, is_required=False, validator_list=None):
807 if validator_list is None: validator_list = []
808 validator_list = [self.isValidDate] + validator_list
809 TextField.__init__(self, field_name, length=10, maxlength=10,
810 is_required=is_required, validator_list=validator_list)
812 def isValidDate(self, field_data, all_data):
813 try:
814 validators.isValidANSIDate(field_data, all_data)
815 except validators.ValidationError, e:
816 raise validators.CriticalValidationError, e.messages
818 def html2python(data):
819 "Converts the field into a datetime.date object"
820 import time, datetime
821 try:
822 time_tuple = time.strptime(data, '%Y-%m-%d')
823 return datetime.date(*time_tuple[0:3])
824 except (ValueError, TypeError):
825 return None
826 html2python = staticmethod(html2python)
828 class TimeField(TextField):
829 """A FormField that automatically converts its data to a datetime.time object.
830 The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm."""
831 def __init__(self, field_name, is_required=False, validator_list=None):
832 if validator_list is None: validator_list = []
833 validator_list = [self.isValidTime] + validator_list
834 TextField.__init__(self, field_name, length=8, maxlength=8,
835 is_required=is_required, validator_list=validator_list)
837 def isValidTime(self, field_data, all_data):
838 try:
839 validators.isValidANSITime(field_data, all_data)
840 except validators.ValidationError, e:
841 raise validators.CriticalValidationError, e.messages
843 def html2python(data):
844 "Converts the field into a datetime.time object"
845 import time, datetime
846 try:
847 part_list = data.split('.')
848 try:
849 time_tuple = time.strptime(part_list[0], '%H:%M:%S')
850 except ValueError: # seconds weren't provided
851 time_tuple = time.strptime(part_list[0], '%H:%M')
852 t = datetime.time(*time_tuple[3:6])
853 if (len(part_list) == 2):
854 t = t.replace(microsecond=int(part_list[1]))
855 return t
856 except (ValueError, TypeError, AttributeError):
857 return None
858 html2python = staticmethod(html2python)
860 ####################
861 # INTERNET-RELATED #
862 ####################
864 class EmailField(TextField):
865 "A convenience FormField for validating e-mail addresses"
866 def __init__(self, field_name, length=50, maxlength=75, is_required=False, validator_list=None):
867 if validator_list is None: validator_list = []
868 validator_list = [self.isValidEmail] + validator_list
869 TextField.__init__(self, field_name, length, maxlength=maxlength,
870 is_required=is_required, validator_list=validator_list)
872 def isValidEmail(self, field_data, all_data):
873 try:
874 validators.isValidEmail(field_data, all_data)
875 except validators.ValidationError, e:
876 raise validators.CriticalValidationError, e.messages
878 class URLField(TextField):
879 "A convenience FormField for validating URLs"
880 def __init__(self, field_name, length=50, maxlength=200, is_required=False, validator_list=None):
881 if validator_list is None: validator_list = []
882 validator_list = [self.isValidURL] + validator_list
883 TextField.__init__(self, field_name, length=length, maxlength=maxlength,
884 is_required=is_required, validator_list=validator_list)
886 def isValidURL(self, field_data, all_data):
887 try:
888 validators.isValidURL(field_data, all_data)
889 except validators.ValidationError, e:
890 raise validators.CriticalValidationError, e.messages
892 class IPAddressField(TextField):
893 def __init__(self, field_name, length=15, maxlength=15, is_required=False, validator_list=None):
894 if validator_list is None: validator_list = []
895 validator_list = [self.isValidIPAddress] + validator_list
896 TextField.__init__(self, field_name, length=length, maxlength=maxlength,
897 is_required=is_required, validator_list=validator_list)
899 def isValidIPAddress(self, field_data, all_data):
900 try:
901 validators.isValidIPAddress4(field_data, all_data)
902 except validators.ValidationError, e:
903 raise validators.CriticalValidationError, e.messages
905 def html2python(data):
906 return data or None
907 html2python = staticmethod(html2python)
909 ####################
910 # MISCELLANEOUS #
911 ####################
913 class FilePathField(SelectField):
914 "A SelectField whose choices are the files in a given directory."
915 def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None):
916 import os
917 from django.db.models import BLANK_CHOICE_DASH
918 if match is not None:
919 import re
920 match_re = re.compile(match)
921 choices = not is_required and BLANK_CHOICE_DASH[:] or []
922 if recursive:
923 for root, dirs, files in os.walk(path):
924 for f in files:
925 if match is None or match_re.search(f):
926 choices.append((os.path.join(root, f), f))
927 else:
928 try:
929 for f in os.listdir(path):
930 full_file = os.path.join(path, f)
931 if os.path.isfile(full_file) and (match is None or match_re.search(f)):
932 choices.append((full_file, f))
933 except OSError:
934 pass
935 SelectField.__init__(self, field_name, choices, 1, is_required, validator_list)
937 class PhoneNumberField(TextField):
938 "A convenience FormField for validating phone numbers (e.g. '630-555-1234')"
939 def __init__(self, field_name, is_required=False, validator_list=None):
940 if validator_list is None: validator_list = []
941 validator_list = [self.isValidPhone] + validator_list
942 TextField.__init__(self, field_name, length=12, maxlength=12,
943 is_required=is_required, validator_list=validator_list)
945 def isValidPhone(self, field_data, all_data):
946 try:
947 validators.isValidPhone(field_data, all_data)
948 except validators.ValidationError, e:
949 raise validators.CriticalValidationError, e.messages
951 class USStateField(TextField):
952 "A convenience FormField for validating U.S. states (e.g. 'IL')"
953 def __init__(self, field_name, is_required=False, validator_list=None):
954 if validator_list is None: validator_list = []
955 validator_list = [self.isValidUSState] + validator_list
956 TextField.__init__(self, field_name, length=2, maxlength=2,
957 is_required=is_required, validator_list=validator_list)
959 def isValidUSState(self, field_data, all_data):
960 try:
961 validators.isValidUSState(field_data, all_data)
962 except validators.ValidationError, e:
963 raise validators.CriticalValidationError, e.messages
965 def html2python(data):
966 if data:
967 return data.upper() # Should always be stored in upper case
968 return data
969 html2python = staticmethod(html2python)
971 class CommaSeparatedIntegerField(TextField):
972 "A convenience FormField for validating comma-separated integer fields"
973 def __init__(self, field_name, maxlength=None, is_required=False, validator_list=None):
974 if validator_list is None: validator_list = []
975 validator_list = [self.isCommaSeparatedIntegerList] + validator_list
976 TextField.__init__(self, field_name, length=20, maxlength=maxlength,
977 is_required=is_required, validator_list=validator_list)
979 def isCommaSeparatedIntegerList(self, field_data, all_data):
980 try:
981 validators.isCommaSeparatedIntegerList(field_data, all_data)
982 except validators.ValidationError, e:
983 raise validators.CriticalValidationError, e.messages
985 def render(self, data):
986 if data is None:
987 data = ''
988 elif isinstance(data, (list, tuple)):
989 data = ','.join(data)
990 return super(CommaSeparatedIntegerField, self).render(data)
992 class RawIdAdminField(CommaSeparatedIntegerField):
993 def html2python(data):
994 if data:
995 return data.split(',')
996 else:
997 return []
998 html2python = staticmethod(html2python)
1000 class XMLLargeTextField(LargeTextField):
1002 A LargeTextField with an XML validator. The schema_path argument is the
1003 full path to a Relax NG compact schema to validate against.
1005 def __init__(self, field_name, schema_path, **kwargs):
1006 self.schema_path = schema_path
1007 kwargs.setdefault('validator_list', []).insert(0, self.isValidXML)
1008 LargeTextField.__init__(self, field_name, **kwargs)
1010 def isValidXML(self, field_data, all_data):
1011 v = validators.RelaxNGCompact(self.schema_path)
1012 try:
1013 v(field_data, all_data)
1014 except validators.ValidationError, e:
1015 raise validators.CriticalValidationError, e.messages