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