0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
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
0053
0054
0055
0056
0057
0058
0059
0060
0061
0062
0063
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
0107def datetime_time(module):
0108 if module.__name__ == 'datetime':
0109 return module.time
0110
0111
0112def datetime_isotime(module):
0113 if module.__name__ == 'datetime':
0114 return module.time.isoformat
0115
0116
0117
0118
0119
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
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
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
0806 today_or_after = False
0807
0808
0809
0810 datetime_module = None
0811
0812 messages = {
0813 'after': _("Date must be after %(date)s"),
0814 'before': _("Date must be before %(date)s"),
0815
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
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
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
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
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
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
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
1772
1773 accept_day = True
1774
1775 month_style = 'mm/dd/yyyy'
1776
1777
1778
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
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
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
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
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
2067 pass
2068 elif hour == 12 and offset == 0:
2069
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
2196 return False
2197
2198class StringBool(FancyValidator):
2199
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
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
2339 if '/' in value:
2340 addr, bits = value.split('/')
2341 else:
2342 addr, bits = value, 32
2343
2344 octets = addr.split('.')
2345
2346
2347 if len(octets) != 4:
2348 raise Invalid(self.message("not_cidr_format", state, value=value), value, state)
2349
2350
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
2356 if int(bits) < 8 or int(bits) > 32:
2357 raise Invalid(self.message("illegal_bits", state, bits=bits), value, state)
2358
2359
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(':','')
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
2439 required = None
2440
2441 missing = None
2442
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
2772
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)