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"
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
= ''
19 # List of FormField objects
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
:
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
:
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:
42 if user
.has_perm(self
.required_permission
):
44 raise PermissionDenied
46 def prepare(self
, new_data
):
48 Makes any necessary preparations to new_data, in place, before data has
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"
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
)
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.'))
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'):
78 # if hasattr(field, 'requires_data_list'):
79 # validator(new_data.getlist(field.field_name), new_data)
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)
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
):
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
115 if error_dict
is None:
118 self
.error_dict
= error_dict
119 self
._inline
_collections
= None
120 self
.edit_inline
= edit_inline
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
, []))
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
:
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
):
155 except AttributeError:
156 self
._fields
= [self
.__getitem
__(field
.field_name
) for field
in self
.manipulator
.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
169 return str(self
.formfield
.render(self
.data
))
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
)
182 return self
.error_list
184 def html_error_list(self
):
186 return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e
) for e
in self
.errors()])
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
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
]
206 return "<FormFieldCollection: %s>" % self
.formfield_dict
209 "Returns list of all errors in this collection's formfields"
211 for field
in self
.formfield_dict
.values():
212 if hasattr(field
, 'errors'):
213 errors
.extend(field
.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
229 self
._collections
= None
230 self
.name
= rel_obj
.name
231 # This is the name used prior to fixing #1839. Needs for backwards
233 self
.orig_name
= rel_obj
.opts
.module_name
237 return self
._collections
.__len
__()
239 def __getitem__(self
, k
):
241 return self
._collections
.__getitem
__(k
)
243 def __setitem__(self
, k
, v
):
245 return self
._collections
.__setitem
__(k
,v
)
247 def __delitem__(self
, k
):
249 return self
._collections
.__delitem
__(k
)
253 return iter(self
._collections
.values())
257 return self
._collections
.items()
260 if self
._collections
:
263 var_name
= self
.rel_obj
.opts
.object_name
.lower()
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:
288 The field's name for use by programs.
290 A list of validation tests (callback functions) that the data for
291 this field must pass in order to be added or changed.
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.
298 return self
.render('')
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."
307 def html2python(data
):
308 "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type"
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
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())
325 data
= data_dict
.get(self
.get_member_name(), None)
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
)
335 converted_data
= [self
.__class
__.html2python(data
) for data
in d
]
338 new_data
.setlist(name
, converted_data
)
341 #individual fields deal with None values themselves
342 new_data
.setlist(name
, [self
.__class
__.html2python(None)])
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
)
352 validator(new_data
.get(self
.field_name
, ''), new_data
)
354 def get_validation_errors(self
, new_data
):
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.'))
360 for validator
in self
.validator_list
:
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
)
372 "Returns the HTML 'id' attribute for this form field."
373 return FORM_FIELD_ID_PREFIX
+ self
.field_name
379 class TextField(FormField
):
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
):
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
):
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
[:]
425 self
.validator_list
.append(self
.isValidLength
)
426 self
.maxlength
= maxlength
428 def render(self
, 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
):
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"
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
:
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
):
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
):
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
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 />
537 class RadioFieldRenderer
:
538 def __init__(self
, datalist
, ul_class
):
539 self
.datalist
, self
.ul_class
= datalist
, ul_class
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
)
547 for d
in self
.datalist
:
550 return len(self
.datalist
)
552 str_data
= str(data
) # normalize to string
553 for i
, (value
, display_name
) in enumerate(self
.choices
):
555 if str(value
) == str_data
:
556 selected_html
= ' checked="checked"'
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
):
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
:
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
):
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.
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
:
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
)
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
):
670 content
= field_data
['content']
672 raise validators
.CriticalValidationError
, gettext("No file was submitted. Check the encoding type on the form.")
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
):
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
):
694 validators
.isValidImage(field_data
, all_data
)
695 except validators
.ValidationError
, e
:
696 raise validators
.CriticalValidationError
, e
.messages
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
):
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:
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
)
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:
770 html2python
= staticmethod(html2python
)
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"
790 date
, time
= data
.split()
791 y
, m
, d
= date
.split('-')
792 timebits
= time
.split(':')
794 if len(timebits
) > 2:
798 return datetime
.datetime(int(y
), int(m
), int(d
), int(h
), int(mn
), s
)
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
):
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
822 time_tuple
= time
.strptime(data
, '%Y-%m-%d')
823 return datetime
.date(*time_tuple
[0:3])
824 except (ValueError, TypeError):
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
):
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
847 part_list
= data
.split('.')
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]))
856 except (ValueError, TypeError, AttributeError):
858 html2python
= staticmethod(html2python
)
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
):
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
):
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
):
901 validators
.isValidIPAddress4(field_data
, all_data
)
902 except validators
.ValidationError
, e
:
903 raise validators
.CriticalValidationError
, e
.messages
905 def html2python(data
):
907 html2python
= staticmethod(html2python
)
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):
917 from django
.db
.models
import BLANK_CHOICE_DASH
918 if match
is not None:
920 match_re
= re
.compile(match
)
921 choices
= not is_required
and BLANK_CHOICE_DASH
[:] or []
923 for root
, dirs
, files
in os
.walk(path
):
925 if match
is None or match_re
.search(f
):
926 choices
.append((os
.path
.join(root
, f
), f
))
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
))
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
):
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
):
961 validators
.isValidUSState(field_data
, all_data
)
962 except validators
.ValidationError
, e
:
963 raise validators
.CriticalValidationError
, e
.messages
965 def html2python(data
):
967 return data
.upper() # Should always be stored in upper case
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
):
981 validators
.isCommaSeparatedIntegerList(field_data
, all_data
)
982 except validators
.ValidationError
, e
:
983 raise validators
.CriticalValidationError
, e
.messages
985 def render(self
, 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
):
995 return data
.split(',')
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
)
1013 v(field_data
, all_data
)
1014 except validators
.ValidationError
, e
:
1015 raise validators
.CriticalValidationError
, e
.messages