Django

Code

Changeset 3945

Show
Ignore:
Timestamp:
10/28/06 15:34:37 (2 years ago)
Author:
adrian
Message:

Split django.newforms into forms, fields, widgets, util. Also moved unit tests from docstrings to a standalone module in tests/regressiontests/forms, to save docstring memory overhead, keep code readable and fit our exisitng convention

Files:

Legend:

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

    r3944 r3945  
    1111    ValidationWarning 
    1212    "This form field requires foo.js" and form.js_includes() 
    13  
    14 # Form ######################################################################## 
    15  
    16 >>> class Person(Form): 
    17 ...     first_name = CharField() 
    18 ...     last_name = CharField() 
    19 ...     birthday = DateField() 
    20 >>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'}) 
    21 >>> p.errors() 
    22 {} 
    23 >>> p.is_valid() 
    24 True 
    25 >>> p.errors().as_ul() 
    26 u'' 
    27 >>> p.errors().as_text() 
    28 u'' 
    29 >>> p.to_python() 
    30 {'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)} 
    31 >>> print p['first_name'] 
    32 <input type="text" name="first_name" value="John" /> 
    33 >>> print p['last_name'] 
    34 <input type="text" name="last_name" value="Lennon" /> 
    35 >>> print p['birthday'] 
    36 <input type="text" name="birthday" value="1940-10-09" /> 
    37 >>> for boundfield in p: 
    38 ...     print boundfield 
    39 <input type="text" name="first_name" value="John" /> 
    40 <input type="text" name="last_name" value="Lennon" /> 
    41 <input type="text" name="birthday" value="1940-10-09" /> 
    42  
    43 >>> p = Person({'last_name': u'Lennon'}) 
    44 >>> p.errors() 
    45 {'first_name': [u'This field is required.'], 'birthday': [u'This field is required.']} 
    46 >>> p.is_valid() 
    47 False 
    48 >>> p.errors().as_ul() 
    49 u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is required.</li></ul></li><li>birthday<ul class="errorlist"><li>This field is required.</li></ul></li></ul>' 
    50 >>> print p.errors().as_text() 
    51 * first_name 
    52   * This field is required. 
    53 * birthday 
    54   * This field is required. 
    55 >>> p.to_python() 
    56 >>> repr(p.to_python()) 
    57 'None' 
    58 >>> p['first_name'].errors 
    59 [u'This field is required.'] 
    60 >>> p['first_name'].errors.as_ul() 
    61 u'<ul class="errorlist"><li>This field is required.</li></ul>' 
    62 >>> p['first_name'].errors.as_text() 
    63 u'* This field is required.' 
    64  
    65 >>> p = Person() 
    66 >>> print p['first_name'] 
    67 <input type="text" name="first_name" /> 
    68 >>> print p['last_name'] 
    69 <input type="text" name="last_name" /> 
    70 >>> print p['birthday'] 
    71 <input type="text" name="birthday" /> 
    72  
    73 >>> class SignupForm(Form): 
    74 ...     email = EmailField() 
    75 ...     get_spam = BooleanField() 
    76 >>> f = SignupForm() 
    77 >>> print f['email'] 
    78 <input type="text" name="email" /> 
    79 >>> print f['get_spam'] 
    80 <input type="checkbox" name="get_spam" /> 
    81  
    82 >>> f = SignupForm({'email': 'test@example.com', 'get_spam': True}) 
    83 >>> print f['email'] 
    84 <input type="text" name="email" value="test@example.com" /> 
    85 >>> print f['get_spam'] 
    86 <input checked="checked" type="checkbox" name="get_spam" /> 
    87  
    88 Any Field can have a Widget class passed to its constructor: 
    89 >>> class ContactForm(Form): 
    90 ...     subject = CharField() 
    91 ...     message = CharField(widget=Textarea) 
    92 >>> f = ContactForm() 
    93 >>> print f['subject'] 
    94 <input type="text" name="subject" /> 
    95 >>> print f['message'] 
    96 <textarea name="message"></textarea> 
    97  
    98 as_textarea() and as_text() are shortcuts for changing the output widget type: 
    99 >>> f['subject'].as_textarea() 
    100 u'<textarea name="subject"></textarea>' 
    101 >>> f['message'].as_text() 
    102 u'<input type="text" name="message" />' 
    103  
    104 The 'widget' parameter to a Field can also be an instance: 
    105 >>> class ContactForm(Form): 
    106 ...     subject = CharField() 
    107 ...     message = CharField(widget=Textarea(attrs={'rows': 80, 'cols': 20})) 
    108 >>> f = ContactForm() 
    109 >>> print f['message'] 
    110 <textarea rows="80" cols="20" name="message"></textarea> 
    111  
    112 Instance-level attrs are *not* carried over to as_textarea() and as_text(): 
    113 >>> f['message'].as_text() 
    114 u'<input type="text" name="message" />' 
    115 >>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}) 
    116 >>> f['subject'].as_textarea() 
    117 u'<textarea name="subject">Hello</textarea>' 
    118 >>> f['message'].as_text() 
    119 u'<input type="text" name="message" value="I love you." />' 
    12013""" 
    12114 
    122 from django.utils.html import escape 
    123 import datetime 
    124 import re 
    125 import time 
    126  
    127 # Default encoding for input byte strings. 
    128 DEFAULT_ENCODING = 'utf-8' # TODO: First look at django.conf.settings, then fall back to this. 
    129  
    130 def smart_unicode(s): 
    131     if not isinstance(s, unicode): 
    132         s = unicode(s, DEFAULT_ENCODING) 
    133     return s 
    134  
    135 ################### 
    136 # VALIDATOR STUFF # 
    137 ################### 
    138  
    139 class ErrorDict(dict): 
    140     """ 
    141     A collection of errors that knows how to display itself in various formats. 
    142  
    143     The dictionary keys are the field names, and the values are the errors. 
    144     """ 
    145     def __str__(self): 
    146         return self.as_ul() 
    147  
    148     def as_ul(self): 
    149         if not self: return u'' 
    150         return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s%s</li>' % (k, v) for k, v in self.items()]) 
    151  
    152     def as_text(self): 
    153         return u'\n'.join([u'* %s\n%s' % (k, u'\n'.join([u'  * %s' % i for i in v])) for k, v in self.items()]) 
    154  
    155 class ErrorList(list): 
    156     """ 
    157     A collection of errors that knows how to display itself in various formats. 
    158     """ 
    159     def __str__(self): 
    160         return self.as_ul() 
    161  
    162     def as_ul(self): 
    163         if not self: return u'' 
    164         return u'<ul class="errorlist">%s</ul>' % ''.join([u'<li>%s</li>' % e for e in self]) 
    165  
    166     def as_text(self): 
    167         if not self: return u'' 
    168         return u'\n'.join([u'* %s' % e for e in self]) 
    169  
    170 class ValidationError(Exception): 
    171     def __init__(self, message): 
    172         "ValidationError can be passed a string or a list." 
    173         if isinstance(message, list): 
    174             self.messages = ErrorList([smart_unicode(msg) for msg in message]) 
    175         else: 
    176             assert isinstance(message, basestring), ("%s should be a basestring" % repr(message)) 
    177             message = smart_unicode(message) 
    178             self.messages = ErrorList([message]) 
    179  
    180     def __str__(self): 
    181         # This is needed because, without a __str__(), printing an exception 
    182         # instance would result in this: 
    183         # AttributeError: ValidationError instance has no attribute 'args' 
    184         # See http://www.python.org/doc/current/tut/node10.html#handling 
    185         return repr(self.messages) 
    186  
    187 ################ 
    188 # HTML WIDGETS # 
    189 ################ 
    190  
    191 # Converts a dictionary to a single string with key="value", XML-style. 
    192 # Assumes keys do not need to be XML-escaped. 
    193 flatatt = lambda attrs: ' '.join(['%s="%s"' % (k, escape(v)) for k, v in attrs.items()]) 
    194  
    195 class Widget(object): 
    196     def __init__(self, attrs=None): 
    197         self.attrs = attrs or {} 
    198  
    199     def render(self, name, value): 
    200         raise NotImplementedError 
    201  
    202 class TextInput(Widget): 
    203     """ 
    204     >>> w = TextInput() 
    205     >>> w.render('email', '') 
    206     u'<input type="text" name="email" />' 
    207     >>> w.render('email', None) 
    208     u'<input type="text" name="email" />' 
    209     >>> w.render('email', 'test@example.com') 
    210     u'<input type="text" name="email" value="test@example.com" />' 
    211     >>> w.render('email', 'some "quoted" & ampersanded value') 
    212     u'<input type="text" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />' 
    213     >>> w.render('email', 'test@example.com', attrs={'class': 'fun'}) 
    214     u'<input type="text" name="email" value="test@example.com" class="fun" />' 
    215  
    216     You can also pass 'attrs' to the constructor: 
    217     >>> w = TextInput(attrs={'class': 'fun'}) 
    218     >>> w.render('email', '') 
    219     u'<input type="text" class="fun" name="email" />' 
    220     >>> w.render('email', 'foo@example.com') 
    221     u'<input type="text" class="fun" value="foo@example.com" name="email" />' 
    222  
    223     'attrs' passed to render() get precedence over those passed to the constructor: 
    224     >>> w = TextInput(attrs={'class': 'pretty'}) 
    225     >>> w.render('email', '', attrs={'class': 'special'}) 
    226     u'<input type="text" class="special" name="email" />' 
    227     """ 
    228     def render(self, name, value, attrs=None): 
    229         if value in EMPTY_VALUES: value = '' 
    230         final_attrs = dict(self.attrs, type='text', name=name) 
    231         if attrs: 
    232             final_attrs.update(attrs) 
    233         if value != '': final_attrs['value'] = value # Only add the 'value' attribute if a value is non-empty. 
    234         return u'<input %s />' % flatatt(final_attrs) 
    235  
    236 class Textarea(Widget): 
    237     """ 
    238     >>> w = Textarea() 
    239     >>> w.render('msg', '') 
    240     u'<textarea name="msg"></textarea>' 
    241     >>> w.render('msg', None) 
    242     u'<textarea name="msg"></textarea>' 
    243     >>> w.render('msg', 'value') 
    244     u'<textarea name="msg">value</textarea>' 
    245     >>> w.render('msg', 'some "quoted" & ampersanded value') 
    246     u'<textarea name="msg">some &quot;quoted&quot; &amp; ampersanded value</textarea>' 
    247     >>> w.render('msg', 'value', attrs={'class': 'pretty'}) 
    248     u'<textarea name="msg" class="pretty">value</textarea>' 
    249  
    250     You can also pass 'attrs' to the constructor: 
    251     >>> w = Textarea(attrs={'class': 'pretty'}) 
    252     >>> w.render('msg', '') 
    253     u'<textarea class="pretty" name="msg"></textarea>' 
    254     >>> w.render('msg', 'example') 
    255     u'<textarea class="pretty" name="msg">example</textarea>' 
    256  
    257     'attrs' passed to render() get precedence over those passed to the constructor: 
    258     >>> w = Textarea(attrs={'class': 'pretty'}) 
    259     >>> w.render('msg', '', attrs={'class': 'special'}) 
    260     u'<textarea class="special" name="msg"></textarea>' 
    261     """ 
    262     def render(self, name, value, attrs=None): 
    263         if value in EMPTY_VALUES: value = '' 
    264         final_attrs = dict(self.attrs, name=name) 
    265         if attrs: 
    266             final_attrs.update(attrs) 
    267         return u'<textarea %s>%s</textarea>' % (flatatt(final_attrs), escape(value)) 
    268  
    269 class CheckboxInput(Widget): 
    270     """ 
    271     >>> w = CheckboxInput() 
    272     >>> w.render('is_cool', '') 
    273     u'<input type="checkbox" name="is_cool" />' 
    274     >>> w.render('is_cool', False) 
    275     u'<input type="checkbox" name="is_cool" />' 
    276     >>> w.render('is_cool', True) 
    277     u'<input checked="checked" type="checkbox" name="is_cool" />' 
    278     >>> w.render('is_cool', False, attrs={'class': 'pretty'}) 
    279     u'<input type="checkbox" name="is_cool" class="pretty" />' 
    280  
    281     You can also pass 'attrs' to the constructor: 
    282     >>> w = CheckboxInput(attrs={'class': 'pretty'}) 
    283     >>> w.render('is_cool', '') 
    284     u'<input type="checkbox" class="pretty" name="is_cool" />' 
    285  
    286     'attrs' passed to render() get precedence over those passed to the constructor: 
    287     >>> w = CheckboxInput(attrs={'class': 'pretty'}) 
    288     >>> w.render('is_cool', '', attrs={'class': 'special'}) 
    289     u'<input type="checkbox" class="special" name="is_cool" />' 
    290     """ 
    291     def render(self, name, value, attrs=None): 
    292         final_attrs = dict(self.attrs, type='checkbox', name=name) 
    293         if attrs: 
    294             final_attrs.update(attrs) 
    295         if value: final_attrs['checked'] = 'checked' 
    296         return u'<input %s />' % flatatt(final_attrs) 
    297  
    298 ########## 
    299 # FIELDS # 
    300 ########## 
    301  
    302 # These values, if given to to_python(), will trigger the self.required check. 
    303 EMPTY_VALUES = (None, '') 
    304  
    305 class Field(object): 
    306     widget = TextInput # Default widget to use when rendering this type of Field. 
    307  
    308     def __init__(self, required=True, widget=None): 
    309         self.required = required 
    310         widget = widget or self.widget 
    311         if isinstance(widget, type): 
    312             widget = widget() 
    313         self.widget = widget 
    314  
    315     def to_python(self, value): 
    316         """ 
    317         Validates the given value and returns its "normalized" value as an 
    318         appropriate Python object. 
    319  
    320         Raises ValidationError for any errors. 
    321         """ 
    322         if self.required and value in EMPTY_VALUES: 
    323             raise ValidationError(u'This field is required.') 
    324         return value 
    325  
    326 class CharField(Field): 
    327     """ 
    328     >>> f = CharField(required=False) 
    329     >>> f.to_python(1) 
    330     u'1' 
    331     >>> f.to_python('hello') 
    332     u'hello' 
    333     >>> f.to_python(None) 
    334     u'' 
    335     >>> f.to_python([1, 2, 3]) 
    336     u'[1, 2, 3]' 
    337  
    338     CharField accepts an optional max_length parameter: 
    339     >>> f = CharField(max_length=10, required=False) 
    340     >>> f.to_python('') 
    341     u'' 
    342     >>> f.to_python('12345') 
    343     u'12345' 
    344     >>> f.to_python('1234567890') 
    345     u'1234567890' 
    346     >>> f.to_python('1234567890a') 
    347     Traceback (most recent call last): 
    348     ... 
    349     ValidationError: [u'Ensure this value has at most 10 characters.'] 
    350  
    351     CharField accepts an optional min_length parameter: 
    352     >>> f = CharField(min_length=10, required=False) 
    353     >>> f.to_python('') 
    354     Traceback (most recent call last): 
    355     ... 
    356     ValidationError: [u'Ensure this value has at least 10 characters.'] 
    357     >>> f.to_python('12345') 
    358     Traceback (most recent call last): 
    359     ... 
    360     ValidationError: [u'Ensure this value has at least 10 characters.'] 
    361     >>> f.to_python('1234567890') 
    362     u'1234567890' 
    363     >>> f.to_python('1234567890a') 
    364     u'1234567890a' 
    365     """ 
    366     def __init__(self, max_length=None, min_length=None, required=True, widget=None): 
    367         Field.__init__(self, required, widget) 
    368         self.max_length, self.min_length = max_length, min_length 
    369  
    370     def to_python(self, value): 
    371         "Validates max_length and min_length. Returns a Unicode object." 
    372         Field.to_python(self, value) 
    373         if value in EMPTY_VALUES: value = u'' 
    374         if not isinstance(value, basestring): 
    375             value = unicode(str(value), DEFAULT_ENCODING) 
    376         elif not isinstance(value, unicode): 
    377             value = unicode(value, DEFAULT_ENCODING) 
    378         if self.max_length is not None and len(value) > self.max_length: 
    379             raise ValidationError(u'Ensure this value has at most %d characters.' % self.max_length) 
    380         if self.min_length is not None and len(value) < self.min_length: 
    381             raise ValidationError(u'Ensure this value has at least %d characters.' % self.min_length) 
    382         return value 
    383  
    384 class IntegerField(Field): 
    385     """ 
    386     >>> f = IntegerField() 
    387     >>> f.to_python('1') 
    388     1 
    389     >>> isinstance(f.to_python('1'), int) 
    390     True 
    391     >>> f.to_python('23') 
    392     23 
    393     >>> f.to_python('a') 
    394     Traceback (most recent call last): 
    395     ... 
    396     ValidationError: [u'Enter a whole number.'] 
    397     >>> f.to_python('1 ') 
    398     1 
    399     >>> f.to_python(' 1') 
    400     1 
    401     >>> f.to_python(' 1 ') 
    402     1 
    403     >>> f.to_python('1a') 
    404     Traceback (most recent call last): 
    405     ... 
    406     ValidationError: [u'Enter a whole number.'] 
    407     """ 
    408     def to_python(self, value): 
    409         """ 
    410         Validates that int() can be called on the input. Returns the result 
    411         of int(). 
    412         """ 
    413         super(IntegerField, self).to_python(value) 
    414         try: 
    415             return int(value) 
    416         except (ValueError, TypeError): 
    417             raise ValidationError(u'Enter a whole number.') 
    418  
    419 DEFAULT_DATE_INPUT_FORMATS = ( 
    420     '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06' 
    421     '%b %d %Y', '%b %d, %Y',            # 'Oct 25 2006', 'Oct 25, 2006' 
    422     '%d %b %Y', '%d %b, %Y',            # '25 Oct 2006', '25 Oct, 2006' 
    423     '%B %d %Y', '%B %d, %Y',            # 'October 25 2006', 'October 25, 2006' 
    424     '%d %B %Y', '%d %B, %Y',            # '25 October 2006', '25 October, 2006' 
    425 
    426  
    427 class DateField(Field): 
    428     """ 
    429     >>> import datetime 
    430     >>> f = DateField() 
    431     >>> f.to_python(datetime.date(2006, 10, 25)) 
    432     datetime.date(2006, 10, 25) 
    433     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) 
    434     datetime.date(2006, 10, 25) 
    435     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59)) 
    436     datetime.date(2006, 10, 25) 
    437     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) 
    438     datetime.date(2006, 10, 25) 
    439     >>> f.to_python('2006-10-25') 
    440     datetime.date(2006, 10, 25) 
    441     >>> f.to_python('10/25/2006') 
    442     datetime.date(2006, 10, 25) 
    443     >>> f.to_python('10/25/06') 
    444     datetime.date(2006, 10, 25) 
    445     >>> f.to_python('Oct 25 2006') 
    446     datetime.date(2006, 10, 25) 
    447     >>> f.to_python('October 25 2006') 
    448     datetime.date(2006, 10, 25) 
    449     >>> f.to_python('October 25, 2006') 
    450     datetime.date(2006, 10, 25) 
    451     >>> f.to_python('25 October 2006') 
    452     datetime.date(2006, 10, 25) 
    453     >>> f.to_python('25 October, 2006') 
    454     datetime.date(2006, 10, 25) 
    455     >>> f.to_python('2006-4-31') 
    456     Traceback (most recent call last): 
    457     ... 
    458     ValidationError: [u'Enter a valid date.'] 
    459     >>> f.to_python('200a-10-25') 
    460     Traceback (most recent call last): 
    461     ... 
    462     ValidationError: [u'Enter a valid date.'] 
    463     >>> f.to_python('25/10/06') 
    464     Traceback (most recent call last): 
    465     ... 
    466     ValidationError: [u'Enter a valid date.'] 
    467     >>> f.to_python(None) 
    468     Traceback (most recent call last): 
    469     ... 
    470     ValidationError: [u'This field is required.'] 
    471  
    472     >>> f = DateField(required=False) 
    473     >>> f.to_python(None) 
    474     >>> repr(f.to_python(None)) 
    475     'None' 
    476     >>> f.to_python('') 
    477     >>> repr(f.to_python('')) 
    478     'None' 
    479  
    480     DateField accepts an optional input_formats parameter: 
    481     >>> f = DateField(input_formats=['%Y %m %d']) 
    482     >>> f.to_python(datetime.date(2006, 10, 25)) 
    483     datetime.date(2006, 10, 25) 
    484     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) 
    485     datetime.date(2006, 10, 25) 
    486     >>> f.to_python('2006 10 25') 
    487     datetime.date(2006, 10, 25) 
    488  
    489     The input_formats parameter overrides all default input formats, 
    490     so the default formats won't work unless you specify them: 
    491     >>> f.to_python('2006-10-25') 
    492     Traceback (most recent call last): 
    493     ... 
    494     ValidationError: [u'Enter a valid date.'] 
    495     >>> f.to_python('10/25/2006') 
    496     Traceback (most recent call last): 
    497     ... 
    498     ValidationError: [u'Enter a valid date.'] 
    499     >>> f.to_python('10/25/06') 
    500     Traceback (most recent call last): 
    501     ... 
    502     ValidationError: [u'Enter a valid date.'] 
    503     """ 
    504     def __init__(self, input_formats=None, required=True, widget=None): 
    505         Field.__init__(self, required, widget) 
    506         self.input_formats = input_formats or DEFAULT_DATE_INPUT_FORMATS 
    507  
    508     def to_python(self, value): 
    509         """ 
    510         Validates that the input can be converted to a date. Returns a Python 
    511         datetime.date object. 
    512         """ 
    513         Field.to_python(self, value) 
    514         if value in EMPTY_VALUES: 
    515             return None 
    516         if isinstance(value, datetime.datetime): 
    517             return value.date() 
    518         if isinstance(value, datetime.date): 
    519             return value 
    520         for format in self.input_formats: 
    521             try: 
    522                 return datetime.date(*time.strptime(value, format)[:3]) 
    523             except ValueError: 
    524                 continue 
    525         raise ValidationError(u'Enter a valid date.') 
    526  
    527 DEFAULT_DATETIME_INPUT_FORMATS = ( 
    528     '%Y-%m-%d %H:%M:%S',     # '2006-10-25 14:30:59' 
    529     '%Y-%m-%d %H:%M',        # '2006-10-25 14:30' 
    530     '%Y-%m-%d',              # '2006-10-25' 
    531     '%m/%d/%Y %H:%M:%S',     # '10/25/2006 14:30:59' 
    532     '%m/%d/%Y %H:%M',        # '10/25/2006 14:30' 
    533     '%m/%d/%Y',              # '10/25/2006' 
    534     '%m/%d/%y %H:%M:%S',     # '10/25/06 14:30:59' 
    535     '%m/%d/%y %H:%M',        # '10/25/06 14:30' 
    536     '%m/%d/%y',              # '10/25/06' 
    537 
    538  
    539 class DateTimeField(Field): 
    540     """ 
    541     >>> import datetime 
    542     >>> f = DateTimeField() 
    543     >>> f.to_python(datetime.date(2006, 10, 25)) 
    544     datetime.datetime(2006, 10, 25, 0, 0) 
    545     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) 
    546     datetime.datetime(2006, 10, 25, 14, 30) 
    547     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59)) 
    548     datetime.datetime(2006, 10, 25, 14, 30, 59) 
    549     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) 
    550     datetime.datetime(2006, 10, 25, 14, 30, 59, 200) 
    551     >>> f.to_python('2006-10-25 14:30:45') 
    552     datetime.datetime(2006, 10, 25, 14, 30, 45) 
    553     >>> f.to_python('2006-10-25 14:30:00') 
    554     datetime.datetime(2006, 10, 25, 14, 30) 
    555     >>> f.to_python('2006-10-25 14:30') 
    556     datetime.datetime(2006, 10, 25, 14, 30) 
    557     >>> f.to_python('2006-10-25') 
    558     datetime.datetime(2006, 10, 25, 0, 0) 
    559     >>> f.to_python('10/25/2006 14:30:45') 
    560     datetime.datetime(2006, 10, 25, 14, 30, 45) 
    561     >>> f.to_python('10/25/2006 14:30:00') 
    562     datetime.datetime(2006, 10, 25, 14, 30) 
    563     >>> f.to_python('10/25/2006 14:30') 
    564     datetime.datetime(2006, 10, 25, 14, 30) 
    565     >>> f.to_python('10/25/2006') 
    566     datetime.datetime(2006, 10, 25, 0, 0) 
    567     >>> f.to_python('10/25/06 14:30:45') 
    568     datetime.datetime(2006, 10, 25, 14, 30, 45) 
    569     >>> f.to_python('10/25/06 14:30:00') 
    570     datetime.datetime(2006, 10, 25, 14, 30) 
    571     >>> f.to_python('10/25/06 14:30') 
    572     datetime.datetime(2006, 10, 25, 14, 30) 
    573     >>> f.to_python('10/25/06') 
    574     datetime.datetime(2006, 10, 25, 0, 0) 
    575     >>> f.to_python('hello') 
    576     Traceback (most recent call last): 
    577     ... 
    578     ValidationError: [u'Enter a valid date/time.'] 
    579     >>> f.to_python('2006-10-25 4:30 p.m.') 
    580     Traceback (most recent call last): 
    581     ... 
    582     ValidationError: [u'Enter a valid date/time.'] 
    583  
    584     DateField accepts an optional input_formats parameter: 
    585     >>> f = DateTimeField(input_formats=['%Y %m %d %I:%M %p']) 
    586     >>> f.to_python(datetime.date(2006, 10, 25)) 
    587     datetime.datetime(2006, 10, 25, 0, 0) 
    588     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30)) 
    589     datetime.datetime(2006, 10, 25, 14, 30) 
    590     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59)) 
    591     datetime.datetime(2006, 10, 25, 14, 30, 59) 
    592     >>> f.to_python(datetime.datetime(2006, 10, 25, 14, 30, 59, 200)) 
    593     datetime.datetime(2006, 10, 25, 14, 30, 59, 200) 
    594     >>> f.to_python('2006 10 25 2:30 PM') 
    595     datetime.datetime(2006, 10, 25, 14, 30) 
    596  
    597     The input_formats parameter overrides all default input formats, 
    598     so the default formats won't work unless you specify them: 
    599     >>> f.to_python('2006-10-25 14:30:45') 
    600     Traceback (most recent call last): 
    601     ... 
    602     ValidationError: [u'Enter a valid date/time.'] 
    603     """ 
    604     def __init__(self, input_formats=None, required=True, widget=None): 
    605         Field.__init__(self, required, widget) 
    606         self.input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS 
    607  
    608     def to_python(self, value): 
    609         """ 
    610         Validates that the input can be converted to a datetime. Returns a 
    611         Python datetime.datetime object. 
    612         """ 
    613         Field.to_python(self, value) 
    614         if value in EMPTY_VALUES: 
    615             return None 
    616         if isinstance(value, datetime.datetime): 
    617             return value 
    618         if isinstance(value, datetime.date): 
    619             return datetime.datetime(value.year, value.month, value.day) 
    620         for format in self.input_formats: 
    621             try: 
    622                 return datetime.datetime(*time.strptime(value, format)[:6]) 
    623             except ValueError: 
    624                 continue 
    625         raise ValidationError(u'Enter a valid date/time.') 
    626  
    627 class RegexField(Field): 
    628     """ 
    629     >>> import re 
    630  
    631     >>> f = RegexField('^\d[A-F]\d$') 
    632     >>> f.to_python('2A2') 
    633     u'2A2' 
    634     >>> f.to_python('3F3') 
    635     u'3F3' 
    636     >>> f.to_python('3G3') 
    637     Traceback (most recent call last): 
    638     ... 
    639     ValidationError: [u'Enter a valid value.'] 
    640     >>> f.to_python(' 2A2') 
    641     Traceback (most recent call last): 
    642     ... 
    643     ValidationError: [u'Enter a valid value.'] 
    644     >>> f.to_python('2A2 ') 
    645     Traceback (most recent call last): 
    646     ... 
    647     ValidationError: [u'Enter a valid value.'] 
    648  
    649     Alternatively, RegexField can take a compiled regular expression: 
    650     >>> f = RegexField(re.compile('^\d[A-F]\d$')) 
    651     >>> f.to_python('2A2') 
    652     u'2A2' 
    653     >>> f.to_python('3F3') 
    654     u'3F3' 
    655     >>> f.to_python('3G3') 
    656     Traceback (most recent call last): 
    657     ... 
    658     ValidationError: [u'Enter a valid value.'] 
    659     >>> f.to_python(' 2A2') 
    660     Traceback (most recent call last): 
    661     ... 
    662     ValidationError: [u'Enter a valid value.'] 
    663     >>> f.to_python('2A2 ') 
    664     Traceback (most recent call last): 
    665     ... 
    666     ValidationError: [u'Enter a valid value.'] 
    667  
    668     RegexField takes an optional error_message argument: 
    669     >>> f = RegexField('^\d\d\d\d$', 'Enter a four-digit number.') 
    670     >>> f.to_python('1234') 
    671     u'1234' 
    672     >>> f.to_python('123') 
    673     Traceback (most recent call last): 
    674     ... 
    675     ValidationError: [u'Enter a four-digit number.'] 
    676     >>> f.to_python('abcd') 
    677     Traceback (most recent call last): 
    678     ... 
    679     ValidationError: [u'Enter a four-digit number.'] 
    680     """ 
    681     def __init__(self, regex, error_message=None, required=True, widget=None): 
    682         """ 
    683         regex can be either a string or a compiled regular expression object. 
    684         error_message is an optional error message to use, if 
    685         'Enter a valid value' is too generic for you. 
    686         """ 
    687         Field.__init__(self, required, widget) 
    688         if isinstance(regex, basestring): 
    689             regex = re.compile(regex) 
    690         self.regex = regex 
    691         self.error_message = error_message or u'Enter a valid value.' 
    692  
    693     def to_python(self, value): 
    694         """ 
    695         Validates that the input matches the regular expression. Returns a 
    696         Unicode object. 
    697         """ 
    698         Field.to_python(self, value) 
    699         if value in EMPTY_VALUES: value = u'' 
    700         if not isinstance(value, basestring): 
    701             value = unicode(str(value), DEFAULT_ENCODING) 
    702         elif not isinstance(value, unicode): 
    703             value = unicode(value, DEFAULT_ENCODING) 
    704         if not self.regex.search(value): 
    705             raise ValidationError(self.error_message) 
    706         return value 
    707  
    708 email_re = re.compile( 
    709     r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*"  # dot-atom 
    710     r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 
    711     r')@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}$', re.IGNORECASE)  # domain 
    712  
    713 class EmailField(RegexField): 
    714     """ 
    715     >>> f = EmailField() 
    716     >>> f.to_python('person@example.com') 
    717     u'person@example.com' 
    718     >>> f.to_python('foo') 
    719     Traceback (most recent call last): 
    720     ... 
    721     ValidationError: [u'Enter a valid e-mail address.'] 
    722     >>> f.to_python('foo@') 
    723     Traceback (most recent call last): 
    724     ... 
    725     ValidationError: [u'Enter a valid e-mail address.'] 
    726     >>> f.to_python('foo@bar') 
    727     Traceback (most recent call last): 
    728     ... 
    729     ValidationError: [u'Enter a valid e-mail address.'] 
    730     """ 
    731     def __init__(self, required=True, widget=None): 
    732         RegexField.__init__(self, email_re, u'Enter a valid e-mail address.', required, widget) 
    733  
    734 class BooleanField(Field): 
    735     """ 
    736     >>> f = BooleanField() 
    737     >>> f.to_python(True) 
    738     True 
    739     >>> f.to_python(False) 
    740     False 
    741     >>> f.to_python(1) 
    742     True 
    743     >>> f.to_python(0) 
    744     False 
    745     >>> f.to_python('Django rocks') 
    746     True 
    747     """ 
    748     widget = CheckboxInput 
    749  
    750     def to_python(self, value): 
    751         "Returns a Python boolean object." 
    752         Field.to_python(self, value) 
    753         return bool(value) 
    754  
    755 ######### 
    756 # FORMS # 
    757 ######### 
    758  
    759 class DeclarativeFieldsMetaclass(type): 
    760     "Metaclass that converts Field attributes to a dictionary called 'fields'." 
    761     def __new__(cls, name, bases, attrs): 
    762         attrs['fields'] = dict([(name, attrs.pop(name)) for name, obj in attrs.items() if isinstance(obj, Field)]) 
    763         return type.__new__(cls, name, bases, attrs) 
    764  
    765 class Form(object): 
    766     "A collection of Fields, plus their associated data." 
    767     __metaclass__ = DeclarativeFieldsMetaclass 
    768  
    769     def __init__(self, data=None): # TODO: prefix stuff 
    770         self.data = data or {} 
    771         self.__data_python = None # Stores the data after to_python() has been called. 
    772         self.__errors = None # Stores the errors after to_python() has been called. 
    773  
    774     def __iter__(self): 
    775         for name, field in self.fields.items(): 
    776             yield BoundField(self, field, name) 
    777  
    778     def to_python(self): 
    779         if self.__errors is None: 
    780             self._validate() 
    781         return self.__data_python 
    782  
    783     def errors(self): 
    784         "Returns an ErrorDict for self.data" 
    785         if self.__errors is None: 
    786             self._validate() 
    787         return self.__errors 
    788  
    789     def is_valid(self): 
    790         """ 
    791         Returns True if the form has no errors. Otherwise, False. This exists 
    792         solely for convenience, so client code can use positive logic rather 
    793         than confusing negative logic ("if not form.errors()"). 
    794         """ 
    795         return not bool(self.errors()) 
    796  
    797     def __getitem__(self, name): 
    798         "Returns a BoundField with the given name." 
    799         try: 
    800             field = self.fields[name] 
    801         except KeyError: 
    802             raise KeyError('Key %r not found in Form' % name) 
    803         return BoundField(self, field, name) 
    804  
    805     def _validate(self): 
    806         data_python = {} 
    807         errors = ErrorDict() 
    808         for name, field in self.fields.items(): 
    809             try: 
    810                 value = field.to_python(self.data.get(name, None)) 
    811                 data_python[name] = value 
    812             except ValidationError, e: 
    813                 errors[name] = e.messages 
    814         if not errors: # Only set self.data_python if there weren't errors. 
    815             self.__data_python = data_python 
    816         self.__errors = errors 
    817  
    818 class BoundField(object): 
    819     "A Field plus data" 
    820     def __init__(self, form, field, name): 
    821         self._form = form 
    822         self._field = field 
    823         self._name = name 
    824  
    825     def __str__(self): 
    826         "Renders this field as an HTML widget." 
    827         # Use the 'widget' attribute on the field to determine which type 
    828         # of HTML widget to use. 
    829         return self.as_widget(self._field.widget) 
    830  
    831     def _errors(self): 
    832         """ 
    833         Returns an ErrorList for this field. Returns an empty ErrorList 
    834         if there are none. 
    835         """ 
    836         try: 
    837             return self._form.errors()[self._name] 
    838         except KeyError: 
    839             return ErrorList() 
    840     errors = property(_errors) 
    841  
    842     def as_widget(self, widget, attrs=None): 
    843         return widget.render(self._name, self._form.data.get(self._name, None), attrs=attrs) 
    844  
    845     def as_text(self, attrs=None): 
    846         """ 
    847         Returns a string of HTML for representing this as an <input type="text">. 
    848         """ 
    849         return self.as_widget(TextInput(), attrs) 
    850  
    851     def as_textarea(self, attrs=None): 
    852         "Returns a string of HTML for representing this as a <textarea>." 
    853         return self.as_widget(Textarea(), attrs) 
     15from widgets import * 
     16from fields import * 
     17from forms import Form 
    85418 
    85519########################## 
     
    86428    "Returns a Form instance for the given list of Django database field instances." 
    86529    raise NotImplementedError 
    866  
    867 if __name__ == "__main__": 
    868     import doctest 
    869     doctest.testmod()