0001## FormEncode, a  Form processor
0002## Copyright (C) 2003, Ian Bicking <ianb@colorstudy.com>
0003##  
0004## This library is free software; you can redistribute it and/or
0005## modify it under the terms of the GNU Lesser General Public
0006## License as published by the Free Software Foundation; either
0007## version 2.1 of the License, or (at your option) any later version.
0008##
0009## This library is distributed in the hope that it will be useful,
0010## but WITHOUT ANY WARRANTY; without even the implied warranty of
0011## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012## Lesser General Public License for more details.
0013##
0014## You should have received a copy of the GNU Lesser General Public
0015## License along with this library; if not, write to the Free Software
0016## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
0017##
0018## NOTE: In the context of the Python environment, I interpret "dynamic
0019## linking" as importing -- thus the LGPL applies to the contents of
0020## the modules, but make no requirements on code importing these
0021## modules.
0022"""
0023Validator/Converters for use with FormEncode.
0024"""
0025
0026import re
0027DateTime = None
0028httplib = None
0029urlparse = None
0030import socket
0031from interfaces import *
0032from api import *
0033sha = random = None
0034try:
0035    import sets
0036except ImportError:
0037    sets = None
0038
0039import cgi
0040
0041import fieldstorage
0042
0043try:
0044    import DNS
0045    DNS.DiscoverNameServers()
0046    have_dns=True
0047except ImportError:
0048    have_dns=False
0049
0050True, False = (1==1), (0==1)
0051
0052def _(s): return s # dummy translation function, nothing is translated here.
0053                   # Instead this is actually done in api.message.
0054                   # The surrounding _("string") of the strings is only for extracting
0055                   # the strings automatically
0056                   # if you run pygettext with this source comment this function out temporarly 
0057
0058############################################################
0059## Utility methods
0060############################################################
0061
0062# These all deal with accepting both mxDateTime and datetime
0063# modules and types
0064datetime_module = None
0065mxDateTime_module = None
0066
0067def import_datetime(module_type):
0068    global datetime_module, mxDateTime_module
0069    if module_type is None:
0070        try:
0071            if datetime_module is None:
0072                import datetime as datetime_module
0073            return datetime_module
0074        except ImportError:
0075            if mxDateTime_module is None:
0076                from mx import DateTime as mxDateTime_module
0077            return mxDateTime_module
0078
0079    module_type = module_type.lower()
0080    assert module_type in ('datetime', 'mxdatetime')
0081    if module_type == 'datetime':
0082        if datetime_module is None:
0083            import datetime as datetime_module
0084        return datetime_module
0085    else:
0086        if mxDateTime_module is None:
0087            from mx import DateTime as mxDateTime_module
0088        return mxDateTime_module
0089
0090def datetime_now(module):
0091    if module.__name__ == 'datetime':
0092        return module.datetime.now()
0093    else:
0094        return module.now()
0095
0096def datetime_makedate(module, year, month, day):
0097    if module.__name__ == 'datetime':
0098        return module.date(year, month, day)
0099    else:
0100        try:
0101            return module.DateTime(year, month, day)
0102        except module.RangeError, e:
0103            raise ValueError(str(e))
0104
0105
0106# TODO: Needs being extended to support mx.DateTime as well.
0107def datetime_time(module):
0108    if module.__name__ == 'datetime':
0109        return module.time
0110
0111# TODO: Needs being extended to support mx.DateTime as well.
0112def datetime_isotime(module):
0113    if module.__name__ == 'datetime':
0114        return module.time.isoformat
0115
0116
0117
0118############################################################
0119## Wrapper Validators
0120############################################################
0121
0122class ConfirmType(FancyValidator):
0123
0124    """
0125    Confirms that the input/output is of the proper type.
0126
0127    Uses the parameters:
0128
0129    subclass:
0130        The class or a tuple of classes; the item must be an instance
0131        of the class or a subclass.
0132    type:
0133        A type or tuple of types (or classes); the item must be of
0134        the exact class or type.  Subclasses are not allowed.
0135
0136    Examples::
0137    
0138        >>> cint = ConfirmType(subclass=int)
0139        >>> cint.to_python(True)
0140        True
0141        >>> cint.to_python('1')
0142        Traceback (most recent call last):
0143            ...
0144        Invalid: '1' is not a subclass of <type 'int'>
0145        >>> cintfloat = ConfirmType(subclass=(float, int))
0146        >>> cintfloat.to_python(1.0), cintfloat.from_python(1.0)
0147        (1.0, 1.0)
0148        >>> cintfloat.to_python(1), cintfloat.from_python(1)
0149        (1, 1)
0150        >>> cintfloat.to_python(None)
0151        Traceback (most recent call last):
0152            ...
0153        Invalid: None is not a subclass of one of the types <type 'float'>, <type 'int'>
0154        >>> cint2 = ConfirmType(type=int)
0155        >>> cint2(accept_python=False).from_python(True)
0156        Traceback (most recent call last):
0157            ...
0158        Invalid: True must be of the type <type 'int'>
0159    """
0160
0161    subclass = None
0162    type = None
0163
0164    messages = {
0165        'subclass': _("%(object)r is not a subclass of %(subclass)s"),
0166        'inSubclass': _("%(object)r is not a subclass of one of the types %(subclassList)s"),
0167        'inType': _("%(object)r must be one of the types %(typeList)s"),
0168        'type': _("%(object)r must be of the type %(type)s"),
0169        }
0170
0171    def __init__(self, *args, **kw):
0172        FancyValidator.__init__(self, *args, **kw)
0173        if self.subclass:
0174            if isinstance(self.subclass, list):
0175                self.subclass = tuple(self.subclass)
0176            elif not isinstance(self.subclass, tuple):
0177                self.subclass = (self.subclass,)
0178            self.validate_python = self.confirm_subclass
0179        if self.type:
0180            if isinstance(self.type, list):
0181                self.type = tuple(self.type)
0182            elif not isinstance(self.type, tuple):
0183                self.type = (self.type,)
0184            self.validate_python = self.confirm_type
0185
0186    def confirm_subclass(self, value, state):
0187        if not isinstance(value, self.subclass):
0188            if len(self.subclass) == 1:
0189                msg = self.message('subclass', state, object=value,
0190                                   subclass=self.subclass[0])
0191            else:
0192                subclass_list = ', '.join(map(str, self.subclass))
0193                msg = self.message('inSubclass', state, object=value,
0194                                   subclassList=subclass_list)
0195            raise Invalid(msg, value, state)
0196
0197    def confirm_type(self, value, state):
0198        for t in self.type:
0199            if type(value) is t:
0200                break
0201        else:
0202            if len(self.type) == 1:
0203                msg = self.message('type', state, object=value,
0204                                   type=self.type[0])
0205            else:
0206                msg = self.message('inType', state, object=value,
0207                                   typeList=', '.join(map(str, self.type)))
0208            raise Invalid(msg, value, state)
0209        return value
0210
0211    def is_empty(self, value):
0212        return False
0213
0214class Wrapper(FancyValidator):
0215
0216    """
0217    Used to convert functions to validator/converters.
0218
0219    You can give a simple function for `to_python`, `from_python`,
0220    `validate_python` or `validate_other`.  If that function raises an
0221    exception, the value is considered invalid.  Whatever value the
0222    function returns is considered the converted value.
0223
0224    Unlike validators, the `state` argument is not used.  Functions
0225    like `int` can be used here, that take a single argument.
0226
0227    Examples::
0228
0229        >>> def downcase(v):
0230        ...     return v.lower()
0231        >>> wrap = Wrapper(to_python=downcase)
0232        >>> wrap.to_python('This')
0233        'this'
0234        >>> wrap.from_python('This')
0235        'This'
0236        >>> wrap2 = Wrapper(from_python=downcase)
0237        >>> wrap2.from_python('This')
0238        'this'
0239        >>> wrap2.from_python(1)
0240        Traceback (most recent call last):
0241          ...
0242        Invalid: 'int' object has no attribute 'lower'
0243        >>> wrap3 = Wrapper(validate_python=int)
0244        >>> wrap3.to_python('1')
0245        '1'
0246        >>> wrap3.to_python('a')
0247        Traceback (most recent call last):
0248          ...
0249        Invalid: invalid literal for int(): a
0250    """
0251
0252    func_to_python = None
0253    func_from_python = None
0254    func_validate_python = None
0255    func_validate_other = None
0256
0257    def __init__(self, *args, **kw):
0258        for n in ['to_python', 'from_python', 'validate_python',
0259                  'validate_other']:
0260            if kw.has_key(n):
0261                kw['func_%s' % n] = kw[n]
0262                del kw[n]
0263        FancyValidator.__init__(self, *args, **kw)
0264        self._to_python = self.wrap(self.func_to_python)
0265        self._from_python = self.wrap(self.func_from_python)
0266        self.validate_python = self.wrap(self.func_validate_python)
0267        self.validate_other = self.wrap(self.func_validate_other)
0268
0269    def wrap(self, func):
0270        if not func:
0271            return None
0272        def result(value, state, func=func):
0273            try:
0274                return func(value)
0275            except Exception, e:
0276                raise Invalid(str(e), {}, value, state)
0277        return result
0278
0279class Constant(FancyValidator):
0280
0281    """
0282    This converter converts everything to the same thing.
0283
0284    I.e., you pass in the constant value when initializing, then all
0285    values get converted to that constant value.
0286
0287    This is only really useful for funny situations, like::
0288
0289      fromEmailValidator = ValidateAny(
0290                               ValidEmailAddress(),
0291                               Constant('unknown@localhost'))
0292                               
0293    In this case, the if the email is not valid
0294    ``'unknown@localhost'`` will be used instead.  Of course, you
0295    could use ``if_invalid`` instead.
0296
0297    Examples::
0298
0299        >>> Constant('X').to_python('y')
0300        'X'
0301    """
0302
0303    __unpackargs__ = ('value',)
0304
0305    def _to_python(self, value, state):
0306        return self.value
0307
0308    _from_python = _to_python
0309
0310############################################################
0311## Normal validators
0312############################################################
0313
0314class MaxLength(FancyValidator):
0315
0316    """
0317    Invalid if the value is longer than `maxLength`.  Uses len(),
0318    so it can work for strings, lists, or anything with length.
0319
0320    Examples::
0321
0322        >>> max5 = MaxLength(5)
0323        >>> max5.to_python('12345')
0324        '12345'
0325        >>> max5.from_python('12345')
0326        '12345'
0327        >>> max5.to_python('123456')
0328        Traceback (most recent call last):
0329          ...
0330        Invalid: Enter a value less than 5 characters long
0331        >>> max5(accept_python=False).from_python('123456')
0332        Traceback (most recent call last):
0333          ...
0334        Invalid: Enter a value less than 5 characters long
0335        >>> max5.to_python([1, 2, 3])
0336        [1, 2, 3]
0337        >>> max5.to_python([1, 2, 3, 4, 5, 6])
0338        Traceback (most recent call last):
0339          ...
0340        Invalid: Enter a value less than 5 characters long
0341        >>> max5.to_python(5)
0342        Traceback (most recent call last):
0343          ...
0344        Invalid: Invalid value (value with length expected)
0345    """
0346
0347    __unpackargs__ = ('maxLength',)
0348    messages = {
0349        'tooLong': _("Enter a value less than %(maxLength)i characters long"),
0350        'invalid': _("Invalid value (value with length expected)"),
0351        }
0352
0353    def validate_python(self, value, state):
0354        try:
0355            if value and                  len(value) > self.maxLength:
0357                raise Invalid(self.message('tooLong', state,
0358                                           maxLength=self.maxLength),
0359                              value, state)
0360            else:
0361                return None
0362        except TypeError:
0363            raise Invalid(self.message('invalid', state),
0364                          value, state)
0365
0366class MinLength(FancyValidator):
0367
0368    """
0369    Invalid if the value is shorter than `minlength`.  Uses len(),
0370    so it can work for strings, lists, or anything with length.
0371
0372    Examples::
0373
0374        >>> min5 = MinLength(5)
0375        >>> min5.to_python('12345')
0376        '12345'
0377        >>> min5.from_python('12345')
0378        '12345'
0379        >>> min5.to_python('1234')
0380        Traceback (most recent call last):
0381          ...
0382        Invalid: Enter a value at least 5 characters long
0383        >>> min5(accept_python=False).from_python('1234')
0384        Traceback (most recent call last):
0385          ...
0386        Invalid: Enter a value at least 5 characters long
0387        >>> min5.to_python([1, 2, 3, 4, 5])
0388        [1, 2, 3, 4, 5]
0389        >>> min5.to_python([1, 2, 3])
0390        Traceback (most recent call last):
0391          ...
0392        Invalid: Enter a value at least 5 characters long
0393        >>> min5.to_python(5)
0394        Traceback (most recent call last):
0395          ...
0396        Invalid: Invalid value (value with length expected)
0397        
0398    """
0399
0400    __unpackargs__ = ('minLength',)
0401
0402    messages = {
0403        'tooShort': _("Enter a value at least %(minLength)i characters long"),
0404        'invalid': _("Invalid value (value with length expected)"),
0405        }
0406
0407    def validate_python(self, value, state):
0408        try:
0409            if len(value) < self.minLength:
0410                raise Invalid(self.message('tooShort', state,
0411                                           minLength=self.minLength),
0412                              value, state)
0413        except TypeError:
0414            raise Invalid(self.message('invalid', state),
0415                          value, state)
0416
0417class NotEmpty(FancyValidator):
0418
0419    """
0420    Invalid if value is empty (empty string, empty list, etc).
0421
0422    Generally for objects that Python considers false, except zero
0423    which is not considered invalid.
0424
0425    Examples::
0426
0427        >>> ne = NotEmpty(messages={'empty': 'enter something'})
0428        >>> ne.to_python('')
0429        Traceback (most recent call last):
0430          ...
0431        Invalid: enter something
0432        >>> ne.to_python(0)
0433        0
0434    """
0435    not_empty = True
0436
0437    messages = {
0438        'empty': _("Please enter a value"),
0439        }
0440
0441    def validate_python(self, value, state):
0442        if value == 0:
0443            # This isn't "empty" for this definition.
0444            return value
0445        if not value:
0446            raise Invalid(self.message('empty', state),
0447                          value, state)
0448
0449class Empty(FancyValidator):
0450
0451    """
0452    Invalid unless the value is empty.  Use cleverly, if at all.
0453
0454    Examples::
0455
0456        >>> Empty.to_python(0)
0457        Traceback (most recent call last):
0458          ...
0459        Invalid: You cannot enter a value here
0460    """
0461
0462    messages = {
0463        'notEmpty': _("You cannot enter a value here"),
0464        }
0465
0466    def validate_python(self, value, state):
0467        if value or value == 0:
0468            raise Invalid(self.message('notEmpty', state),
0469                          value, state)
0470
0471class Regex(FancyValidator):
0472
0473    """
0474    Invalid if the value doesn't match the regular expression `regex`.
0475
0476    The regular expression can be a compiled re object, or a string
0477    which will be compiled for you.
0478
0479    Use strip=True if you want to strip the value before validation,
0480    and as a form of conversion (often useful).
0481
0482    Examples::
0483
0484        >>> cap = Regex(r'^[A-Z]+$')
0485        >>> cap.to_python('ABC')
0486        'ABC'
0487
0488    Note that ``.from_python()`` calls (in general) do not validate
0489    the input::
0490    
0491        >>> cap.from_python('abc')
0492        'abc'
0493        >>> cap(accept_python=False).from_python('abc')
0494        Traceback (most recent call last):
0495          ...
0496        Invalid: The input is not valid
0497        >>> cap.to_python(1)
0498        Traceback (most recent call last):
0499          ...
0500        Invalid: The input must be a string (not a <type 'int'>: 1)
0501        >>> Regex(r'^[A-Z]+$', strip=True).to_python('  ABC  ')
0502        'ABC'
0503        >>> Regex(r'this', regexOps=('I',)).to_python('THIS')
0504        'THIS'
0505    """
0506
0507    regexOps = ()
0508    strip = False
0509    regex = None
0510
0511    __unpackargs__ = ('regex',)
0512
0513    messages = {
0514        'invalid': _("The input is not valid"),
0515        }
0516
0517    def __init__(self, *args, **kw):
0518        FancyValidator.__init__(self, *args, **kw)
0519        if isinstance(self.regex, basestring):
0520            ops = 0
0521            assert not isinstance(self.regexOps, basestring), (
0522                "regexOps should be a list of options from the re module "
0523                "(names, or actual values)")
0524            for op in self.regexOps:
0525                if isinstance(op, basestring):
0526                    ops |= getattr(re, op)
0527                else:
0528                    ops |= op
0529            self.regex = re.compile(self.regex, ops)
0530
0531    def validate_python(self, value, state):
0532        self.assert_string(value, state)
0533        if self.strip and isinstance(value, basestring):
0534            value = value.strip()
0535        if not self.regex.search(value):
0536            raise Invalid(self.message('invalid', state),
0537                          value, state)
0538
0539    def _to_python(self, value, state):
0540        if self.strip and isinstance(value, basestring):
0541            return value.strip()
0542        return value
0543
0544class PlainText(Regex):
0545
0546    """
0547    Test that the field contains only letters, numbers, underscore,
0548    and the hyphen.  Subclasses Regex.
0549
0550    Examples::
0551
0552        >>> PlainText.to_python('_this9_')
0553        '_this9_'
0554        >>> PlainText.from_python('  this  ')
0555        '  this  '
0556        >>> PlainText(accept_python=False).from_python('  this  ')
0557        Traceback (most recent call last):
0558          ...
0559        Invalid: Enter only letters, numbers, or _ (underscore)
0560        >>> PlainText(strip=True).to_python('  this  ')
0561        'this'
0562        >>> PlainText(strip=True).from_python('  this  ')
0563        'this'
0564    """
0565
0566    regex = r"^[a-zA-Z_\-0-9]*$"
0567
0568    messages = {
0569        'invalid': _('Enter only letters, numbers, or _ (underscore)'),
0570        }
0571
0572class OneOf(FancyValidator):
0573
0574    """
0575    Tests that the value is one of the members of a given list.
0576
0577    If ``testValueList=True``, then if the input value is a list or
0578    tuple, all the members of the sequence will be checked (i.e., the
0579    input must be a subset of the allowed values).
0580
0581    Use ``hideList=True`` to keep the list of valid values out of the
0582    error message in exceptions.
0583
0584    Examples::
0585
0586        >>> oneof = OneOf([1, 2, 3])
0587        >>> oneof.to_python(1)
0588        1
0589        >>> oneof.to_python(4)
0590        Traceback (most recent call last):
0591          ...
0592        Invalid: Value must be one of: 1; 2; 3 (not 4)
0593        >>> oneof(testValueList=True).to_python([2, 3, [1, 2, 3]])
0594        [2, 3, [1, 2, 3]]
0595        >>> oneof.to_python([2, 3, [1, 2, 3]])
0596        Traceback (most recent call last):
0597          ...
0598        Invalid: Value must be one of: 1; 2; 3 (not [2, 3, [1, 2, 3]])
0599    """
0600
0601    list = None
0602    testValueList = False
0603    hideList = False
0604
0605    __unpackargs__ = ('list',)
0606
0607    messages = {
0608        'invalid': _("Invalid value"),
0609        'notIn': _("Value must be one of: %(items)s (not %(value)r)"),
0610        }
0611
0612    def validate_python(self, value, state):
0613        if self.testValueList and isinstance(value, (list, tuple)):
0614            for v in value:
0615                self.validate_python(v, state)
0616        else:
0617            if not value in self.list:
0618                if self.hideList:
0619                    raise Invalid(self.message('invalid', state),
0620                                  value, state)
0621                else:
0622                    items = '; '.join(map(str, self.list))
0623                    raise Invalid(self.message('notIn', state,
0624                                               items=items,
0625                                               value=value),
0626                                  value, state)
0627
0628class DictConverter(FancyValidator):
0629
0630    """
0631    Converts values based on a dictionary which has values as keys for
0632    the resultant values.
0633
0634    If ``allowNull`` is passed, it will not balk if a false value
0635    (e.g., '' or None) is given (it will return None in these cases).
0636
0637    to_python takes keys and gives values, from_python takes values and
0638    gives keys.
0639
0640    If you give hideDict=True, then the contents of the dictionary
0641    will not show up in error messages.
0642
0643    Examples::
0644
0645        >>> dc = DictConverter({1: 'one', 2: 'two'})
0646        >>> dc.to_python(1)
0647        'one'
0648        >>> dc.from_python('one')
0649        1
0650        >>> dc.to_python(3)
0651        Traceback (most recent call last):
0652        Invalid: Enter a value from: 1; 2
0653        >>> dc2 = dc(hideDict=True)
0654        >>> dc2.hideDict
0655        True
0656        >>> dc2.dict
0657        {1: 'one', 2: 'two'}
0658        >>> dc2.to_python(3)
0659        Traceback (most recent call last):
0660        Invalid: Choose something
0661        >>> dc.from_python('three')
0662        Traceback (most recent call last):
0663        Invalid: Nothing in my dictionary goes by the value 'three'.  Choose one of: 'one'; 'two'
0664    """
0665
0666    dict = None
0667    hideDict = False
0668
0669    __unpackargs__ = ('dict',)
0670
0671    messages = {
0672        'keyNotFound': _("Choose something"),
0673        'chooseKey': _("Enter a value from: %(items)s"),
0674        'valueNotFound': _("That value is not known"),
0675        'chooseValue': _("Nothing in my dictionary goes by the value %(value)s.  Choose one of: %(items)s"),
0676        }
0677
0678    def _to_python(self, value, state):
0679        try:
0680            return self.dict[value]
0681        except KeyError:
0682            if self.hideDict:
0683                raise Invalid(self.message('keyNotFound', state),
0684                              value, state)
0685            else:
0686                items = '; '.join(map(repr, self.dict.keys()))
0687                raise Invalid(self.message('chooseKey', state,
0688                                           items=items),
0689                              value, state)
0690
0691    def _from_python(self, value, state):
0692        for k, v in self.dict.items():
0693            if value == v:
0694                return k
0695        if self.hideDict:
0696            raise Invalid(self.message('valueNotFound', state),
0697                          value, state)
0698        else:
0699            items = '; '.join(map(repr, self.dict.values()))
0700            raise Invalid(self.message('chooseValue', state,
0701                                       value=repr(value),
0702                                       items=items),
0703                          value, state)
0704
0705class IndexListConverter(FancyValidator):
0706
0707    """
0708    Converts a index (which may be a string like '2') to the value in
0709    the given list.
0710
0711    Examples::
0712
0713        >>> index = IndexListConverter(['zero', 'one', 'two'])
0714        >>> index.to_python(0)
0715        'zero'
0716        >>> index.from_python('zero')
0717        0
0718        >>> index.to_python('1')
0719        'one'
0720        >>> index.to_python(5)
0721        Traceback (most recent call last):
0722        Invalid: Index out of range
0723        >>> index(not_empty=True).to_python(None)
0724        Traceback (most recent call last):
0725        Invalid: Please enter a value
0726        >>> index.from_python('five')
0727        Traceback (most recent call last):
0728        Invalid: Item 'five' was not found in the list
0729    """
0730
0731    list = None
0732
0733    __unpackargs__ = ('list',)
0734
0735    messages = {
0736        'integer': _("Must be an integer index"),
0737        'outOfRange': _("Index out of range"),
0738        'notFound': _("Item %(value)s was not found in the list"),
0739        }
0740
0741    def _to_python(self, value, state):
0742        try:
0743            value = int(value)
0744        except (ValueError, TypeError):
0745            raise Invalid(self.message('integer', state),
0746                          value, state)
0747        try:
0748            return self.list[value]
0749        except IndexError:
0750            raise Invalid(self.message('outOfRange', state),
0751                          value, state)
0752
0753    def _from_python(self, value, state):
0754        for i in range(len(self.list)):
0755            if self.list[i] == value:
0756                return i
0757        raise Invalid(self.message('notFound', state,
0758                                   value=repr(value)),
0759                      value, state)
0760
0761class DateValidator(FancyValidator):
0762
0763    """
0764    Validates that a date is within the given range.  Be sure to call
0765    DateConverter first if you aren't expecting mxDateTime input.
0766
0767    ``earliest_date`` and ``latest_date`` may be functions; if so,
0768    they will be called each time before validating.
0769
0770    ``after_now`` means a time after the current timestamp; note that
0771    just a few milliseconds before now is invalid!  ``today_or_after``
0772    is more permissive, and ignores hours and minutes.
0773
0774    Examples::
0775
0776        >>> from datetime import datetime, timedelta
0777        >>> d = DateValidator(earliest_date=datetime(2003, 1, 1))
0778        >>> d.to_python(datetime(2004, 1, 1))
0779        datetime.datetime(2004, 1, 1, 0, 0)
0780        >>> d.to_python(datetime(2002, 1, 1))
0781        Traceback (most recent call last):
0782            ...
0783        Invalid: Date must be after Wednesday, 01 January 2003
0784        >>> d.to_python(datetime(2003, 1, 1))
0785        datetime.datetime(2003, 1, 1, 0, 0)
0786        >>> d = DateValidator(after_now=True)
0787        >>> now = datetime.now()
0788        >>> d.to_python(now+timedelta(seconds=5)) == now+timedelta(seconds=5)
0789        True
0790        >>> d.to_python(now-timedelta(days=1))
0791        Traceback (most recent call last):
0792            ...
0793        Invalid: The date must be sometime in the future
0794        >>> d.to_python(now+timedelta(days=1)) > now
0795        True
0796        >>> d = DateValidator(today_or_after=True)
0797        >>> d.to_python(now) == now
0798        True
0799    
0800    """
0801
0802    earliest_date = None
0803    latest_date = None
0804    after_now = False
0805    # Like after_now, but just after this morning:
0806    today_or_after = False
0807    # Use 'datetime' to force the Python 2.3+ datetime module, or
0808    # 'mxDateTime' to force the mxDateTime module (None means use
0809    # datetime, or if not present mxDateTime)
0810    datetime_module = None
0811
0812    messages = {
0813        'after': _("Date must be after %(date)s"),
0814        'before': _("Date must be before %(date)s"),
0815        # Double %'s, because this will be substituted twice:
0816        'date_format': _("%%A, %%d %%B %%Y"),
0817        'future': _("The date must be sometime in the future"),
0818        }
0819
0820    def validate_python(self, value, state):
0821        date_format = self.message('date_format', state)
0822        if isinstance(date_format, unicode):
0823            # strftime doesn't like unicode
0824            encoding = 'utf8'
0825            date_format = date_format.encode(encoding)
0826        else:
0827            encoding = None
0828        if self.earliest_date:
0829            if callable(self.earliest_date):
0830                earliest_date = self.earliest_date()
0831            else:
0832                earliest_date = self.earliest_date
0833            if value < earliest_date:
0834                date_formatted = earliest_date.strftime(date_format)
0835                if encoding:
0836                    date_formatted = date_formatted.decode(encoding)
0837                raise Invalid(
0838                    self.message('after', state,
0839                                 date=date_formatted),
0840                    value, state)
0841        if self.latest_date:
0842            if callable(self.latest_date):
0843                latest_date = self.latest_date()
0844            else:
0845                latest_date = self.latest_date
0846            if value > latest_date:
0847                date_formatted = latest_date.strftime(date_format)
0848                if encoding:
0849                    date_formatted = date_formatted.decode(encoding)
0850                raise Invalid(
0851                    self.message('before', state,
0852                                 date=date_formatted),
0853                    value, state)
0854        if self.after_now:
0855            dt_mod = import_datetime(self.datetime_module)
0856            now = datetime_now(dt_mod)
0857            if value < now:
0858                date_formatted = now.strftime(date_format)
0859                if encoding:
0860                    date_formatted = date_formatted.decode(encoding)
0861                raise Invalid(
0862                    self.message('future', state,
0863                                 date=date_formatted),
0864                    value, state)
0865        if self.today_or_after:
0866            dt_mod = import_datetime(self.datetime_module)
0867            now = datetime_now(dt_mod)
0868            today = datetime_makedate(dt_mod,
0869                                      now.year, now.month, now.day)
0870            value_as_date = datetime_makedate(
0871                dt_mod, value.year, value.month, value.day)
0872            if value_as_date < today:
0873                date_formatted = now.strftime(date_format)
0874                if encoding:
0875                    date_formatted = date_formatted.decode(encoding)
0876                raise Invalid(
0877                    self.message('future', state,
0878                                 date=date_formatted),
0879                    value, state)
0880
0881
0882class Bool(FancyValidator):
0883
0884    """
0885    Always Valid, returns True or False based on the value and the
0886    existance of the value.
0887
0888    If you want to convert strings like ``'true'`` to booleans, then
0889    use ``StringBoolean``.
0890
0891    Examples::
0892
0893        >>> Bool.to_python(0)
0894        False
0895        >>> Bool.to_python(1)
0896        True
0897        >>> Bool.to_python('')
0898        False
0899        >>> Bool.to_python(None)
0900        False
0901    """
0902
0903    if_missing = False
0904
0905    def _to_python(self, value, state):
0906        return bool(value)
0907    _from_python = _to_python
0908
0909    def empty_value(self, value):
0910        return False
0911
0912class Int(FancyValidator):
0913
0914    """
0915    Convert a value to an integer.
0916
0917    Example::
0918
0919        >>> Int.to_python('10')
0920        10
0921        >>> Int.to_python('ten')
0922        Traceback (most recent call last):
0923            ...
0924        Invalid: Please enter an integer value
0925    """
0926
0927    messages = {
0928        'integer': _("Please enter an integer value"),
0929        'tooLow': _("Please enter an integer above %(min)i"),
0930        'tooHigh': _("Please enter an integer below %(max)i"),
0931        }
0932
0933    min = None
0934    max = None
0935
0936    def __initargs__(self, args):
0937        if self.min != None:
0938            self.min = int(self.min)
0939        if self.max != None:
0940            self.max = int(self.max)
0941
0942
0943    def _to_python(self, value, state):
0944        try:
0945            return int(value)
0946        except (ValueError, TypeError):
0947            raise Invalid(self.message('integer', state),
0948                          value, state)
0949
0950    def validate_python(self, value, state):
0951        if self.min != None and value < self.min:
0952            msg = self.message("tooLow", state, min=self.min)
0953            raise Invalid(msg, value, state)
0954        if self.max != None and value > self.max:
0955            msg = self.message("tooHigh", state, max=self.max)
0956            raise Invalid(msg, value, state)
0957
0958    _from_python = _to_python
0959
0960class Number(FancyValidator):
0961
0962    """
0963    Convert a value to a float or integer.  Tries to convert it to
0964    an integer if no information is lost.
0965
0966    ::
0967
0968        >>> Number.to_python('10')
0969        10
0970        >>> Number.to_python('10.5')
0971        10.5
0972        >>> Number.to_python('ten')
0973        Traceback (most recent call last):
0974            ...
0975        Invalid: Please enter a number
0976
0977    """
0978
0979    messages = {
0980        'number': _("Please enter a number"),
0981        }
0982
0983    def _to_python(self, value, state):
0984        try:
0985            value = float(value)
0986            if value == int(value):
0987                return int(value)
0988            return value
0989        except ValueError:
0990            raise Invalid(self.message('number', state),
0991                          value, state)
0992
0993class String(FancyValidator):
0994    """
0995    Converts things to string, but treats empty things as the empty
0996    string.
0997
0998    Also takes a `max` and `min` argument, and the string length must
0999    fall in that range.
1000
1001    Also you may give an `encoding` argument, which will encode any
1002    unicode that is found.  Lists and tuples are joined with
1003    `list_joiner` (default ``', '``) in ``from_python``.
1004
1005    ::
1006
1007        >>> String(min=2).to_python('a')
1008        Traceback (most recent call last):
1009            ...
1010        Invalid: Enter a value 2 characters long or more
1011        >>> String(max=10).to_python('xxxxxxxxxxx')
1012        Traceback (most recent call last):
1013            ...
1014        Invalid: Enter a value less than 10 characters long
1015        >>> String().from_python(None)
1016        ''
1017        >>> String().from_python([])
1018        ''
1019        >>> String().to_python(None)
1020        ''
1021        >>> String(min=3).to_python(None)
1022        Traceback (most recent call last):
1023            ...
1024        Invalid: Please enter a value
1025        >>> String(min=1).to_python('')
1026        Traceback (most recent call last):
1027            ...
1028        Invalid: Please enter a value
1029    
1030    """
1031
1032    min = None
1033    max = None
1034    not_empty = None
1035    encoding = None
1036    list_joiner = ', '
1037
1038    messages = {
1039        'tooLong': _("Enter a value less than %(max)i characters long"),
1040        'tooShort': _("Enter a value %(min)i characters long or more"),
1041        }
1042
1043    def __initargs__(self, new_attrs):
1044        if self.not_empty is None and self.min:
1045            self.not_empty = True
1046
1047    def _to_python(self, value, state):
1048        if not value:
1049            value = ''
1050        if not isinstance(value, basestring):
1051            try:
1052                value = str(value)
1053            except UnicodeEncodeError:
1054                value = unicode(value)
1055        if self.encoding is not None and isinstance(value, unicode):
1056            value = value.encode(self.encoding)
1057        return value
1058
1059    def _from_python(self, value, state):
1060        if not value and value != 0:
1061            value = ''
1062        if not isinstance(value, basestring):
1063            if isinstance(value, (list, tuple)):
1064                value = self.list_joiner.join([
1065                    self._from_python(v, state) for v in value])
1066            try:
1067                value = str(value)
1068            except UnicodeEncodeError:
1069                value = unicode(value)
1070        if self.encoding is not None and isinstance(value, unicode):
1071            value = value.encode(self.encoding)
1072        if self.strip:
1073            value = value.strip()
1074        return value
1075
1076    def validate_other(self, value, state):
1077        if (self.max is not None and value is not None
1078            and len(value) > self.max):
1079            raise Invalid(self.message('tooLong', state,
1080                                       max=self.max),
1081                          value, state)
1082        if (self.min is not None
1083            and (not value or len(value) < self.min)):
1084            raise Invalid(self.message('tooShort', state,
1085                                       min=self.min),
1086                          value, state)
1087
1088    def empty_value(self, value):
1089        return ''
1090
1091class UnicodeString(String):
1092    """
1093    Converts things to unicode string, this is a specialization of
1094    the String class.
1095    
1096    In addition to the String arguments, an encoding argument is also
1097    accepted. By default the encoding will be utf-8.
1098    
1099    All converted strings are returned as Unicode strings.
1100    
1101    ::
1102    
1103        >>> UnicodeString().to_python(None)
1104        u''
1105        >>> UnicodeString().to_python([])
1106        u''
1107        >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni')
1108        u'Ni Ni Ni'
1109    
1110    """
1111    encoding = 'utf-8'
1112    messages = {
1113        'badEncoding' : _("Invalid data or incorrect encoding"),
1114    }
1115
1116    def __init__(self, inputEncoding=None, outputEncoding=None, **kw):
1117        String.__init__(self, **kw)
1118        self.inputEncoding = inputEncoding or self.encoding
1119        self.outputEncoding = outputEncoding or self.encoding
1120
1121    def _to_python(self, value, state):
1122        if not value:
1123            return u''
1124        if isinstance(value, unicode):
1125            return value
1126        if not isinstance(value, unicode):
1127            if hasattr(value, '__unicode__'):
1128                value = unicode(value)
1129                return value
1130            else:
1131                value = str(value)
1132        try:
1133            return unicode(value, self.inputEncoding)
1134        except UnicodeDecodeError:
1135            raise Invalid(self.message('badEncoding', state), value, state)
1136        except TypeError:
1137            raise Invalid(self.message('badType', state, type=type(value), value=value), value, state)
1138
1139    def _from_python(self, value, state):
1140        if not isinstance(value, unicode):
1141            if hasattr(value, '__unicode__'):
1142                value = unicode(value)
1143            else:
1144                value = str(value)
1145        if isinstance(value, unicode):
1146            value = value.encode(self.outputEncoding)
1147        return value
1148
1149    def empty_value(self, value):
1150        return u''
1151
1152class Set(FancyValidator):
1153
1154    """
1155    This is for when you think you may return multiple values for a
1156    certain field.
1157
1158    This way the result will always be a list, even if there's only
1159    one result.  It's equivalent to ForEach(convertToList=True).
1160
1161    If you give ``use_set=True``, then it will return an actual
1162    ``sets.Set`` object.
1163
1164    ::
1165
1166       >>> Set.to_python(None)
1167       []
1168       >>> Set.to_python('this')
1169       ['this']
1170       >>> Set.to_python(('this', 'that'))
1171       ['this', 'that']
1172       >>> s = Set(use_set=True)
1173       >>> s.to_python(None)
1174       Set([])
1175       >>> s.to_python('this')
1176       Set(['this'])
1177       >>> s.to_python(('this',))
1178       Set(['this'])
1179    """
1180
1181    use_set = False
1182
1183    if_missing = ()
1184
1185    def _to_python(self, value, state):
1186        if self.use_set:
1187            if isinstance(value, sets.Set):
1188                return value
1189            elif isinstance(value, (list, tuple)):
1190                return sets.Set(value)
1191            elif value is None:
1192                return sets.Set()
1193            else:
1194                return sets.Set([value])
1195        else:
1196            if isinstance(value, list):
1197                return value
1198            elif sets and isinstance(value, sets.Set):
1199                return list(value)
1200            elif isinstance(value, tuple):
1201                return list(value)
1202            elif value is None:
1203                return []
1204            else:
1205                return [value]
1206
1207    def empty_value(self, value):
1208        if self.use_set:
1209            return sets.Set([])
1210        else:
1211            return []
1212
1213class Email(FancyValidator):
1214    r"""
1215    Validate an email address.
1216
1217    If you pass ``resolve_domain=True``, then it will try to resolve
1218    the domain name to make sure it's valid.  This takes longer, of
1219    course.  You must have the `pyDNS <http://pydns.sf.net>`__ modules
1220    installed to look up DNS (MX and A) records.
1221
1222    ::
1223
1224        >>> e = Email()
1225        >>> e.to_python(' test@foo.com ')
1226        'test@foo.com'
1227        >>> e.to_python('test')
1228        Traceback (most recent call last):
1229            ...
1230        Invalid: An email address must contain a single @
1231        >>> e.to_python('test@foobar.com.5')
1232        Traceback (most recent call last):
1233            ...
1234        Invalid: The domain portion of the email address is invalid (the portion after the @: foobar.com.5)
1235        >>> e.to_python('o*reilly@test.com')
1236        'o*reilly@test.com'
1237        >>> e = Email(resolve_domain=True)
1238        >>> e.resolve_domain
1239        True
1240        >>> e.to_python('doesnotexist@colorstudy.com')
1241        'doesnotexist@colorstudy.com'
1242        >>> e.to_python('test@forums.nyu.edu')
1243        'test@forums.nyu.edu'
1244        >>> # NOTE: If you do not have PyDNS installed this example won't work:
1245        >>> e.to_python('test@thisdomaindoesnotexistithinkforsure.com')
1246        Traceback (most recent call last):
1247            ...
1248        Invalid: The domain of the email address does not exist (the portion after the @: thisdomaindoesnotexistithinkforsure.com)
1249        >>> e = Email(not_empty=False)
1250        >>> e.to_python('')
1251        
1252    """
1253
1254    resolve_domain = False
1255
1256    usernameRE = re.compile(r"^[^ \t\n\r@<>()]+$", re.I)
1257    domainRE = re.compile(r"^[a-z0-9][a-z0-9\.\-_]*\.[a-z]+$", re.I)
1258
1259    messages = {
1260        'empty': _('Please enter an email address'),
1261        'noAt': _('An email address must contain a single @'),
1262        'badUsername': _('The username portion of the email address is invalid (the portion before the @: %(username)s)'),
1263        'socketError': _('An error occured when trying to connect to the server: %(error)s'),
1264        'badDomain': _('The domain portion of the email address is invalid (the portion after the @: %(domain)s)'),
1265        'domainDoesNotExist': _('The domain of the email address does not exist (the portion after the @: %(domain)s)'),
1266        }
1267
1268    def __init__(self, *args, **kw):
1269        FancyValidator.__init__(self, *args, **kw)
1270        if self.resolve_domain:
1271            if not have_dns:
1272                import warnings
1273                warnings.warn(
1274                    "pyDNS <http://pydns.sf.net> is not installed on "
1275                    "your system (or the DNS package cannot be found).  "
1276                    "I cannot resolve domain names in addresses")
1277                raise ImportError, "no module named DNS"
1278
1279    def validate_python(self, value, state):
1280        if not value:
1281            raise Invalid(
1282                self.message('empty', state),
1283                value, state)
1284        value = value.strip()
1285        splitted = value.split('@', 1)
1286        try:
1287            username, domain=splitted
1288        except ValueError:
1289            raise Invalid(
1290                self.message('noAt', state),
1291                value, state)
1292        if not self.usernameRE.search(username):
1293            raise Invalid(
1294                self.message('badUsername', state,
1295                             username=username),
1296                value, state)
1297        if not self.domainRE.search(domain):
1298            raise Invalid(
1299                self.message('badDomain', state,
1300                             domain=domain),
1301                value, state)
1302        if self.resolve_domain:
1303            assert have_dns, "pyDNS should be available"
1304            try:
1305                a=DNS.DnsRequest(domain, qtype='mx').req().answers
1306                if not a:
1307                    a=DNS.DnsRequest(domain, qtype='a').req().answers
1308                dnsdomains=[x['data'] for x in a]
1309            except (socket.error, DNS.DNSError), e:
1310                raise Invalid(
1311                    self.message('socketError', state, error=e),
1312                    value, state)
1313            if not dnsdomains:
1314                raise Invalid(
1315                    self.message('domainDoesNotExist', state,
1316                                 domain=domain),
1317                    value, state)
1318
1319    def _to_python(self, value, state):
1320        return value.strip()
1321
1322class URL(FancyValidator):
1323
1324    """
1325    Validate a URL, either http://... or https://.  If check_exists
1326    is true, then we'll actually make a request for the page.
1327
1328    If add_http is true, then if no scheme is present we'll add
1329    http://
1330
1331    ::
1332
1333        >>> u = URL(add_http=True)
1334        >>> u.to_python('foo.com')
1335        'http://foo.com'
1336        >>> u.to_python('http://hahaha/bar.html')
1337        'http://hahaha/bar.html'
1338        >>> u.to_python('https://test.com')
1339        'https://test.com'
1340        >>> u = URL(add_http=False, check_exists=True)
1341        >>> u.to_python('http://google.com')
1342        'http://google.com'
1343        >>> u.to_python('http://colorstudy.com/doesnotexist.html')
1344        Traceback (most recent call last):
1345            ...
1346        Invalid: The server responded that the page could not be found
1347        >>> u.to_python('http://this.domain.does.not.exists.formencode.org/test.html')
1348        Traceback (most recent call last):
1349            ...
1350        Invalid: An error occured when trying to connect to the server: ...
1351        
1352    """
1353
1354    check_exists = False
1355    add_http = True
1356
1357    url_re = re.compile(r'^(http|https)://'
1358                        r'(?:[a-z0-9\-]+|[a-z0-9][a-z0-9\-\.\_]*\.[a-z]+)'
1359                        r'(?::[0-9]+)?'
1360                        r'(?:/.*)?$', re.I)
1361    scheme_re = re.compile(r'^[a-zA-Z]+:')
1362
1363    messages = {
1364        'noScheme': _('You must start your URL with http://, https://, etc'),
1365        'badURL': _('That is not a valid URL'),
1366        'httpError': _('An error occurred when trying to access the URL: %(error)s'),
1367        'socketError': _('An error occured when trying to connect to the server: %(error)s'),
1368        'notFound': _('The server responded that the page could not be found'),
1369        'status': _('The server responded with a bad status code (%(status)s)'),
1370        }
1371
1372    def _to_python(self, value, state):
1373        value = value.strip()
1374        if self.add_http:
1375            if not self.scheme_re.search(value):
1376                value = 'http://' + value
1377        match = self.scheme_re.search(value)
1378        if not match:
1379            raise Invalid(
1380                self.message('noScheme', state),
1381                value, state)
1382        value = match.group(0).lower() + value[len(match.group(0)):]
1383        if not self.url_re.search(value):
1384            raise Invalid(
1385                self.message('badURL', state),
1386                value, state)
1387        if self.check_exists and (value.startswith('http://')
1388                                  or value.startswith('https://')):
1389            self._check_url_exists(value, state)
1390        return value
1391
1392    def _check_url_exists(self, url, state):
1393        global httplib, urlparse, socket
1394        if httplib is None:
1395            import httplib
1396        if urlparse is None:
1397            import urlparse
1398        if socket is None:
1399            import socket
1400        scheme, netloc, path, params, query, fragment = urlparse.urlparse(
1401            url, 'http')
1402        if scheme == 'http':
1403            ConnClass = httplib.HTTPConnection
1404        else:
1405            ConnClass = httplib.HTTPSConnection
1406        try:
1407            conn = ConnClass(netloc)
1408            if params:
1409                path += ';' + params
1410            if query:
1411                path += '?' + query
1412            conn.request('HEAD', path)
1413            res = conn.getresponse()
1414        except httplib.HTTPException, e:
1415            raise Invalid(
1416                self.message('httpError', state, error=e),
1417                state, url)
1418        except socket.error, e:
1419            raise Invalid(
1420                self.message('socketError', state, error=e),
1421                state, url)
1422        else:
1423            if res.status == 404:
1424                raise Invalid(
1425                    self.message('notFound', state),
1426                    state, url)
1427            if (res.status < 200
1428                or res.status >= 500):
1429                raise Invalid(
1430                    self.message('status', state, status=res.status),
1431                    state, url)
1432
1433
1434
1435class StateProvince(FancyValidator):
1436
1437    """
1438    Valid state or province code (two-letter).
1439
1440    Well, for now I don't know the province codes, but it does state
1441    codes.  Give your own `states` list to validate other state-like
1442    codes; give `extra_states` to add values without losing the
1443    current state values.
1444
1445    ::
1446
1447        >>> s = StateProvince('XX')
1448        >>> s.to_python('IL')
1449        'IL'
1450        >>> s.to_python('XX')
1451        'XX'
1452        >>> s.to_python('xx')
1453        'XX'
1454        >>> s.to_python('YY')
1455        Traceback (most recent call last):
1456            ...
1457        Invalid: That is not a valid state code
1458    """
1459
1460    states = ['AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE',
1461              'FL', 'GA', 'HI', 'IA', 'ID', 'IN', 'IL', 'KS', 'KY',
1462              'LA', 'MA', 'MD', 'ME', 'MI', 'MN', 'MO', 'MS', 'MT',
1463              'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH',
1464              'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT',
1465              'VA', 'VT', 'WA', 'WI', 'WV', 'WY']
1466
1467    extra_states = []
1468
1469    __unpackargs__ = ('extra_states',)
1470
1471    messages = {
1472        'empty': _('Please enter a state code'),
1473        'wrongLength': _('Please enter a state code with TWO letters'),
1474        'invalid': _('That is not a valid state code'),
1475        }
1476
1477    def validate_python(self, value, state):
1478        value = str(value).strip().upper()
1479        if not value:
1480            raise Invalid(
1481                self.message('empty', state),
1482                value, state)
1483        if not value or len(value) != 2:
1484            raise Invalid(
1485                self.message('wrongLength', state),
1486                value, state)
1487        if value not in self.states              and not (self.extra_states and value in self.extra_states):
1489            raise Invalid(
1490                self.message('invalid', state),
1491                value, state)
1492
1493    def _to_python(self, value, state):
1494        return str(value).strip().upper()
1495
1496class PhoneNumber(FancyValidator):
1497
1498    """
1499    Validates, and converts to ###-###-####, optionally with extension
1500    (as ext.##...).  Only support US phone numbers.  See
1501    InternationalPhoneNumber for support for that kind of phone number.
1502
1503    ::
1504
1505        >>> p = PhoneNumber()
1506        >>> p.to_python('333-3333')
1507        Traceback (most recent call last):
1508            ...
1509        Invalid: Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####"
1510        >>> p.to_python('555-555-5555')
1511        '555-555-5555'
1512        >>> p.to_python('1-393-555-3939')
1513        '1-393-555-3939'
1514        >>> p.to_python('321.555.4949')
1515        '321.555.4949'
1516        >>> p.to_python('3335550000')
1517        '3335550000'
1518    """
1519    # for emacs: "
1520
1521    _phoneRE = re.compile(r'^\s*(?:1-)?(\d\d\d)[\- \.]?(\d\d\d)[\- \.]?(\d\d\d\d)(?:\s*ext\.?\s*(\d+))?\s*$', re.I)
1522
1523    messages = {
1524        'phoneFormat': _('Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####"'),
1525        }
1526
1527    def _to_python(self, value, state):
1528        self.assert_string(value, state)
1529        match = self._phoneRE.search(value)
1530        if not match:
1531            raise Invalid(
1532                self.message('phoneFormat', state),
1533                value, state)
1534        return value
1535
1536    def _from_python(self, value, state):
1537        self.assert_string(value, state)
1538        match = self._phoneRE.search(value)
1539        if not match:
1540            raise Invalid(self.message('phoneFormat', state),
1541                          value, state)
1542        result = '%s-%s-%s' % (match.group(1), match.group(2), match.group(3))
1543        if match.group(4):
1544            result = result + " ext.%s" % match.group(4)
1545        return result
1546
1547# from dbmanager.intl_util.validators@r313[208:303]
1548class IPhoneNumberValidator(FancyValidator):
1549
1550    """
1551    Validates, and converts phone numbers to +##-###-#######.
1552    Adapted from RFC 3966
1553
1554    ::
1555
1556        >>> p = IPhoneNumberValidator(default_cc=49)
1557        >>> p.to_python('333-3333')
1558        Traceback (most recent call last):
1559            ...
1560        Invalid: Please enter a number, with area code, in the form +##-###-#######.
1561        >>> p.to_python('0555/4860-300')
1562        '+49-555-4860-300'
1563        >>> p.to_python('0555-49924-51')
1564        '+49-555-49924-51'
1565        >>> p.to_python('0555/8114100')
1566        '+49-555-8114100'
1567        >>> p.to_python('0555 8114100')
1568        '+49-555-8114100'
1569        >>> p.to_python(' +49 (0)555 350 60 0')
1570        '+49-555-35060-0'
1571        >>> p.to_python('+49 555 350600')
1572        '+49-555-350600'
1573        >>> p.to_python('0049/ 555/ 871 82 96')
1574        '+49-555-87182-96'
1575        >>> p.to_python('0555-2 50-30')
1576        '+49-555-250-30'
1577        >>> p.to_python('(05 55)4 94 33 47')
1578        '+49-555-49433-47'
1579        >>> p.to_python('+973-555431')
1580        '+973-555431'
1581        >>> p.to_python('1-393-555-3939')
1582        '+1-393-555-3939'
1583        >>> p.to_python('+43 (1) 55528/0')
1584        '+43-1-55528-0'
1585        >>> p.to_python('+43 5555 429 62-0')
1586        '+43-5555-42962-0'
1587        >>> p.to_python('00 218 55 33 50 317 321')
1588        '+218-55-3350317-321'
1589        >>> p.to_python('+218 (0)55-3636639/38')
1590        '+218-55-3636639-38'
1591    """
1592
1593    strip = True
1594    # Use if there's a default country code you want to use:
1595    default_cc = None
1596    _mark_chars_re = re.compile(r"[_.!~*'/]")
1597    _preTransformations = [
1598        (re.compile(r'^\((\d+)\s*(\d+)\)(.+)$'), '%s%s-%s'),
1599        (re.compile(r'^(?:1-)(\d+.+)$'), '+1-%s'),
1600        (re.compile(r'^00\s*(\d+.+)$'), '+%s'),
1601        (re.compile(r'^(\+\d+)\s+\(0\)\s*(\d+.+)$'), '%s-%s'),
1602        (re.compile(r'^(0\d+)\s(\d+)$'), '%s-%s'),
1603        ]
1604    _ccIncluder = [
1605        (re.compile(r'^0([1-9]\d*)\-(\d+.*)$'), '+%d-%s-%s'),
1606        ]
1607    _postTransformations = [
1608        (re.compile(r'^(\+\d+)[-\s]\(?(\d+)\)?[-\s](\d+.+)$'), '%s-%s-%s'),
1609        (re.compile(r'^(.+)\s(\d+)$'), '%s-%s'),
1610        ]
1611    _phoneIsSane = re.compile(r'^(\+[1-9]\d*)-([\d\-]+)$')
1612
1613    messages = {
1614        'phoneFormat': _('Please enter a number, with area code, in the form +##-###-#######.'),
1615        }
1616
1617    def _perform_rex_transformation(self, value, transformations):
1618        for rex, trf in transformations:
1619            match = rex.search(value)
1620            if match:
1621                value = trf % match.groups()
1622        return value
1623
1624    def _prepend_country_code(self, value, transformations, country_code):
1625        for rex, trf in transformations:
1626            match = rex.search(value)
1627            if match:
1628                return trf % ((country_code,)+match.groups())
1629        return value
1630
1631    def _to_python(self, value, state):
1632        self.assert_string(value, state)
1633        value = self._perform_rex_transformation(value, self._preTransformations)
1634        value = self._mark_chars_re.sub('-', value)
1635        if self.default_cc:
1636            value = self._prepend_country_code(value, self._ccIncluder, self.default_cc)
1637        for f, t in [('  ', ' '), ('--', '-'), (' - ', '-'), ('- ', '-'), (' -', '-')]:
1638            value = value.replace(f, t)
1639        value = self._perform_rex_transformation(value, self._postTransformations)
1640        value = value.replace(' ', '')
1641        # did we successfully transform that phone number? Thus, is it valid?
1642        if not self._phoneIsSane.search(value):
1643            raise Invalid(self.message('phoneFormat', state), value, state)
1644        return value
1645
1646
1647
1648
1649class FieldStorageUploadConverter(FancyValidator):
1650    """
1651    Converts a cgi.FieldStorage instance to
1652    a value that FormEncode can use for file
1653    uploads.
1654    """
1655    def _to_python(self, value, state=None):
1656        if isinstance(value, cgi.FieldStorage):
1657            if value.filename:
1658                return value
1659            raise Invalid('invalid', value, state)
1660        else:
1661            return value
1662
1663class FileUploadKeeper(FancyValidator):
1664    """
1665    Takes two inputs (a dictionary with keys ``static`` and
1666    ``upload``) and converts them into one value on the Python side (a
1667    dictionary with ``filename`` and ``content`` keys).  The upload
1668    takes priority over the static value.  The filename may be None if
1669    it can't be discovered.
1670
1671    Handles uploads of both text and ``cgi.FieldStorage`` upload
1672    values.
1673
1674    This is basically for use when you have an upload field, and you
1675    want to keep the upload around even if the rest of the form
1676    submission fails.  When converting *back* to the form submission,
1677    there may be extra values ``'original_filename'`` and
1678    ``'original_content'``, which may want to use in your form to show
1679    the user you still have their content around.
1680
1681    To use this, make sure you are using variabledecode, then use
1682    something like::
1683
1684      <input type="file" name="myfield.upload">
1685      <input type="hidden" name="myfield.static">
1686
1687    Then in your scheme::
1688
1689      class MyScheme(Scheme):
1690          myfield = FileUploadKeeper()
1691
1692    Note that big file uploads mean big hidden fields, and lots of
1693    bytes passed back and forth in the case of an error.
1694    """
1695
1696    upload_key = 'upload'
1697    static_key = 'static'
1698
1699    def _to_python(self, value, state):
1700        upload = value.get(self.upload_key)
1701        static = value.get(self.static_key, '').strip()
1702        filename = content = None
1703        if isinstance(upload, cgi.FieldStorage):
1704            filename = upload.filename
1705            content = upload.value
1706        elif isinstance(upload, basestring) and upload:
1707            filename = None
1708            # @@: Should this encode upload if it is unicode?
1709            content = upload
1710        if not content and static:
1711            filename, content = static.split(None, 1)
1712            if filename == '-':
1713                filename = ''
1714            else:
1715                filename = filename.decode('base64')
1716            content = content.decode('base64')
1717        return {'filename': filename, 'content': content}
1718
1719    def _from_python(self, value, state):
1720        filename = value.get('filename', '')
1721        content = value.get('content', '')
1722        if filename or content:
1723            result = self.pack_content(filename, content)
1724            return {self.upload_key: '',
1725                    self.static_key: result,
1726                    'original_filename': filename,
1727                    'original_content': content}
1728        else:
1729            return {self.upload_key: '',
1730                    self.static_key: ''}
1731
1732    def pack_content(self, filename, content):
1733        enc_filename = self.base64encode(filename) or '-'
1734        enc_content = (content or '').encode('base64')
1735        result = '%s %s' % (enc_filename, enc_content)
1736        return result
1737
1738class DateConverter(FancyValidator):
1739
1740    """
1741    Validates and converts a string date, like mm/yy, dd/mm/yy,
1742    dd-mm-yy, etc.  Using ``month_style`` you can support
1743    ``'mm/dd/yyyy'`` or ``'dd/mm/yyyy'``.  Only these two general
1744    styles are supported.
1745
1746    Accepts English month names, also abbreviated.  Returns value as a
1747    datetime object (you can get mx.DateTime objects if you use
1748    ``datetime_module='mxDateTime'``).  Two year dates are assumed to
1749    be within 1950-2020, with dates from 21-49 being ambiguous and
1750    signaling an error.
1751
1752    Use accept_day=False if you just want a month/year (like for a
1753    credit card expiration date).
1754
1755    ::
1756
1757        >>> d = DateConverter()
1758        >>> d.to_python('12/3/09')
1759        datetime.date(2009, 12, 3)
1760        >>> d.to_python('12/3/2009')
1761        datetime.date(2009, 12, 3)
1762        >>> d.to_python('2/30/04')
1763        Traceback (most recent call last):
1764            ...
1765        Invalid: That month only has 29 days
1766        >>> d.to_python('13/2/05')
1767        Traceback (most recent call last):
1768            ...
1769        Invalid: Please enter a month from 1 to 12
1770    """
1771    ## @@: accepts only US-style dates
1772
1773    accept_day = True
1774    # also allowed: 'dd/mm/yyyy'
1775    month_style = 'mm/dd/yyyy'
1776    # Use 'datetime' to force the Python 2.3+ datetime module, or
1777    # 'mxDateTime' to force the mxDateTime module (None means use
1778    # datetime, or if not present mxDateTime)
1779    datetime_module = None
1780
1781    _day_date_re = re.compile(r'^\s*(\d\d?)[\-\./\\](\d\d?|jan|january|feb|febuary|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)[\-\./\\](\d\d\d?\d?)\s*$', re.I)
1782    _month_date_re = re.compile(r'^\s*(\d\d?|jan|january|feb|febuary|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)[\-\./\\](\d\d\d?\d?)\s*$', re.I)
1783
1784    _month_names = {
1785        'jan': 1, 'january': 1,
1786        'feb': 2, 'febuary': 2,
1787        'mar': 3, 'march': 3,
1788        'apr': 4, 'april': 4,
1789        'may': 5,
1790        'jun': 6, 'june': 6,
1791        'jul': 7, 'july': 7,
1792        'aug': 8, 'august': 8,
1793        'sep': 9, 'sept': 9, 'september': 9,
1794        'oct': 10, 'october': 10,
1795        'nov': 11, 'november': 11,
1796        'dec': 12, 'december': 12,
1797        }
1798
1799    ## @@: Feb. should be leap-year aware (but mxDateTime does catch that)
1800    _monthDays = {
1801        1: 31, 2: 29, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31,
1802        9: 30, 10: 31, 11: 30, 12: 31}
1803
1804    messages = {
1805        'badFormat': _('Please enter the date in the form %(format)s'),
1806        'monthRange': _('Please enter a month from 1 to 12'),
1807        'invalidDay': _('Please enter a valid day'),
1808        'dayRange': _('That month only has %(days)i days'),
1809        'invalidDate': _('That is not a valid day (%(exception)s)'),
1810        'unknownMonthName': _("Unknown month name: %(month)s"),
1811        'invalidYear': _('Please enter a number for the year'),
1812        'fourDigitYear': _('Please enter a four-digit year after 1899'),
1813        'wrongFormat': _('Please enter the date in the form %(format)s'),
1814        }
1815
1816    def _to_python(self, value, state):
1817        if self.accept_day:
1818            return self.convert_day(value, state)
1819        else:
1820            return self.convert_month(value, state)
1821
1822    def convert_day(self, value, state):
1823        self.assert_string(value, state)
1824        match = self._day_date_re.search(value)
1825        if not match:
1826            raise Invalid(self.message('badFormat', state,
1827                                       format=self.month_style),
1828                          value, state)
1829        day = int(match.group(1))
1830        try:
1831            month = int(match.group(2))
1832        except TypeError:
1833            month = self.make_month(match.group(2), state)
1834        else:
1835            if self.month_style == 'mm/dd/yyyy':
1836                month, day = day, month
1837        year = self.make_year(match.group(3), state)
1838        if month > 12 or month < 1:
1839            raise Invalid(self.message('monthRange', state),
1840                          value, state)
1841        if day < 1:
1842            raise Invalid(self.message('invalidDay', state),
1843                          value, state)
1844        if self._monthDays[month] < day:
1845            raise Invalid(self.message('dayRange', state,
1846                                       days=self._monthDays[month]),
1847                          value, state)
1848        dt_mod = import_datetime(self.datetime_module)
1849        try:
1850            return datetime_makedate(dt_mod, year, month, day)
1851        except ValueError, v:
1852            raise Invalid(self.message('invalidDate', state,
1853                                       exception=str(v)),
1854                          value, state)
1855
1856    def make_month(self, value, state):
1857        try:
1858            return int(value)
1859        except ValueError:
1860            value = value.lower().strip()
1861            if self._month_names.has_key(value):
1862                return self._month_names[value]
1863            else:
1864                raise Invalid(self.message('unknownMonthName', state,
1865                                           month=value),
1866                              value, state)
1867
1868    def make_year(self, year, state):
1869        try:
1870            year = int(year)
1871        except ValueError:
1872            raise Invalid(self.message('invalidYear', state),
1873                          year, state)
1874        if year <= 20:
1875            year = year + 2000
1876        if year >= 50 and year < 100:
1877            year = year + 1900
1878        if (year > 20 and year < 50) or (year>99 and year<1900):
1879            raise Invalid(self.message('fourDigitYear', state),
1880                          year, state)
1881        return year
1882
1883    def convert_month(self, value, state):
1884        match = self._month_date_re.search(value)
1885        if not match:
1886            raise Invalid(self.message('wrongFormat', state,
1887                                       format='mm/yyyy'),
1888                          value, state)
1889        month = self.make_month(match.group(1), state)
1890        year = self.make_year(match.group(2), state)
1891        if month > 12 or month < 1:
1892            raise Invalid(self.message('monthRange', state),
1893                          value, state)
1894        dt_mod = import_datetime(self.datetime_module)
1895        return datetime_makedate(dt_mod, year, month, 1)
1896
1897    def _from_python(self, value, state):
1898        if self.if_empty is not NoDefault and not value:
1899            return ''
1900        if self.accept_day:
1901            return self.unconvert_day(value, state)
1902        else:
1903            return self.unconvert_month(value, state)
1904
1905    def unconvert_day(self, value, state):
1906        # @@ ib: double-check, improve
1907        if self.month_style == 'mm/dd/yyyy':
1908            return value.strftime("%m/%d/%Y")
1909        else:
1910            return value.strftime("%d/%m/%Y")
1911
1912    def unconvert_month(self, value, state):
1913        # @@ ib: double-check, improve
1914        return value.strftime("%m/%Y")
1915
1916class TimeConverter(FancyValidator):
1917
1918    """
1919    Converts times in the format HH:MM:SSampm to (h, m, s).
1920    Seconds are optional.
1921
1922    For ampm, set use_ampm = True.  For seconds, use_seconds = True.
1923    Use 'optional' for either of these to make them optional.
1924
1925    Examples::
1926
1927        >>> tim = TimeConverter()
1928        >>> tim.to_python('8:30')
1929        (8, 30)
1930        >>> tim.to_python('20:30')
1931        (20, 30)
1932        >>> tim.to_python('30:00')
1933        Traceback (most recent call last):
1934            ...
1935        Invalid: You must enter an hour in the range 0-23
1936        >>> tim.to_python('13:00pm')
1937        Traceback (most recent call last):
1938            ...
1939        Invalid: You must enter an hour in the range 1-12
1940        >>> tim.to_python('12:-1')
1941        Traceback (most recent call last):
1942            ...
1943        Invalid: You must enter a minute in the range 0-59
1944        >>> tim.to_python('12:02pm')
1945        (12, 2)
1946        >>> tim.to_python('12:02am')
1947        (0, 2)
1948        >>> tim.to_python('1:00PM')
1949        (13, 0)
1950        >>> tim.from_python((13, 0))
1951        '13:00:00'
1952        >>> tim2 = tim(use_ampm=True, use_seconds=False)
1953        >>> tim2.from_python((13, 0))
1954        '1:00pm'
1955        >>> tim2.from_python((0, 0))
1956        '12:00am'
1957        >>> tim2.from_python((12, 0))
1958        '12:00pm'
1959
1960    Examples with ``datetime.time``::
1961
1962        >>> v = TimeConverter(use_datetime=True)
1963        >>> a = v.to_python('18:00')
1964        >>> a
1965        datetime.time(18, 0)
1966        >>> b = v.to_python('30:00')
1967        Traceback (most recent call last):
1968            ...
1969        Invalid: You must enter an hour in the range 0-23
1970        >>> v2 = TimeConverter(prefer_ampm=True, use_datetime=True)
1971        >>> v2.from_python(a)
1972        '6:00:00pm'
1973        >>> v3 = TimeConverter(prefer_ampm=True,
1974        ...                    use_seconds=False, use_datetime=True)
1975        >>> a = v3.to_python('18:00')
1976        >>> a
1977        datetime.time(18, 0)
1978        >>> v3.from_python(a)
1979        '6:00pm'
1980        >>> a = v3.to_python('18:00:00')
1981        Traceback (most recent call last):
1982            ...
1983        Invalid: You may not enter seconds
1984    """
1985
1986    use_ampm = 'optional'
1987    prefer_ampm = False
1988    use_seconds = 'optional'
1989    use_datetime = False
1990    # This can be set to make it prefer mxDateTime:
1991    datetime_module = None
1992
1993    messages = {
1994        'noAMPM': _('You must indicate AM or PM'),
1995        'tooManyColon': _('There are too many :\'s'),
1996        'noSeconds': _('You may not enter seconds'),
1997        'secondsRequired': _('You must enter seconds'),
1998        'minutesRequired': _('You must enter minutes (after a :)'),
1999        'badNumber': _('The %(part)s value you gave is not a number: %(number)r'),
2000        'badHour': _('You must enter an hour in the range %(range)s'),
2001        'badMinute': _('You must enter a minute in the range 0-59'),
2002        'badSecond': _('You must enter a second in the range 0-59'),
2003        }
2004
2005    def _to_python(self, value, state):
2006        result = self._to_python_tuple(value, state)
2007        if self.use_datetime:
2008            dt_mod = import_datetime(self.datetime_module)
2009            time_class = datetime_time(dt_mod)
2010            return time_class(*result)
2011        else:
2012            return result
2013
2014    def _to_python_tuple(self, value, state):
2015        time = value.strip()
2016        explicit_ampm = False
2017        if self.use_ampm:
2018            last_two = time[-2:].lower()
2019            if last_two not in ('am', 'pm'):
2020                if self.use_ampm != 'optional':
2021                    raise Invalid(
2022                        self.message('noAMPM', state),
2023                        value, state)
2024                else:
2025                    offset = 0
2026            else:
2027                explicit_ampm = True
2028                if last_two == 'pm':
2029                    offset = 12
2030                else:
2031                    offset = 0
2032                time = time[:-2]
2033        else:
2034            offset = 0
2035        parts = time.split(':')
2036        if len(parts) > 3:
2037            raise Invalid(
2038                self.message('tooManyColon', state),
2039                value, state)
2040        if len(parts) == 3 and not self.use_seconds:
2041            raise Invalid(
2042                self.message('noSeconds', state),
2043                value, state)
2044        if (len(parts) == 2
2045            and self.use_seconds
2046            and self.use_seconds != 'optional'):
2047            raise Invalid(
2048                self.message('secondsRequired', state),
2049                value, state)
2050        if len(parts) == 1:
2051            raise Invalid(
2052                self.message('minutesRequired', state),
2053                value, state)
2054        try:
2055            hour = int(parts[0])
2056        except ValueError:
2057            raise Invalid(
2058                self.message('badNumber', state, number=parts[0], part='hour'),
2059                value, state)
2060        if explicit_ampm:
2061            if hour > 12 or hour < 1:
2062                raise Invalid(
2063                    self.message('badHour', state, number=hour, range='1-12'),
2064                    value, state)
2065            if hour == 12 and offset == 12:
2066                # 12pm == 12
2067                pass
2068            elif hour == 12 and offset == 0:
2069                # 12am == 0
2070                hour = 0
2071            else:
2072                hour += offset
2073        else:
2074            if hour > 23 or hour < 0:
2075                raise Invalid(
2076                    self.message('badHour', state,
2077                                 number=hour, range='0-23'),
2078                    value, state)
2079        try:
2080            minute = int(parts[1])
2081        except ValueError:
2082            raise Invalid(
2083                self.message('badNumber', state,
2084                             number=parts[1], part='minute'),
2085                value, state)
2086        if minute > 59 or minute < 0:
2087            raise Invalid(
2088                self.message('badMinute', state, number=minute),
2089                value, state)
2090        if len(parts) == 3:
2091            try:
2092                second = int(parts[2])
2093            except ValueError:
2094                raise Invalid(
2095                    self.message('badNumber', state,
2096                                 number=parts[2], part='second'))
2097            if second > 59 or second < 0:
2098                raise Invalid(
2099                    self.message('badSecond', state, number=second),
2100                    value, state)
2101        else:
2102            second = None
2103        if second is None:
2104            return (hour, minute)
2105        else:
2106            return (hour, minute, second)
2107
2108    def _from_python(self, value, state):
2109        if isinstance(value, (str, unicode)):
2110            return value
2111        if hasattr(value, 'hour'):
2112            hour, minute = value.hour, value.minute
2113            second = value.second
2114        elif len(value) == 3:
2115            hour, minute, second = value
2116        elif len(value) == 2:
2117            hour, minute = value
2118            second = 0
2119        ampm = ''
2120        if ((self.use_ampm == 'optional' and self.prefer_ampm)
2121            or (self.use_ampm and self.use_ampm != 'optional')):
2122            ampm = 'am'
2123            if hour > 12:
2124                hour -= 12
2125                ampm = 'pm'
2126            elif hour == 12:
2127                ampm = 'pm'
2128            elif hour == 0:
2129                hour = 12
2130        if self.use_seconds:
2131            return '%i:%02i:%02i%s' % (hour, minute, second, ampm)
2132        else:
2133            return '%i:%02i%s' % (hour, minute, ampm)
2134
2135class PostalCode(Regex):
2136
2137    """
2138    US Postal codes (aka Zip Codes).
2139
2140    ::
2141
2142        >>> PostalCode.to_python('55555')
2143        '55555'
2144        >>> PostalCode.to_python('55555-5555')
2145        '55555-5555'
2146        >>> PostalCode.to_python('5555')
2147        Traceback (most recent call last):
2148            ...
2149        Invalid: Please enter a zip code (5 digits)
2150    """
2151
2152    regex = r'^\d\d\d\d\d(?:-\d\d\d\d)?$'
2153    strip = True
2154
2155    messages = {
2156        'invalid': _('Please enter a zip code (5 digits)'),
2157        }
2158
2159class StripField(FancyValidator):
2160
2161    """
2162    Take a field from a dictionary, removing the key from the
2163    dictionary.
2164
2165    ``name`` is the key.  The field value and a new copy of the
2166    dictionary with that field removed are returned.
2167
2168    >>> StripField('test').to_python({'a': 1, 'test': 2})
2169    (2, {'a': 1})
2170    >>> StripField('test').to_python({})
2171    Traceback (most recent call last):
2172        ...
2173    Invalid: The name 'test' is missing
2174    
2175    """
2176
2177    __unpackargs__ = ('name',)
2178
2179    messages = {
2180        'missing': _('The name %(name)s is missing'),
2181        }
2182
2183    def _to_python(self, valueDict, state):
2184        v = valueDict.copy()
2185        try:
2186            field = v[self.name]
2187            del v[self.name]
2188        except KeyError:
2189            raise Invalid(self.message('missing', state,
2190                                       name=repr(self.name)),
2191                          valueDict, state)
2192        return field, v
2193
2194    def is_empty(self, value):
2195        ## Empty dictionaries don't really apply here
2196        return False
2197
2198class StringBool(FancyValidator):
2199    # Originally from TurboGears
2200    """
2201    Converts a string to a boolean.
2202    
2203    Values like 'true' and 'false' are considered True and False,
2204    respectively; anything in ``true_values`` is true, anything in
2205    ``false_values`` is false, case-insensitive).  The first item of
2206    those lists is considered the preferred form.
2207
2208    ::
2209
2210        >>> s = StringBoolean()
2211        >>> s.to_python('yes'), s.to_python('no')
2212        (True, False)
2213        >>> s.to_python(1), s.to_python('N')
2214        (True, False)
2215        >>> s.to_python('ye')
2216        Traceback (most recent call last):
2217            ...
2218        Invalid: Value should be 'true' or 'false'
2219    """
2220
2221    true_values = ['true', 't', 'yes', 'y', 'on', '1']
2222    false_values = ['false', 'f', 'no', 'n', 'off', '0']
2223
2224    messages = { "string" : _("Value should be %(true)r or %(false)r") }
2225
2226    def _to_python(self, value, state):
2227        if isinstance(value, (str, unicode)):
2228            value = value.strip().lower()
2229            if value in self.true_values:
2230                return True
2231            if not value or value in self.false_values:
2232                return False
2233            raise Invalid(self.message("string", state,
2234                                       true=self.true_values[0],
2235                                       false=self.false_values[0]),
2236                          value, state)
2237        return bool(value)
2238
2239    def _from_python(self, value, state):
2240        if value:
2241            return self.true_values[0]
2242        else:
2243            return self.false_values[0]
2244
2245# Should deprecate:
2246StringBoolean = StringBool
2247
2248class SignedString(FancyValidator):
2249
2250    """
2251    Encodes a string into a signed string, and base64 encodes both the
2252    signature string and a random nonce.
2253
2254    It is up to you to provide a secret, and to keep the secret handy
2255    and consistent.
2256    """
2257
2258    messages = {
2259        'malformed': _('Value does not contain a signature'),
2260        'badsig': _('Signature is not correct'),
2261        }
2262
2263    secret = None
2264    nonce_length = 4
2265
2266    def _to_python(self, value, state):
2267        global sha
2268        if not sha:
2269            import sha
2270        assert self.secret is not None, (
2271            "You must give a secret")
2272        parts = value.split(None, 1)
2273        if not parts or len(parts) == 1:
2274            raise Invalid(self.message('malformed', state),
2275                          value, state)
2276        sig, rest = parts
2277        sig = sig.decode('base64')
2278        rest = rest.decode('base64')
2279        nonce = rest[:self.nonce_length]
2280        rest = rest[self.nonce_length:]
2281        expected = sha.new(str(self.secret)+nonce+rest).digest()
2282        if expected != sig:
2283            raise Invalid(self.message('badsig', state),
2284                          value, state)
2285        return rest
2286
2287    def _from_python(self, value, state):
2288        global sha
2289        if not sha:
2290            import sha
2291        nonce = self.make_nonce()
2292        value = str(value)
2293        digest = sha.new(self.secret+nonce+value).digest()
2294        return self.encode(digest)+' '+self.encode(nonce+value)
2295
2296    def encode(self, value):
2297        return value.encode('base64').strip().replace('\n', '')
2298
2299    def make_nonce(self):
2300        global random
2301        if not random:
2302            import random
2303        return ''.join([
2304            chr(random.randrange(256))
2305            for i in range(self.nonce_length)])
2306
2307class CIDR(FancyValidator):
2308    """
2309    Formencode validator to check whether a string is in correct CIDR
2310    notation (IP address, or IP address plus /mask)
2311
2312    Examples::
2313
2314        >>> cidr = CIDR()
2315        >>> cidr.to_python('127.0.0.1')
2316        '127.0.0.1'
2317        >>> cidr.to_python('299.0.0.1')
2318        Traceback (most recent call last):
2319            ...
2320        Invalid: The octets must be within the range of 0-255 (not '299')
2321        >>> cidr.to_python('192.168.0.1/1')
2322        Traceback (most recent call last):
2323            ...
2324        Invalid: The network size (bits) must be within the range of 8-32 (not '1')
2325        >>> cidr.to_python('asdf')
2326        Traceback (most recent call last):
2327            ...
2328        Invalid: Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)
2329    """
2330    messages = {
2331            'not_cidr_format' : u'Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)',
2332            'illegal_octets' : u'The octets must be within the range of 0-255 (not %(octet)r)',
2333            'illegal_bits' : u'The network size (bits) must be within the range of 8-32 (not %(bits)r)',
2334            }
2335
2336    def validate_python(self, value, state):
2337        try:
2338            # Split into octets and bits
2339            if '/' in value: # a.b.c.d/e
2340                addr, bits = value.split('/')
2341            else: # a.b.c.d
2342                addr, bits = value, 32
2343
2344            octets = addr.split('.')
2345
2346            # Only 4 octets?
2347            if len(octets) != 4:
2348                raise Invalid(self.message("not_cidr_format", state, value=value), value, state)
2349
2350            # Correct octets?
2351            for octet in octets:
2352                if int(octet) < 0 or int(octet) > 255:
2353                    raise Invalid(self.message("illegal_octets", state, octet=octet), value, state)
2354
2355            # Bits (netmask) correct?
2356            if int(bits) < 8 or int(bits) > 32:
2357                    raise Invalid(self.message("illegal_bits", state, bits=bits), value, state)
2358
2359        # Splitting faild: wrong syntax
2360        except ValueError:
2361            raise Invalid(self.message("not_cidr_format", state), value, state)
2362
2363class MACAddress(FancyValidator):
2364    """
2365    Formencode validator to check whether a string is a correct hardware
2366    (MAC) address.
2367
2368    Examples::
2369
2370        >>> mac = MACAddress()
2371        >>> mac.to_python('aa:bb:cc:dd:ee:ff')
2372        'aabbccddeeff'
2373        >>> mac.to_python('aa:bb:cc:dd:ee:ff:e')
2374        Traceback (most recent call last):
2375            ...
2376        Invalid: A MAC address must contain 12 digits and A-F; the value you gave has 13 characters
2377        >>> mac.to_python('aa:bb:cc:dd:ee:fx')
2378        Traceback (most recent call last):
2379            ...
2380        Invalid: MAC addresses may only contain 0-9 and A-F (and optionally :), not 'x'
2381        >>> MACAddress(add_colons=True).to_python('aabbccddeeff')
2382        'aa:bb:cc:dd:ee:ff'
2383    """
2384
2385    strip=True
2386    valid_characters = '0123456789abcdefABCDEF'
2387    add_colons = False
2388
2389    messages = {
2390        'bad_length': _(u'A MAC address must contain 12 digits and A-F; the value you gave has %(length)s characters'),
2391        'bad_character': _(u'MAC addresses may only contain 0-9 and A-F (and optionally :), not %(char)r'),
2392        }
2393
2394    def _to_python(self, value, state):
2395        address = value.replace(':','') # remove colons
2396        address = address.lower()
2397        if len(address)!=12:
2398            raise Invalid(self.message("bad_length", state, length=len(address)), address, state)
2399        for char in address:
2400            if char not in self.valid_characters:
2401                raise Invalid(self.message("bad_character", state, char=char), address, state)
2402        if self.add_colons:
2403            address = '%s:%s:%s:%s:%s:%s' % (
2404                address[0:2], address[2:4], address[4:6],
2405                address[6:8], address[8:10], address[10:12])
2406        return address
2407
2408    _from_python = _to_python
2409
2410class FormValidator(FancyValidator):
2411    """
2412    A FormValidator is something that can be chained with a
2413    Schema.  Unlike normal chaining the FormValidator can 
2414    validate forms that aren't entirely valid.
2415
2416    The important method is .validate(), of course.  It gets passed a
2417    dictionary of the (processed) values from the form.  If you have
2418    .validate_partial_form set to True, then it will get the incomplete
2419    values as well -- use .has_key() to test if the field was able to
2420    process any particular field.
2421
2422    Anyway, .validate() should return a string or a dictionary.  If a
2423    string, it's an error message that applies to the whole form.  If
2424    not, then it should be a dictionary of fieldName: errorMessage.
2425    The special key "form" is the error message for the form as a whole
2426    (i.e., a string is equivalent to {"form": string}).
2427
2428    Return None on no errors.
2429    """
2430
2431    validate_partial_form = False
2432
2433    validate_partial_python = None
2434    validate_partial_other = None
2435
2436class RequireIfMissing(FormValidator):
2437
2438    # Field that potentially is required:
2439    required = None
2440    # If this field is missing, then it is required:
2441    missing = None
2442    # If this field is present, then it is required:
2443    present = None
2444    __unpackargs__ = ('required',)
2445
2446    def _to_python(self, value_dict, state):
2447        is_required = False
2448        if self.missing and not value_dict.get(self.missing):
2449            is_required = True
2450        if self.present and value_dict.get(self.present):
2451            is_required = True
2452        if is_required and not value_dict.get(self.required):
2453            raise Invalid('You must give a value for %s' % self.required,
2454                          value, state,
2455                          error_dict={self.required: Invalid(self.message(
2456                              'empty', state), value, state)})
2457        return value_dict
2458
2459RequireIfPresent = RequireIfMissing
2460
2461class FieldsMatch(FormValidator):
2462
2463    """
2464    Tests that the given fields match, i.e., are identical.  Useful
2465    for password+confirmation fields.  Pass the list of field names in
2466    as `field_names`.
2467
2468    ::
2469
2470        >>> f = FieldsMatch('pass', 'conf')
2471        >>> f.to_python({'pass': 'xx', 'conf': 'xx'})
2472        {'conf': 'xx', 'pass': 'xx'}
2473        >>> f.to_python({'pass': 'xx', 'conf': 'yy'})
2474        Traceback (most recent call last):
2475            ...
2476        Invalid: conf: Fields do not match
2477    """
2478
2479    show_match = False
2480    field_names = None
2481    validate_partial_form = True
2482    __unpackargs__ = ('*', 'field_names')
2483
2484    messages = {
2485        'invalid': _("Fields do not match (should be %(match)s)"),
2486        'invalidNoMatch': _("Fields do not match"),
2487        }
2488
2489    def validate_partial(self, field_dict, state):
2490        for name in self.field_names:
2491            if not field_dict.has_key(name):
2492                return
2493        self.validate_python(field_dict, state)
2494
2495    def validate_python(self, field_dict, state):
2496        ref = field_dict[self.field_names[0]]
2497        errors = {}
2498        for name in self.field_names[1:]:
2499            if field_dict.get(name, '') != ref:
2500                if self.show_match:
2501                    errors[name] = self.message('invalid', state,
2502                                                match=ref)
2503                else:
2504                    errors[name] = self.message('invalidNoMatch', state)
2505        if errors:
2506            error_list = errors.items()
2507            error_list.sort()
2508            error_message = '<br>\n'.join(
2509                ['%s: %s' % (name, value) for name, value in error_list])
2510            raise Invalid(error_message,
2511                          field_dict, state,
2512                          error_dict=errors)
2513
2514class CreditCardValidator(FormValidator):
2515    """
2516    Checks that credit card numbers are valid (if not real).
2517
2518    You pass in the name of the field that has the credit card
2519    type and the field with the credit card number.  The credit
2520    card type should be one of "visa", "mastercard", "amex",
2521    "dinersclub", "discover", "jcb".
2522
2523    You must check the expiration date yourself (there is no
2524    relation between CC number/types and expiration dates).
2525
2526    ::
2527
2528        >>> cc = CreditCardValidator()
2529        >>> cc.to_python({'ccType': 'visa', 'ccNumber': '4111111111111111'})
2530        {'ccNumber': '4111111111111111', 'ccType': 'visa'}
2531        >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111111'})
2532        Traceback (most recent call last):
2533            ...
2534        Invalid: ccNumber: You did not enter a valid number of digits
2535        >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111112'})
2536        Traceback (most recent call last):
2537            ...
2538        Invalid: ccNumber: You did not enter a valid number of digits
2539    """
2540
2541    validate_partial_form = True
2542
2543    cc_type_field = 'ccType'
2544    cc_number_field = 'ccNumber'
2545    __unpackargs__ = ('cc_type_field', 'cc_number_field')
2546
2547    messages = {
2548        'notANumber': _("Please enter only the number, no other characters"),
2549        'badLength': _("You did not enter a valid number of digits"),
2550        'invalidNumber': _("That number is not valid"),
2551        }
2552
2553    def validate_partial(self, field_dict, state):
2554        if not field_dict.get(self.cc_type_field, None)              or not field_dict.get(self.cc_number_field, None):
2556            return None
2557        self.validate_python(field_dict, state)
2558
2559    def validate_python(self, field_dict, state):
2560        errors = self._validateReturn(field_dict, state)
2561        if errors:
2562            error_list = errors.items()
2563            error_list.sort()
2564            raise Invalid(
2565                '<br>\n'.join(["%s: %s" % (name, value)
2566                               for name, value in error_list]),
2567                field_dict, state, error_dict=errors)
2568
2569    def _validateReturn(self, field_dict, state):
2570        ccType = field_dict[self.cc_type_field].lower().strip()
2571        number = field_dict[self.cc_number_field].strip()
2572        number = number.replace(' ', '')
2573        number = number.replace('-', '')
2574        try:
2575            long(number)
2576        except ValueError:
2577            return {self.cc_number_field: self.message('notANumber', state)}
2578
2579        assert self._cardInfo.has_key(ccType), (
2580            "I can't validate that type of credit card")
2581        foundValid = False
2582        validLength = False
2583        for prefix, length in self._cardInfo[ccType]:
2584            if len(number) == length:
2585                validLength = True
2586            if (len(number) == length
2587                and number.startswith(prefix)):
2588                foundValid = True
2589                break
2590        if not validLength:
2591            return {self.cc_number_field: self.message('badLength', state)}
2592        if not foundValid:
2593            return {self.cc_number_field: self.message('invalidNumber', state)}
2594
2595        if not self._validateMod10(number):
2596            return {self.cc_number_field: self.message('invalidNumber', state)}
2597        return None
2598
2599    def _validateMod10(self, s):
2600        """
2601        This code by Sean Reifschneider, of tummy.com
2602        """
2603        double = 0
2604        sum = 0
2605        for i in range(len(s) - 1, -1, -1):
2606            for c in str((double + 1) * int(s[i])):
2607                sum = sum + int(c)
2608            double = (double + 1) % 2
2609        return((sum % 10) == 0)
2610
2611    _cardInfo = {
2612        "visa": [('4', 16),
2613                 ('4', 13)],
2614        "mastercard": [('51', 16),
2615                       ('52', 16),
2616                       ('53', 16),
2617                       ('54', 16),
2618                       ('55', 16)],
2619        "discover": [('6011', 16)],
2620        "amex": [('34', 15),
2621                 ('37', 15)],
2622        "dinersclub": [('300', 14),
2623                       ('301', 14),
2624                       ('302', 14),
2625                       ('303', 14),
2626                       ('304', 14),
2627                       ('305', 14),
2628                       ('36', 14),
2629                       ('38', 14)],
2630        "jcb": [('3', 16),
2631                ('2131', 15),
2632                ('1800', 15)],
2633            }
2634
2635class CreditCardExpires(FormValidator):
2636    """
2637    Checks that credit card expiration date is valid relative to 
2638    the current date.
2639
2640    You pass in the name of the field that has the credit card
2641    expiration month and the field with the credit card expiration 
2642    year.  
2643
2644    ::
2645
2646        >>> ed = CreditCardExpires()
2647        >>> ed.to_python({'ccExpiresMonth': '11', 'ccExpiresYear': '2250'})
2648        {'ccExpiresYear': '2250', 'ccExpiresMonth': '11'}
2649        >>> ed.to_python({'ccExpiresMonth': '10', 'ccExpiresYear': '2005'})
2650        Traceback (most recent call last):
2651            ...
2652        Invalid: ccExpiresMonth: Invalid Expiration Date<br>
2653        ccExpiresYear: Invalid Expiration Date
2654    """
2655
2656    validate_partial_form = True
2657
2658    cc_expires_month_field = 'ccExpiresMonth'
2659    cc_expires_year_field = 'ccExpiresYear'
2660    __unpackargs__ = ('cc_expires_month_field', 'cc_expires_year_field')
2661
2662    datetime_module = None
2663
2664    messages = {
2665        'notANumber': _("Please enter numbers only for month and year"),
2666        'invalidNumber': _("Invalid Expiration Date"),
2667        }
2668
2669    def validate_partial(self, field_dict, state):
2670        if not field_dict.get(self.cc_expires_month_field, None)              or not field_dict.get(self.cc_expires_year_field, None):
2672            return None
2673        self.validate_python(field_dict, state)
2674
2675    def validate_python(self, field_dict, state):
2676        errors = self._validateReturn(field_dict, state)
2677        if errors:
2678            error_list = errors.items()
2679            error_list.sort()
2680            raise Invalid(
2681                '<br>\n'.join(["%s: %s" % (name, value)
2682                               for name, value in error_list]),
2683                field_dict, state, error_dict=errors)
2684
2685    def _validateReturn(self, field_dict, state):
2686        ccExpiresMonth = str(field_dict[self.cc_expires_month_field]).strip()
2687        ccExpiresYear = str(field_dict[self.cc_expires_year_field]).strip()
2688
2689        try:
2690            ccExpiresMonth = int(ccExpiresMonth)
2691            ccExpiresYear = int(ccExpiresYear)
2692            dt_mod = import_datetime(self.datetime_module)
2693            now = datetime_now(dt_mod)
2694            today = datetime_makedate(dt_mod, now.year, now.month, now.day)
2695            next_month = (ccExpiresMonth % 12) + 1
2696            if next_month == 1:
2697                next_month_year = ccExpiresYear + 1
2698            else:
2699                next_month_year = ccExpiresYear
2700            expires_date = datetime_makedate(dt_mod, next_month_year, next_month, 1)
2701            assert expires_date > today
2702        except ValueError:
2703            return {self.cc_expires_month_field: self.message('notANumber', state),
2704                    self.cc_expires_year_field: self.message('notANumber', state)}
2705        except AssertionError:
2706            return {self.cc_expires_month_field: self.message('invalidNumber', state),
2707                    self.cc_expires_year_field: self.message('invalidNumber', state)}
2708
2709class CreditCardSecurityCode(FormValidator):
2710    """
2711    Checks that credit card security code has the correct number
2712    of digits for the given credit card type.
2713
2714    You pass in the name of the field that has the credit card
2715    type and the field with the credit card security code.
2716
2717    ::
2718
2719        >>> code = CreditCardSecurityCode()
2720        >>> code.to_python({'ccType': 'visa', 'ccCode': '111'})
2721        {'ccType': 'visa', 'ccCode': '111'}
2722        >>> code.to_python({'ccType': 'visa', 'ccCode': '1111'})
2723        Traceback (most recent call last):
2724            ...
2725        Invalid: ccCode: Invalid credit card security code length
2726    """
2727
2728    validate_partial_form = True
2729
2730    cc_type_field = 'ccType'
2731    cc_code_field = 'ccCode'
2732    __unpackargs__ = ('cc_type_field', 'cc_code_field')
2733
2734    messages = {
2735        'notANumber': _("Please enter numbers only for credit card security code"),
2736        'badLength': _("Invalid credit card security code length"),
2737        }
2738
2739    def validate_partial(self, field_dict, state):
2740        if not field_dict.get(self.cc_type_field, None)              or not field_dict.get(self.cc_code_field, None):
2742            return None
2743        self.validate_python(field_dict, state)
2744
2745    def validate_python(self, field_dict, state):
2746        errors = self._validateReturn(field_dict, state)
2747        if errors:
2748            error_list = errors.items()
2749            error_list.sort()
2750            raise Invalid(
2751                '<br>\n'.join(["%s: %s" % (name, value)
2752                               for name, value in error_list]),
2753                field_dict, state, error_dict=errors)
2754
2755    def _validateReturn(self, field_dict, state):
2756        ccType = str(field_dict[self.cc_type_field]).strip()
2757        ccCode = str(field_dict[self.cc_code_field]).strip()
2758
2759        try:
2760            int(ccCode)
2761        except ValueError:
2762            return {self.cc_code_field: self.message('notANumber', state)}
2763
2764        length = self._cardInfo[ccType]
2765        validLength = False
2766        if len(ccCode) == length:
2767            validLength = True
2768        if not validLength:
2769            return {self.cc_code_field: self.message('badLength', state)}
2770
2771    # key = credit card type
2772    # value = length of security code
2773    _cardInfo = {
2774        "visa": 3,
2775        "mastercard": 3,
2776        "discover": 3,
2777        "amex": 4,
2778            }
2779
2780
2781__all__ = ['Invalid']
2782for name, value in globals().items():
2783    if isinstance(value, type) and issubclass(value, Validator):
2784        __all__.append(name)