Django

Code

Show
Ignore:
Timestamp:
12/21/06 19:44:34 (2 years ago)
Author:
adrian
Message:

Changed django.forms to remove duplicate code and import from django.oldforms instead

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/trunk/django/forms/__init__.py

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