| from django.core import validators |
| from django.core.exceptions import PermissionDenied |
| from django.utils.html import escape |
| from django.utils.safestring import mark_safe |
| from django.conf import settings |
| from django.utils.translation import ugettext, ungettext |
| from django.utils.encoding import smart_unicode, force_unicode |
| |
| FORM_FIELD_ID_PREFIX = 'id_' |
| |
| class EmptyValue(Exception): |
| "This is raised when empty data is provided" |
| pass |
| |
| class Manipulator(object): |
| # List of permission strings. User must have at least one to manipulate. |
| # None means everybody has permission. |
| required_permission = '' |
| |
| def __init__(self): |
| # List of FormField objects |
| self.fields = [] |
| |
| def __getitem__(self, field_name): |
| "Looks up field by field name; raises KeyError on failure" |
| for field in self.fields: |
| if field.field_name == field_name: |
| return field |
| raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields)) |
| |
| def __delitem__(self, field_name): |
| "Deletes the field with the given field name; raises KeyError on failure" |
| for i, field in enumerate(self.fields): |
| if field.field_name == field_name: |
| del self.fields[i] |
| return |
| raise KeyError, "Field %s not found" % field_name |
| |
| def check_permissions(self, user): |
| """Confirms user has required permissions to use this manipulator; raises |
| PermissionDenied on failure.""" |
| if self.required_permission is None: |
| return |
| if user.has_perm(self.required_permission): |
| return |
| raise PermissionDenied |
| |
| def prepare(self, new_data): |
| """ |
| Makes any necessary preparations to new_data, in place, before data has |
| been validated. |
| """ |
| for field in self.fields: |
| field.prepare(new_data) |
| |
| def get_validation_errors(self, new_data): |
| "Returns dictionary mapping field_names to error-message lists" |
| errors = {} |
| self.prepare(new_data) |
| for field in self.fields: |
| errors.update(field.get_validation_errors(new_data)) |
| val_name = 'validate_%s' % field.field_name |
| if hasattr(self, val_name): |
| val = getattr(self, val_name) |
| try: |
| field.run_validator(new_data, val) |
| except (validators.ValidationError, validators.CriticalValidationError), e: |
| errors.setdefault(field.field_name, []).extend(e.messages) |
| |
| # if field.is_required and not new_data.get(field.field_name, False): |
| # errors.setdefault(field.field_name, []).append(ugettext_lazy('This field is required.')) |
| # continue |
| # try: |
| # validator_list = field.validator_list |
| # if hasattr(self, 'validate_%s' % field.field_name): |
| # validator_list.append(getattr(self, 'validate_%s' % field.field_name)) |
| # for validator in validator_list: |
| # if field.is_required or new_data.get(field.field_name, False) or hasattr(validator, 'always_test'): |
| # try: |
| # if hasattr(field, 'requires_data_list'): |
| # validator(new_data.getlist(field.field_name), new_data) |
| # else: |
| # validator(new_data.get(field.field_name, ''), new_data) |
| # except validators.ValidationError, e: |
| # errors.setdefault(field.field_name, []).extend(e.messages) |
| # # If a CriticalValidationError is raised, ignore any other ValidationErrors |
| # # for this particular field |
| # except validators.CriticalValidationError, e: |
| # errors.setdefault(field.field_name, []).extend(e.messages) |
| return errors |
| |
| def save(self, new_data): |
| "Saves the changes and returns the new object" |
| # changes is a dictionary-like object keyed by field_name |
| raise NotImplementedError |
| |
| def do_html2python(self, new_data): |
| """ |
| Convert the data from HTML data types to Python datatypes, changing the |
| object in place. This happens after validation but before storage. This |
| must happen after validation because html2python functions aren't |
| expected to deal with invalid input. |
| """ |
| for field in self.fields: |
| field.convert_post_data(new_data) |
| |
| class FormWrapper(object): |
| """ |
| A wrapper linking a Manipulator to the template system. |
| This allows dictionary-style lookups of formfields. It also handles feeding |
| prepopulated data and validation error messages to the formfield objects. |
| """ |
| def __init__(self, manipulator, data=None, error_dict=None, edit_inline=True): |
| self.manipulator = manipulator |
| if data is None: |
| data = {} |
| if error_dict is None: |
| error_dict = {} |
| self.data = data |
| self.error_dict = error_dict |
| self._inline_collections = None |
| self.edit_inline = edit_inline |
| |
| def __repr__(self): |
| return repr(self.__dict__) |
| |
| def __getitem__(self, key): |
| for field in self.manipulator.fields: |
| if field.field_name == key: |
| data = field.extract_data(self.data) |
| return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) |
| if self.edit_inline: |
| self.fill_inline_collections() |
| for inline_collection in self._inline_collections: |
| # The 'orig_name' comparison is for backwards compatibility |
| # with hand-crafted forms. |
| if inline_collection.name == key or (':' not in key and inline_collection.orig_name == key): |
| return inline_collection |
| raise KeyError, "Could not find Formfield or InlineObjectCollection named %r" % key |
| |
| def fill_inline_collections(self): |
| if not self._inline_collections: |
| ic = [] |
| related_objects = self.manipulator.get_related_objects() |
| for rel_obj in related_objects: |
| data = rel_obj.extract_data(self.data) |
| inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) |
| ic.append(inline_collection) |
| self._inline_collections = ic |
| |
| def has_errors(self): |
| return self.error_dict != {} |
| |
| def _get_fields(self): |
| try: |
| return self._fields |
| except AttributeError: |
| self._fields = [self.__getitem__(field.field_name) for field in self.manipulator.fields] |
| return self._fields |
| |
| fields = property(_get_fields) |
| |
| class FormFieldWrapper(object): |
| "A bridge between the template system and an individual form field. Used by FormWrapper." |
| def __init__(self, formfield, data, error_list): |
| self.formfield, self.data, self.error_list = formfield, data, error_list |
| self.field_name = self.formfield.field_name # for convenience in templates |
| |
| def __str__(self): |
| "Renders the field" |
| return unicode(self).encode('utf-8') |
| |
| def __unicode__(self): |
| "Renders the field" |
| return force_unicode(self.formfield.render(self.data)) |
| |
| def __repr__(self): |
| return '<FormFieldWrapper for "%s">' % self.formfield.field_name |
| |
| def field_list(self): |
| """ |
| Like __str__(), but returns a list. Use this when the field's render() |
| method returns a list. |
| """ |
| return self.formfield.render(self.data) |
| |
| def errors(self): |
| return self.error_list |
| |
| def html_error_list(self): |
| if self.errors(): |
| return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])) |
| else: |
| return mark_safe('') |
| |
| def get_id(self): |
| return self.formfield.get_id() |
| |
| class FormFieldCollection(FormFieldWrapper): |
| "A utility class that gives the template access to a dict of FormFieldWrappers" |
| def __init__(self, formfield_dict): |
| self.formfield_dict = formfield_dict |
| |
| def __str__(self): |
| return unicode(self).encode('utf-8') |
| |
| def __unicode__(self): |
| return unicode(self.formfield_dict) |
| |
| def __getitem__(self, template_key): |
| "Look up field by template key; raise KeyError on failure" |
| return self.formfield_dict[template_key] |
| |
| def __repr__(self): |
| return "<FormFieldCollection: %s>" % self.formfield_dict |
| |
| def errors(self): |
| "Returns list of all errors in this collection's formfields" |
| errors = [] |
| for field in self.formfield_dict.values(): |
| if hasattr(field, 'errors'): |
| errors.extend(field.errors()) |
| return errors |
| |
| def has_errors(self): |
| return bool(len(self.errors())) |
| |
| def html_combined_error_list(self): |
| return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) |
| |
| class InlineObjectCollection(object): |
| "An object that acts like a sparse list of form field collections." |
| def __init__(self, parent_manipulator, rel_obj, data, errors): |
| self.parent_manipulator = parent_manipulator |
| self.rel_obj = rel_obj |
| self.data = data |
| self.errors = errors |
| self._collections = None |
| self.name = rel_obj.name |
| # This is the name used prior to fixing #1839. Needs for backwards |
| # compatibility. |
| self.orig_name = rel_obj.opts.module_name |
| |
| def __len__(self): |
| self.fill() |
| return self._collections.__len__() |
| |
| def __getitem__(self, k): |
| self.fill() |
| return self._collections.__getitem__(k) |
| |
| def __setitem__(self, k, v): |
| self.fill() |
| return self._collections.__setitem__(k,v) |
| |
| def __delitem__(self, k): |
| self.fill() |
| return self._collections.__delitem__(k) |
| |
| def __iter__(self): |
| self.fill() |
| return iter(self._collections.values()) |
| |
| def items(self): |
| self.fill() |
| return self._collections.items() |
| |
| def fill(self): |
| if self._collections: |
| return |
| else: |
| var_name = self.rel_obj.opts.object_name.lower() |
| collections = {} |
| orig = None |
| if hasattr(self.parent_manipulator, 'original_object'): |
| orig = self.parent_manipulator.original_object |
| orig_list = self.rel_obj.get_list(orig) |
| |
| for i, instance in enumerate(orig_list): |
| collection = {'original': instance} |
| for f in self.rel_obj.editable_fields(): |
| for field_name in f.get_manipulator_field_names(''): |
| full_field_name = '%s.%d.%s' % (var_name, i, field_name) |
| field = self.parent_manipulator[full_field_name] |
| data = field.extract_data(self.data) |
| errors = self.errors.get(full_field_name, []) |
| collection[field_name] = FormFieldWrapper(field, data, errors) |
| collections[i] = FormFieldCollection(collection) |
| self._collections = collections |
| |
| |
| class FormField(object): |
| """Abstract class representing a form field. |
| |
| Classes that extend FormField should define the following attributes: |
| field_name |
| The field's name for use by programs. |
| validator_list |
| A list of validation tests (callback functions) that the data for |
| this field must pass in order to be added or changed. |
| is_required |
| A Boolean. Is it a required field? |
| Subclasses should also implement a render(data) method, which is responsible |
| for rending the form field in XHTML. |
| """ |
| |
| def __str__(self): |
| return unicode(self).encode('utf-8') |
| |
| def __unicode__(self): |
| return self.render(u'') |
| |
| def __repr__(self): |
| return 'FormField "%s"' % self.field_name |
| |
| def prepare(self, new_data): |
| "Hook for doing something to new_data (in place) before validation." |
| pass |
| |
| def html2python(data): |
| "Hook for converting an HTML datatype (e.g. 'on' for checkboxes) to a Python type" |
| return data |
| html2python = staticmethod(html2python) |
| |
| def render(self, data): |
| raise NotImplementedError |
| |
| def get_member_name(self): |
| if hasattr(self, 'member_name'): |
| return self.member_name |
| else: |
| return self.field_name |
| |
| def extract_data(self, data_dict): |
| if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): |
| data = data_dict.getlist(self.get_member_name()) |
| else: |
| data = data_dict.get(self.get_member_name(), None) |
| if data is None: |
| data = '' |
| return data |
| |
| def convert_post_data(self, new_data): |
| name = self.get_member_name() |
| if self.field_name in new_data: |
| d = new_data.getlist(self.field_name) |
| try: |
| converted_data = [self.__class__.html2python(data) for data in d] |
| except ValueError: |
| converted_data = d |
| new_data.setlist(name, converted_data) |
| else: |
| try: |
| #individual fields deal with None values themselves |
| new_data.setlist(name, [self.__class__.html2python(None)]) |
| except EmptyValue: |
| new_data.setlist(name, []) |
| |
| |
| def run_validator(self, new_data, validator): |
| if self.is_required or new_data.get(self.field_name, False) or hasattr(validator, 'always_test'): |
| if hasattr(self, 'requires_data_list'): |
| validator(new_data.getlist(self.field_name), new_data) |
| else: |
| validator(new_data.get(self.field_name, ''), new_data) |
| |
| def get_validation_errors(self, new_data): |
| errors = {} |
| if self.is_required and not new_data.get(self.field_name, False): |
| errors.setdefault(self.field_name, []).append(ugettext('This field is required.')) |
| return errors |
| try: |
| for validator in self.validator_list: |
| try: |
| self.run_validator(new_data, validator) |
| except validators.ValidationError, e: |
| errors.setdefault(self.field_name, []).extend(e.messages) |
| # If a CriticalValidationError is raised, ignore any other ValidationErrors |
| # for this particular field |
| except validators.CriticalValidationError, e: |
| errors.setdefault(self.field_name, []).extend(e.messages) |
| return errors |
| |
| def get_id(self): |
| "Returns the HTML 'id' attribute for this form field." |
| return FORM_FIELD_ID_PREFIX + self.field_name |
| |
| #################### |
| # GENERIC WIDGETS # |
| #################### |
| |
| class TextField(FormField): |
| input_type = "text" |
| def __init__(self, field_name, length=30, max_length=None, is_required=False, validator_list=None, member_name=None): |
| if validator_list is None: validator_list = [] |
| self.field_name = field_name |
| self.length, self.max_length = length, max_length |
| self.is_required = is_required |
| self.validator_list = [self.isValidLength, self.hasNoNewlines] + validator_list |
| if member_name != None: |
| self.member_name = member_name |
| |
| def isValidLength(self, data, form): |
| if data and self.max_length and len(smart_unicode(data)) > self.max_length: |
| raise validators.ValidationError, ungettext("Ensure your text is less than %s character.", |
| "Ensure your text is less than %s characters.", self.max_length) % self.max_length |
| |
| def hasNoNewlines(self, data, form): |
| if data and '\n' in data: |
| raise validators.ValidationError, ugettext("Line breaks are not allowed here.") |
| |
| def render(self, data): |
| if data is None: |
| data = u'' |
| max_length = u'' |
| if self.max_length: |
| max_length = u'maxlength="%s" ' % self.max_length |
| return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ |
| (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '', |
| self.field_name, self.length, escape(data), max_length)) |
| |
| def html2python(data): |
| return data |
| html2python = staticmethod(html2python) |
| |
| class PasswordField(TextField): |
| input_type = "password" |
| |
| class LargeTextField(TextField): |
| def __init__(self, field_name, rows=10, cols=40, is_required=False, validator_list=None, max_length=None): |
| if validator_list is None: validator_list = [] |
| self.field_name = field_name |
| self.rows, self.cols, self.is_required = rows, cols, is_required |
| self.validator_list = validator_list[:] |
| if max_length: |
| self.validator_list.append(self.isValidLength) |
| self.max_length = max_length |
| |
| def render(self, data): |
| if data is None: |
| data = '' |
| return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ |
| (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'', |
| self.field_name, self.rows, self.cols, escape(data))) |
| |
| class HiddenField(FormField): |
| def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): |
| if validator_list is None: validator_list = [] |
| self.field_name, self.is_required = field_name, is_required |
| self.validator_list = validator_list[:] |
| |
| def render(self, data): |
| return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \ |
| (self.get_id(), self.field_name, escape(data))) |
| |
| class CheckboxField(FormField): |
| def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): |
| if validator_list is None: validator_list = [] |
| self.field_name = field_name |
| self.checked_by_default = checked_by_default |
| self.is_required = is_required |
| self.validator_list = validator_list[:] |
| |
| def render(self, data): |
| checked_html = '' |
| if data or (data is '' and self.checked_by_default): |
| checked_html = ' checked="checked"' |
| return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ |
| (self.get_id(), self.__class__.__name__, |
| self.field_name, checked_html)) |
| |
| def html2python(data): |
| "Convert value from browser ('on' or '') to a Python boolean" |
| if data == 'on': |
| return True |
| return False |
| html2python = staticmethod(html2python) |
| |
| class SelectField(FormField): |
| def __init__(self, field_name, choices=None, size=1, is_required=False, validator_list=None, member_name=None): |
| if validator_list is None: validator_list = [] |
| if choices is None: choices = [] |
| choices = [(k, smart_unicode(v, strings_only=True)) for k, v in choices] |
| self.field_name = field_name |
| # choices is a list of (value, human-readable key) tuples because order matters |
| self.choices, self.size, self.is_required = choices, size, is_required |
| self.validator_list = [self.isValidChoice] + validator_list |
| if member_name != None: |
| self.member_name = member_name |
| |
| def render(self, data): |
| output = [u'<select id="%s" class="v%s%s" name="%s" size="%s">' % \ |
| (self.get_id(), self.__class__.__name__, |
| self.is_required and u' required' or u'', self.field_name, self.size)] |
| str_data = smart_unicode(data) # normalize to string |
| for value, display_name in self.choices: |
| selected_html = u'' |
| if smart_unicode(value) == str_data: |
| selected_html = u' selected="selected"' |
| output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(display_name)))) |
| output.append(u' </select>') |
| return mark_safe(u'\n'.join(output)) |
| |
| def isValidChoice(self, data, form): |
| str_data = smart_unicode(data) |
| str_choices = [smart_unicode(item[0]) for item in self.choices] |
| if str_data not in str_choices: |
| raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data': str_data, 'choices': str_choices} |
| |
| class NullSelectField(SelectField): |
| "This SelectField converts blank fields to None" |
| def html2python(data): |
| if not data: |
| return None |
| return data |
| html2python = staticmethod(html2python) |
| |
| class RadioSelectField(FormField): |
| def __init__(self, field_name, choices=None, ul_class='', is_required=False, validator_list=None, member_name=None): |
| if validator_list is None: validator_list = [] |
| if choices is None: choices = [] |
| choices = [(k, smart_unicode(v)) for k, v in choices] |
| self.field_name = field_name |
| # choices is a list of (value, human-readable key) tuples because order matters |
| self.choices, self.is_required = choices, is_required |
| self.validator_list = [self.isValidChoice] + validator_list |
| self.ul_class = ul_class |
| if member_name != None: |
| self.member_name = member_name |
| |
| def render(self, data): |
| """ |
| Returns a special object, RadioFieldRenderer, that is iterable *and* |
| has a default unicode() rendered output. |
| |
| This allows for flexible use in templates. You can just use the default |
| rendering: |
| |
| {{ field_name }} |
| |
| ...which will output the radio buttons in an unordered list. |
| Or, you can manually traverse each radio option for special layout: |
| |
| {% for option in field_name.field_list %} |
| {{ option.field }} {{ option.label }}<br /> |
| {% endfor %} |
| """ |
| class RadioFieldRenderer: |
| def __init__(self, datalist, ul_class): |
| self.datalist, self.ul_class = datalist, ul_class |
| def __unicode__(self): |
| "Default unicode() output for this radio field -- a <ul>" |
| output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')] |
| output.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist]) |
| output.append(u'</ul>') |
| return mark_safe(u''.join(output)) |
| def __iter__(self): |
| for d in self.datalist: |
| yield d |
| def __len__(self): |
| return len(self.datalist) |
| datalist = [] |
| str_data = smart_unicode(data) # normalize to string |
| for i, (value, display_name) in enumerate(self.choices): |
| selected_html = '' |
| if smart_unicode(value) == str_data: |
| selected_html = u' checked="checked"' |
| datalist.append({ |
| 'value': value, |
| 'name': display_name, |
| 'field': mark_safe(u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \ |
| (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)), |
| 'label': mark_safe(u'<label for="%s">%s</label>' % \ |
| (self.get_id() + u'_' + unicode(i), display_name), |
| )}) |
| return RadioFieldRenderer(datalist, self.ul_class) |
| |
| def isValidChoice(self, data, form): |
| str_data = smart_unicode(data) |
| str_choices = [smart_unicode(item[0]) for item in self.choices] |
| if str_data not in str_choices: |
| raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':str_data, 'choices':str_choices} |
| |
| class NullBooleanField(SelectField): |
| "This SelectField provides 'Yes', 'No' and 'Unknown', mapping results to True, False or None" |
| def __init__(self, field_name, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| SelectField.__init__(self, field_name, choices=[('1', ugettext('Unknown')), ('2', ugettext('Yes')), ('3', ugettext('No'))], |
| is_required=is_required, validator_list=validator_list) |
| |
| def render(self, data): |
| if data is None: data = '1' |
| elif data == True: data = '2' |
| elif data == False: data = '3' |
| return SelectField.render(self, data) |
| |
| def html2python(data): |
| return {None: None, '1': None, '2': True, '3': False}[data] |
| html2python = staticmethod(html2python) |
| |
| class SelectMultipleField(SelectField): |
| requires_data_list = True |
| def render(self, data): |
| output = [u'<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \ |
| (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'', |
| self.field_name, self.size)] |
| str_data_list = map(smart_unicode, data) # normalize to strings |
| for value, choice in self.choices: |
| selected_html = u'' |
| if smart_unicode(value) in str_data_list: |
| selected_html = u' selected="selected"' |
| output.append(u' <option value="%s"%s>%s</option>' % (escape(value), selected_html, force_unicode(escape(choice)))) |
| output.append(u' </select>') |
| return mark_safe(u'\n'.join(output)) |
| |
| def isValidChoice(self, field_data, all_data): |
| # data is something like ['1', '2', '3'] |
| str_choices = [smart_unicode(item[0]) for item in self.choices] |
| for val in map(smart_unicode, field_data): |
| if val not in str_choices: |
| raise validators.ValidationError, ugettext("Select a valid choice; '%(data)s' is not in %(choices)s.") % {'data':val, 'choices':str_choices} |
| |
| def html2python(data): |
| if data is None: |
| raise EmptyValue |
| return data |
| html2python = staticmethod(html2python) |
| |
| class CheckboxSelectMultipleField(SelectMultipleField): |
| """ |
| This has an identical interface to SelectMultipleField, except the rendered |
| widget is different. Instead of a <select multiple>, this widget outputs a |
| <ul> of <input type="checkbox">es. |
| |
| Of course, that results in multiple form elements for the same "single" |
| field, so this class's prepare() method flattens the split data elements |
| back into the single list that validators, renderers and save() expect. |
| """ |
| requires_data_list = True |
| def __init__(self, field_name, choices=None, ul_class='', validator_list=None): |
| if validator_list is None: validator_list = [] |
| if choices is None: choices = [] |
| self.ul_class = ul_class |
| SelectMultipleField.__init__(self, field_name, choices, size=1, is_required=False, validator_list=validator_list) |
| |
| def prepare(self, new_data): |
| # new_data has "split" this field into several fields, so flatten it |
| # back into a single list. |
| data_list = [] |
| for value, readable_value in self.choices: |
| if new_data.get('%s%s' % (self.field_name, value), '') == 'on': |
| data_list.append(value) |
| new_data.setlist(self.field_name, data_list) |
| |
| def render(self, data): |
| output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')] |
| str_data_list = map(smart_unicode, data) # normalize to strings |
| for value, choice in self.choices: |
| checked_html = u'' |
| if smart_unicode(value) in str_data_list: |
| checked_html = u' checked="checked"' |
| field_name = u'%s%s' % (self.field_name, value) |
| output.append(u'<li><input type="checkbox" id="%s" class="v%s" name="%s"%s value="on" /> <label for="%s">%s</label></li>' % \ |
| (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, |
| self.get_id() + escape(value), choice)) |
| output.append(u'</ul>') |
| return mark_safe(u'\n'.join(output)) |
| |
| #################### |
| # FILE UPLOADS # |
| #################### |
| |
| class FileUploadField(FormField): |
| def __init__(self, field_name, is_required=False, validator_list=None, max_length=None): |
| if validator_list is None: validator_list = [] |
| self.field_name, self.is_required = field_name, is_required |
| self.validator_list = [self.isNonEmptyFile] + validator_list |
| |
| def isNonEmptyFile(self, new_data, all_data): |
| if hasattr(new_data, 'upload_errors'): |
| upload_errors = new_data.upload_errors() |
| if upload_errors: |
| raise validators.CriticalValidationError, upload_errors |
| try: |
| file_size = new_data.size |
| except AttributeError: |
| file_size = len(new_data['content']) |
| if not file_size: |
| raise validators.CriticalValidationError, ugettext("The submitted file is empty.") |
| |
| def render(self, data): |
| return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \ |
| (self.get_id(), self.__class__.__name__, self.field_name)) |
| |
| def prepare(self, new_data): |
| if hasattr(new_data, 'upload_errors'): |
| upload_errors = new_data.upload_errors() |
| new_data[self.field_name] = { '_file_upload_error': upload_errors } |
| |
| def html2python(data): |
| if data is None: |
| raise EmptyValue |
| return data |
| html2python = staticmethod(html2python) |
| |
| class ImageUploadField(FileUploadField): |
| "A FileUploadField that raises CriticalValidationError if the uploaded file isn't an image." |
| def __init__(self, *args, **kwargs): |
| FileUploadField.__init__(self, *args, **kwargs) |
| self.validator_list.insert(0, self.isValidImage) |
| |
| def isValidImage(self, field_data, all_data): |
| try: |
| validators.isValidImage(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| #################### |
| # INTEGERS/FLOATS # |
| #################### |
| |
| class IntegerField(TextField): |
| def __init__(self, field_name, length=10, max_length=None, is_required=False, validator_list=None, member_name=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isInteger] + validator_list |
| if member_name is not None: |
| self.member_name = member_name |
| TextField.__init__(self, field_name, length, max_length, is_required, validator_list) |
| |
| def isInteger(self, field_data, all_data): |
| try: |
| validators.isInteger(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def html2python(data): |
| if data == '' or data is None: |
| return None |
| return int(data) |
| html2python = staticmethod(html2python) |
| |
| class SmallIntegerField(IntegerField): |
| def __init__(self, field_name, length=5, max_length=5, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isSmallInteger] + validator_list |
| IntegerField.__init__(self, field_name, length, max_length, is_required, validator_list) |
| |
| def isSmallInteger(self, field_data, all_data): |
| if not -32768 <= int(field_data) <= 32767: |
| raise validators.CriticalValidationError, ugettext("Enter a whole number between -32,768 and 32,767.") |
| |
| class PositiveIntegerField(IntegerField): |
| def __init__(self, field_name, length=10, max_length=None, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isPositive] + validator_list |
| IntegerField.__init__(self, field_name, length, max_length, is_required, validator_list) |
| |
| def isPositive(self, field_data, all_data): |
| if int(field_data) < 0: |
| raise validators.CriticalValidationError, ugettext("Enter a positive number.") |
| |
| class PositiveSmallIntegerField(IntegerField): |
| def __init__(self, field_name, length=5, max_length=None, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isPositiveSmall] + validator_list |
| IntegerField.__init__(self, field_name, length, max_length, is_required, validator_list) |
| |
| def isPositiveSmall(self, field_data, all_data): |
| if not 0 <= int(field_data) <= 32767: |
| raise validators.CriticalValidationError, ugettext("Enter a whole number between 0 and 32,767.") |
| |
| class FloatField(TextField): |
| def __init__(self, field_name, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [validators.isValidFloat] + validator_list |
| TextField.__init__(self, field_name, is_required=is_required, validator_list=validator_list) |
| |
| def html2python(data): |
| if data == '' or data is None: |
| return None |
| return float(data) |
| html2python = staticmethod(html2python) |
| |
| class DecimalField(TextField): |
| def __init__(self, field_name, max_digits, decimal_places, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| self.max_digits, self.decimal_places = max_digits, decimal_places |
| validator_list = [self.isValidDecimal] + validator_list |
| # Initialise the TextField, making sure it's large enough to fit the number with a - sign and a decimal point. |
| super(DecimalField, self).__init__(field_name, max_digits+2, max_digits+2, is_required, validator_list) |
| |
| def isValidDecimal(self, field_data, all_data): |
| v = validators.IsValidDecimal(self.max_digits, self.decimal_places) |
| try: |
| v(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def html2python(data): |
| if data == '' or data is None: |
| return None |
| try: |
| import decimal |
| except ImportError: |
| from django.utils import _decimal as decimal |
| try: |
| return decimal.Decimal(data) |
| except decimal.InvalidOperation, e: |
| raise ValueError, e |
| html2python = staticmethod(html2python) |
| |
| #################### |
| # DATES AND TIMES # |
| #################### |
| |
| class DatetimeField(TextField): |
| """A FormField that automatically converts its data to a datetime.datetime object. |
| The data should be in the format YYYY-MM-DD HH:MM:SS.""" |
| def __init__(self, field_name, length=30, max_length=None, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| self.field_name = field_name |
| self.length, self.max_length = length, max_length |
| self.is_required = is_required |
| self.validator_list = [validators.isValidANSIDatetime] + validator_list |
| |
| def html2python(data): |
| "Converts the field into a datetime.datetime object" |
| import datetime |
| try: |
| date, time = data.split() |
| y, m, d = date.split('-') |
| timebits = time.split(':') |
| h, mn = timebits[:2] |
| if len(timebits) > 2: |
| s = int(timebits[2]) |
| else: |
| s = 0 |
| return datetime.datetime(int(y), int(m), int(d), int(h), int(mn), s) |
| except ValueError: |
| return None |
| html2python = staticmethod(html2python) |
| |
| class DateField(TextField): |
| """A FormField that automatically converts its data to a datetime.date object. |
| The data should be in the format YYYY-MM-DD.""" |
| def __init__(self, field_name, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidDate] + validator_list |
| TextField.__init__(self, field_name, length=10, max_length=10, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidDate(self, field_data, all_data): |
| try: |
| validators.isValidANSIDate(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def html2python(data): |
| "Converts the field into a datetime.date object" |
| import time, datetime |
| try: |
| time_tuple = time.strptime(data, '%Y-%m-%d') |
| return datetime.date(*time_tuple[0:3]) |
| except (ValueError, TypeError): |
| return None |
| html2python = staticmethod(html2python) |
| |
| class TimeField(TextField): |
| """A FormField that automatically converts its data to a datetime.time object. |
| The data should be in the format HH:MM:SS or HH:MM:SS.mmmmmm.""" |
| def __init__(self, field_name, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidTime] + validator_list |
| TextField.__init__(self, field_name, length=8, max_length=8, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidTime(self, field_data, all_data): |
| try: |
| validators.isValidANSITime(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def html2python(data): |
| "Converts the field into a datetime.time object" |
| import time, datetime |
| try: |
| part_list = data.split('.') |
| try: |
| time_tuple = time.strptime(part_list[0], '%H:%M:%S') |
| except ValueError: # seconds weren't provided |
| time_tuple = time.strptime(part_list[0], '%H:%M') |
| t = datetime.time(*time_tuple[3:6]) |
| if (len(part_list) == 2): |
| t = t.replace(microsecond=int(part_list[1])) |
| return t |
| except (ValueError, TypeError, AttributeError): |
| return None |
| html2python = staticmethod(html2python) |
| |
| #################### |
| # INTERNET-RELATED # |
| #################### |
| |
| class EmailField(TextField): |
| "A convenience FormField for validating e-mail addresses" |
| def __init__(self, field_name, length=50, max_length=75, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidEmail] + validator_list |
| TextField.__init__(self, field_name, length, max_length=max_length, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidEmail(self, field_data, all_data): |
| try: |
| validators.isValidEmail(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| class URLField(TextField): |
| "A convenience FormField for validating URLs" |
| def __init__(self, field_name, length=50, max_length=200, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidURL] + validator_list |
| TextField.__init__(self, field_name, length=length, max_length=max_length, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidURL(self, field_data, all_data): |
| try: |
| validators.isValidURL(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| class IPAddressField(TextField): |
| def __init__(self, field_name, length=15, max_length=15, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidIPAddress] + validator_list |
| TextField.__init__(self, field_name, length=length, max_length=max_length, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidIPAddress(self, field_data, all_data): |
| try: |
| validators.isValidIPAddress4(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def html2python(data): |
| return data or None |
| html2python = staticmethod(html2python) |
| |
| #################### |
| # MISCELLANEOUS # |
| #################### |
| |
| class FilePathField(SelectField): |
| "A SelectField whose choices are the files in a given directory." |
| def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=None, max_length=None): |
| import os |
| from django.db.models import BLANK_CHOICE_DASH |
| if match is not None: |
| import re |
| match_re = re.compile(match) |
| choices = not is_required and BLANK_CHOICE_DASH[:] or [] |
| if recursive: |
| for root, dirs, files in os.walk(path): |
| for f in files: |
| if match is None or match_re.search(f): |
| f = os.path.join(root, f) |
| choices.append((f, f.replace(path, "", 1))) |
| else: |
| try: |
| for f in os.listdir(path): |
| full_file = os.path.join(path, f) |
| if os.path.isfile(full_file) and (match is None or match_re.search(f)): |
| choices.append((full_file, f)) |
| except OSError: |
| pass |
| SelectField.__init__(self, field_name, choices, 1, is_required, validator_list) |
| |
| class PhoneNumberField(TextField): |
| "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" |
| def __init__(self, field_name, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidPhone] + validator_list |
| TextField.__init__(self, field_name, length=12, max_length=12, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidPhone(self, field_data, all_data): |
| try: |
| validators.isValidPhone(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| class USStateField(TextField): |
| "A convenience FormField for validating U.S. states (e.g. 'IL')" |
| def __init__(self, field_name, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isValidUSState] + validator_list |
| TextField.__init__(self, field_name, length=2, max_length=2, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isValidUSState(self, field_data, all_data): |
| try: |
| validators.isValidUSState(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def html2python(data): |
| if data: |
| return data.upper() # Should always be stored in upper case |
| return data |
| html2python = staticmethod(html2python) |
| |
| class CommaSeparatedIntegerField(TextField): |
| "A convenience FormField for validating comma-separated integer fields" |
| def __init__(self, field_name, max_length=None, is_required=False, validator_list=None): |
| if validator_list is None: validator_list = [] |
| validator_list = [self.isCommaSeparatedIntegerList] + validator_list |
| TextField.__init__(self, field_name, length=20, max_length=max_length, |
| is_required=is_required, validator_list=validator_list) |
| |
| def isCommaSeparatedIntegerList(self, field_data, all_data): |
| try: |
| validators.isCommaSeparatedIntegerList(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |
| |
| def render(self, data): |
| if data is None: |
| data = u'' |
| elif isinstance(data, (list, tuple)): |
| data = u','.join(data) |
| return super(CommaSeparatedIntegerField, self).render(data) |
| |
| class RawIdAdminField(CommaSeparatedIntegerField): |
| def html2python(data): |
| if data: |
| return data.split(',') |
| else: |
| return [] |
| html2python = staticmethod(html2python) |
| |
| class XMLLargeTextField(LargeTextField): |
| """ |
| A LargeTextField with an XML validator. The schema_path argument is the |
| full path to a Relax NG compact schema to validate against. |
| """ |
| def __init__(self, field_name, schema_path, **kwargs): |
| self.schema_path = schema_path |
| kwargs.setdefault('validator_list', []).insert(0, self.isValidXML) |
| LargeTextField.__init__(self, field_name, **kwargs) |
| |
| def isValidXML(self, field_data, all_data): |
| v = validators.RelaxNGCompact(self.schema_path) |
| try: |
| v(field_data, all_data) |
| except validators.ValidationError, e: |
| raise validators.CriticalValidationError, e.messages |