PageRenderTime 72ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 1ms

/formencode/validators.py

https://github.com/mofukuma/gae_onlinegamebase
Python | 2997 lines | 2883 code | 48 blank | 66 comment | 68 complexity | a3c37d079b41e7bee5e9f12da31a1f15 MD5 | raw file
  1. ## FormEncode, a Form processor
  2. ## Copyright (C) 2003, Ian Bicking <ianb@colorstudy.com>
  3. """
  4. Validator/Converters for use with FormEncode.
  5. """
  6. import cgi
  7. import locale
  8. import re
  9. import warnings
  10. try: # import dnspython
  11. import dns.resolver
  12. import dns.exception
  13. from encodings import idna
  14. except (IOError, ImportError):
  15. have_dns = False
  16. else:
  17. have_dns = True
  18. # These are only imported when needed
  19. httplib = None
  20. random = None
  21. sha1 = None
  22. socket = None
  23. urlparse = None
  24. from api import (FancyValidator, Identity, Invalid, NoDefault, Validator,
  25. deprecation_warning, is_empty)
  26. assert Identity and Invalid and NoDefault # silence unused import warnings
  27. # Dummy i18n translation function, nothing is translated here.
  28. # Instead this is actually done in api.message.
  29. # The surrounding _('string') of the strings is only for extracting
  30. # the strings automatically.
  31. # If you run pygettext with this source comment this function out temporarily.
  32. _ = lambda s: s
  33. ############################################################
  34. ## Utility methods
  35. ############################################################
  36. # These all deal with accepting both datetime and mxDateTime modules and types
  37. datetime_module = None
  38. mxDateTime_module = None
  39. def import_datetime(module_type):
  40. global datetime_module, mxDateTime_module
  41. module_type = module_type.lower() if module_type else 'datetime'
  42. if module_type == 'datetime':
  43. if datetime_module is None:
  44. import datetime as datetime_module
  45. return datetime_module
  46. elif module_type == 'mxdatetime':
  47. if mxDateTime_module is None:
  48. from mx import DateTime as mxDateTime_module
  49. return mxDateTime_module
  50. else:
  51. raise ImportError('Invalid datetime module %r' % module_type)
  52. def datetime_now(module):
  53. if module.__name__ == 'datetime':
  54. return module.datetime.now()
  55. else:
  56. return module.now()
  57. def datetime_makedate(module, year, month, day):
  58. if module.__name__ == 'datetime':
  59. return module.date(year, month, day)
  60. else:
  61. try:
  62. return module.DateTime(year, month, day)
  63. except module.RangeError, e:
  64. raise ValueError(str(e))
  65. def datetime_time(module):
  66. if module.__name__ == 'datetime':
  67. return module.time
  68. else:
  69. return module.Time
  70. def datetime_isotime(module):
  71. if module.__name__ == 'datetime':
  72. return module.time.isoformat
  73. else:
  74. return module.ISO.Time
  75. ############################################################
  76. ## Wrapper Validators
  77. ############################################################
  78. class ConfirmType(FancyValidator):
  79. """
  80. Confirms that the input/output is of the proper type.
  81. Uses the parameters:
  82. subclass:
  83. The class or a tuple of classes; the item must be an instance
  84. of the class or a subclass.
  85. type:
  86. A type or tuple of types (or classes); the item must be of
  87. the exact class or type. Subclasses are not allowed.
  88. Examples::
  89. >>> cint = ConfirmType(subclass=int)
  90. >>> cint.to_python(True)
  91. True
  92. >>> cint.to_python('1')
  93. Traceback (most recent call last):
  94. ...
  95. Invalid: '1' is not a subclass of <type 'int'>
  96. >>> cintfloat = ConfirmType(subclass=(float, int))
  97. >>> cintfloat.to_python(1.0), cintfloat.from_python(1.0)
  98. (1.0, 1.0)
  99. >>> cintfloat.to_python(1), cintfloat.from_python(1)
  100. (1, 1)
  101. >>> cintfloat.to_python(None)
  102. Traceback (most recent call last):
  103. ...
  104. Invalid: None is not a subclass of one of the types <type 'float'>, <type 'int'>
  105. >>> cint2 = ConfirmType(type=int)
  106. >>> cint2(accept_python=False).from_python(True)
  107. Traceback (most recent call last):
  108. ...
  109. Invalid: True must be of the type <type 'int'>
  110. """
  111. subclass = None
  112. type = None
  113. messages = dict(
  114. subclass=_('%(object)r is not a subclass of %(subclass)s'),
  115. inSubclass=_('%(object)r is not a subclass of one of the types %(subclassList)s'),
  116. inType=_('%(object)r must be one of the types %(typeList)s'),
  117. type=_('%(object)r must be of the type %(type)s'))
  118. def __init__(self, *args, **kw):
  119. FancyValidator.__init__(self, *args, **kw)
  120. if self.subclass:
  121. if isinstance(self.subclass, list):
  122. self.subclass = tuple(self.subclass)
  123. elif not isinstance(self.subclass, tuple):
  124. self.subclass = (self.subclass,)
  125. self._validate_python = self.confirm_subclass
  126. if self.type:
  127. if isinstance(self.type, list):
  128. self.type = tuple(self.type)
  129. elif not isinstance(self.type, tuple):
  130. self.type = (self.type,)
  131. self._validate_python = self.confirm_type
  132. def confirm_subclass(self, value, state):
  133. if not isinstance(value, self.subclass):
  134. if len(self.subclass) == 1:
  135. msg = self.message('subclass', state, object=value,
  136. subclass=self.subclass[0])
  137. else:
  138. subclass_list = ', '.join(map(str, self.subclass))
  139. msg = self.message('inSubclass', state, object=value,
  140. subclassList=subclass_list)
  141. raise Invalid(msg, value, state)
  142. def confirm_type(self, value, state):
  143. for t in self.type:
  144. if type(value) is t:
  145. break
  146. else:
  147. if len(self.type) == 1:
  148. msg = self.message('type', state, object=value,
  149. type=self.type[0])
  150. else:
  151. msg = self.message('inType', state, object=value,
  152. typeList=', '.join(map(str, self.type)))
  153. raise Invalid(msg, value, state)
  154. return value
  155. def is_empty(self, value):
  156. return False
  157. class Wrapper(FancyValidator):
  158. """
  159. Used to convert functions to validator/converters.
  160. You can give a simple function for `_convert_to_python`,
  161. `_convert_from_python`, `_validate_python` or `_validate_other`.
  162. If that function raises an exception, the value is considered invalid.
  163. Whatever value the function returns is considered the converted value.
  164. Unlike validators, the `state` argument is not used. Functions
  165. like `int` can be used here, that take a single argument.
  166. Note that as Wrapper will generate a FancyValidator, empty
  167. values (those who pass ``FancyValidator.is_empty)`` will return ``None``.
  168. To override this behavior you can use ``Wrapper(empty_value=callable)``.
  169. For example passing ``Wrapper(empty_value=lambda val: val)`` will return
  170. the value itself when is considered empty.
  171. Examples::
  172. >>> def downcase(v):
  173. ... return v.lower()
  174. >>> wrap = Wrapper(convert_to_python=downcase)
  175. >>> wrap.to_python('This')
  176. 'this'
  177. >>> wrap.from_python('This')
  178. 'This'
  179. >>> wrap.to_python('') is None
  180. True
  181. >>> wrap2 = Wrapper(
  182. ... convert_from_python=downcase, empty_value=lambda value: value)
  183. >>> wrap2.from_python('This')
  184. 'this'
  185. >>> wrap2.to_python('')
  186. ''
  187. >>> wrap2.from_python(1)
  188. Traceback (most recent call last):
  189. ...
  190. Invalid: 'int' object has no attribute 'lower'
  191. >>> wrap3 = Wrapper(validate_python=int)
  192. >>> wrap3.to_python('1')
  193. '1'
  194. >>> wrap3.to_python('a') # doctest: +ELLIPSIS
  195. Traceback (most recent call last):
  196. ...
  197. Invalid: invalid literal for int()...
  198. """
  199. func_convert_to_python = None
  200. func_convert_from_python = None
  201. func_validate_python = None
  202. func_validate_other = None
  203. _deprecated_methods = (
  204. ('func_to_python', 'func_convert_to_python'),
  205. ('func_from_python', 'func_convert_from_python'))
  206. def __init__(self, *args, **kw):
  207. # allow old method names as parameters
  208. if 'to_python' in kw and 'convert_to_python' not in kw:
  209. kw['convert_to_python'] = kw.pop('to_python')
  210. if 'from_python' in kw and 'convert_from_python' not in kw:
  211. kw['convert_from_python'] = kw.pop('from_python')
  212. for n in ('convert_to_python', 'convert_from_python',
  213. 'validate_python', 'validate_other'):
  214. if n in kw:
  215. kw['func_%s' % n] = kw.pop(n)
  216. FancyValidator.__init__(self, *args, **kw)
  217. self._convert_to_python = self.wrap(self.func_convert_to_python)
  218. self._convert_from_python = self.wrap(self.func_convert_from_python)
  219. self._validate_python = self.wrap(self.func_validate_python)
  220. self._validate_other = self.wrap(self.func_validate_other)
  221. def wrap(self, func):
  222. if not func:
  223. return None
  224. def result(value, state, func=func):
  225. try:
  226. return func(value)
  227. except Exception, e:
  228. raise Invalid(str(e), value, state)
  229. return result
  230. class Constant(FancyValidator):
  231. """
  232. This converter converts everything to the same thing.
  233. I.e., you pass in the constant value when initializing, then all
  234. values get converted to that constant value.
  235. This is only really useful for funny situations, like::
  236. # Any evaluates sub validators in reverse order for to_python
  237. fromEmailValidator = Any(
  238. Constant('unknown@localhost'),
  239. Email())
  240. In this case, the if the email is not valid
  241. ``'unknown@localhost'`` will be used instead. Of course, you
  242. could use ``if_invalid`` instead.
  243. Examples::
  244. >>> Constant('X').to_python('y')
  245. 'X'
  246. """
  247. __unpackargs__ = ('value',)
  248. def _convert_to_python(self, value, state):
  249. return self.value
  250. _convert_from_python = _convert_to_python
  251. ############################################################
  252. ## Normal validators
  253. ############################################################
  254. class MaxLength(FancyValidator):
  255. """
  256. Invalid if the value is longer than `maxLength`. Uses len(),
  257. so it can work for strings, lists, or anything with length.
  258. Examples::
  259. >>> max5 = MaxLength(5)
  260. >>> max5.to_python('12345')
  261. '12345'
  262. >>> max5.from_python('12345')
  263. '12345'
  264. >>> max5.to_python('123456')
  265. Traceback (most recent call last):
  266. ...
  267. Invalid: Enter a value less than 5 characters long
  268. >>> max5(accept_python=False).from_python('123456')
  269. Traceback (most recent call last):
  270. ...
  271. Invalid: Enter a value less than 5 characters long
  272. >>> max5.to_python([1, 2, 3])
  273. [1, 2, 3]
  274. >>> max5.to_python([1, 2, 3, 4, 5, 6])
  275. Traceback (most recent call last):
  276. ...
  277. Invalid: Enter a value less than 5 characters long
  278. >>> max5.to_python(5)
  279. Traceback (most recent call last):
  280. ...
  281. Invalid: Invalid value (value with length expected)
  282. """
  283. __unpackargs__ = ('maxLength',)
  284. messages = dict(
  285. tooLong=_('Enter a value less than %(maxLength)i characters long'),
  286. invalid=_('Invalid value (value with length expected)'))
  287. def _validate_python(self, value, state):
  288. try:
  289. if value and len(value) > self.maxLength:
  290. raise Invalid(
  291. self.message('tooLong', state,
  292. maxLength=self.maxLength), value, state)
  293. else:
  294. return None
  295. except TypeError:
  296. raise Invalid(
  297. self.message('invalid', state), value, state)
  298. class MinLength(FancyValidator):
  299. """
  300. Invalid if the value is shorter than `minlength`. Uses len(), so
  301. it can work for strings, lists, or anything with length. Note
  302. that you **must** use ``not_empty=True`` if you don't want to
  303. accept empty values -- empty values are not tested for length.
  304. Examples::
  305. >>> min5 = MinLength(5)
  306. >>> min5.to_python('12345')
  307. '12345'
  308. >>> min5.from_python('12345')
  309. '12345'
  310. >>> min5.to_python('1234')
  311. Traceback (most recent call last):
  312. ...
  313. Invalid: Enter a value at least 5 characters long
  314. >>> min5(accept_python=False).from_python('1234')
  315. Traceback (most recent call last):
  316. ...
  317. Invalid: Enter a value at least 5 characters long
  318. >>> min5.to_python([1, 2, 3, 4, 5])
  319. [1, 2, 3, 4, 5]
  320. >>> min5.to_python([1, 2, 3])
  321. Traceback (most recent call last):
  322. ...
  323. Invalid: Enter a value at least 5 characters long
  324. >>> min5.to_python(5)
  325. Traceback (most recent call last):
  326. ...
  327. Invalid: Invalid value (value with length expected)
  328. """
  329. __unpackargs__ = ('minLength',)
  330. messages = dict(
  331. tooShort=_('Enter a value at least %(minLength)i characters long'),
  332. invalid=_('Invalid value (value with length expected)'))
  333. def _validate_python(self, value, state):
  334. try:
  335. if len(value) < self.minLength:
  336. raise Invalid(
  337. self.message('tooShort', state,
  338. minLength=self.minLength), value, state)
  339. except TypeError:
  340. raise Invalid(
  341. self.message('invalid', state), value, state)
  342. class NotEmpty(FancyValidator):
  343. """
  344. Invalid if value is empty (empty string, empty list, etc).
  345. Generally for objects that Python considers false, except zero
  346. which is not considered invalid.
  347. Examples::
  348. >>> ne = NotEmpty(messages=dict(empty='enter something'))
  349. >>> ne.to_python('')
  350. Traceback (most recent call last):
  351. ...
  352. Invalid: enter something
  353. >>> ne.to_python(0)
  354. 0
  355. """
  356. not_empty = True
  357. messages = dict(
  358. empty=_('Please enter a value'))
  359. def _validate_python(self, value, state):
  360. if value == 0:
  361. # This isn't "empty" for this definition.
  362. return value
  363. if not value:
  364. raise Invalid(self.message('empty', state), value, state)
  365. class Empty(FancyValidator):
  366. """
  367. Invalid unless the value is empty. Use cleverly, if at all.
  368. Examples::
  369. >>> Empty.to_python(0)
  370. Traceback (most recent call last):
  371. ...
  372. Invalid: You cannot enter a value here
  373. """
  374. messages = dict(
  375. notEmpty=_('You cannot enter a value here'))
  376. def _validate_python(self, value, state):
  377. if value or value == 0:
  378. raise Invalid(self.message('notEmpty', state), value, state)
  379. class Regex(FancyValidator):
  380. """
  381. Invalid if the value doesn't match the regular expression `regex`.
  382. The regular expression can be a compiled re object, or a string
  383. which will be compiled for you.
  384. Use strip=True if you want to strip the value before validation,
  385. and as a form of conversion (often useful).
  386. Examples::
  387. >>> cap = Regex(r'^[A-Z]+$')
  388. >>> cap.to_python('ABC')
  389. 'ABC'
  390. Note that ``.from_python()`` calls (in general) do not validate
  391. the input::
  392. >>> cap.from_python('abc')
  393. 'abc'
  394. >>> cap(accept_python=False).from_python('abc')
  395. Traceback (most recent call last):
  396. ...
  397. Invalid: The input is not valid
  398. >>> cap.to_python(1)
  399. Traceback (most recent call last):
  400. ...
  401. Invalid: The input must be a string (not a <type 'int'>: 1)
  402. >>> Regex(r'^[A-Z]+$', strip=True).to_python(' ABC ')
  403. 'ABC'
  404. >>> Regex(r'this', regexOps=('I',)).to_python('THIS')
  405. 'THIS'
  406. """
  407. regexOps = ()
  408. strip = False
  409. regex = None
  410. __unpackargs__ = ('regex',)
  411. messages = dict(
  412. invalid=_('The input is not valid'))
  413. def __init__(self, *args, **kw):
  414. FancyValidator.__init__(self, *args, **kw)
  415. if isinstance(self.regex, basestring):
  416. ops = 0
  417. assert not isinstance(self.regexOps, basestring), (
  418. "regexOps should be a list of options from the re module "
  419. "(names, or actual values)")
  420. for op in self.regexOps:
  421. if isinstance(op, basestring):
  422. ops |= getattr(re, op)
  423. else:
  424. ops |= op
  425. self.regex = re.compile(self.regex, ops)
  426. def _validate_python(self, value, state):
  427. self.assert_string(value, state)
  428. if self.strip and isinstance(value, basestring):
  429. value = value.strip()
  430. if not self.regex.search(value):
  431. raise Invalid(self.message('invalid', state), value, state)
  432. def _convert_to_python(self, value, state):
  433. if self.strip and isinstance(value, basestring):
  434. return value.strip()
  435. return value
  436. class PlainText(Regex):
  437. """
  438. Test that the field contains only letters, numbers, underscore,
  439. and the hyphen. Subclasses Regex.
  440. Examples::
  441. >>> PlainText.to_python('_this9_')
  442. '_this9_'
  443. >>> PlainText.from_python(' this ')
  444. ' this '
  445. >>> PlainText(accept_python=False).from_python(' this ')
  446. Traceback (most recent call last):
  447. ...
  448. Invalid: Enter only letters, numbers, or _ (underscore)
  449. >>> PlainText(strip=True).to_python(' this ')
  450. 'this'
  451. >>> PlainText(strip=True).from_python(' this ')
  452. 'this'
  453. """
  454. regex = r"^[a-zA-Z_\-0-9]*$"
  455. messages = dict(
  456. invalid=_('Enter only letters, numbers, or _ (underscore)'))
  457. class OneOf(FancyValidator):
  458. """
  459. Tests that the value is one of the members of a given list.
  460. If ``testValueList=True``, then if the input value is a list or
  461. tuple, all the members of the sequence will be checked (i.e., the
  462. input must be a subset of the allowed values).
  463. Use ``hideList=True`` to keep the list of valid values out of the
  464. error message in exceptions.
  465. Examples::
  466. >>> oneof = OneOf([1, 2, 3])
  467. >>> oneof.to_python(1)
  468. 1
  469. >>> oneof.to_python(4)
  470. Traceback (most recent call last):
  471. ...
  472. Invalid: Value must be one of: 1; 2; 3 (not 4)
  473. >>> oneof(testValueList=True).to_python([2, 3, [1, 2, 3]])
  474. [2, 3, [1, 2, 3]]
  475. >>> oneof.to_python([2, 3, [1, 2, 3]])
  476. Traceback (most recent call last):
  477. ...
  478. Invalid: Value must be one of: 1; 2; 3 (not [2, 3, [1, 2, 3]])
  479. """
  480. list = None
  481. testValueList = False
  482. hideList = False
  483. __unpackargs__ = ('list',)
  484. messages = dict(
  485. invalid=_('Invalid value'),
  486. notIn=_('Value must be one of: %(items)s (not %(value)r)'))
  487. def _validate_python(self, value, state):
  488. if self.testValueList and isinstance(value, (list, tuple)):
  489. for v in value:
  490. self._validate_python(v, state)
  491. else:
  492. if not value in self.list:
  493. if self.hideList:
  494. raise Invalid(self.message('invalid', state), value, state)
  495. else:
  496. try:
  497. items = '; '.join(map(str, self.list))
  498. except UnicodeError:
  499. items = '; '.join(map(unicode, self.list))
  500. raise Invalid(
  501. self.message('notIn', state,
  502. items=items, value=value), value, state)
  503. @property
  504. def accept_iterator(self):
  505. return self.testValueList
  506. class DictConverter(FancyValidator):
  507. """
  508. Converts values based on a dictionary which has values as keys for
  509. the resultant values.
  510. If ``allowNull`` is passed, it will not balk if a false value
  511. (e.g., '' or None) is given (it will return None in these cases).
  512. to_python takes keys and gives values, from_python takes values and
  513. gives keys.
  514. If you give hideDict=True, then the contents of the dictionary
  515. will not show up in error messages.
  516. Examples::
  517. >>> dc = DictConverter({1: 'one', 2: 'two'})
  518. >>> dc.to_python(1)
  519. 'one'
  520. >>> dc.from_python('one')
  521. 1
  522. >>> dc.to_python(3)
  523. Traceback (most recent call last):
  524. ....
  525. Invalid: Enter a value from: 1; 2
  526. >>> dc2 = dc(hideDict=True)
  527. >>> dc2.hideDict
  528. True
  529. >>> dc2.dict
  530. {1: 'one', 2: 'two'}
  531. >>> dc2.to_python(3)
  532. Traceback (most recent call last):
  533. ....
  534. Invalid: Choose something
  535. >>> dc.from_python('three')
  536. Traceback (most recent call last):
  537. ....
  538. Invalid: Nothing in my dictionary goes by the value 'three'. Choose one of: 'one'; 'two'
  539. """
  540. messages = dict(
  541. keyNotFound=_('Choose something'),
  542. chooseKey=_('Enter a value from: %(items)s'),
  543. valueNotFound=_('That value is not known'),
  544. chooseValue=_('Nothing in my dictionary goes by the value %(value)s.'
  545. ' Choose one of: %(items)s'))
  546. dict = None
  547. hideDict = False
  548. __unpackargs__ = ('dict',)
  549. def _convert_to_python(self, value, state):
  550. try:
  551. return self.dict[value]
  552. except KeyError:
  553. if self.hideDict:
  554. raise Invalid(self.message('keyNotFound', state), value, state)
  555. else:
  556. items = sorted(self.dict)
  557. items = '; '.join(map(repr, items))
  558. raise Invalid(self.message('chooseKey',
  559. state, items=items), value, state)
  560. def _convert_from_python(self, value, state):
  561. for k, v in self.dict.iteritems():
  562. if value == v:
  563. return k
  564. if self.hideDict:
  565. raise Invalid(self.message('valueNotFound', state), value, state)
  566. else:
  567. items = '; '.join(map(repr, self.dict.itervalues()))
  568. raise Invalid(
  569. self.message('chooseValue', state,
  570. value=repr(value), items=items), value, state)
  571. class IndexListConverter(FancyValidator):
  572. """
  573. Converts a index (which may be a string like '2') to the value in
  574. the given list.
  575. Examples::
  576. >>> index = IndexListConverter(['zero', 'one', 'two'])
  577. >>> index.to_python(0)
  578. 'zero'
  579. >>> index.from_python('zero')
  580. 0
  581. >>> index.to_python('1')
  582. 'one'
  583. >>> index.to_python(5)
  584. Traceback (most recent call last):
  585. Invalid: Index out of range
  586. >>> index(not_empty=True).to_python(None)
  587. Traceback (most recent call last):
  588. Invalid: Please enter a value
  589. >>> index.from_python('five')
  590. Traceback (most recent call last):
  591. Invalid: Item 'five' was not found in the list
  592. """
  593. list = None
  594. __unpackargs__ = ('list',)
  595. messages = dict(
  596. integer=_('Must be an integer index'),
  597. outOfRange=_('Index out of range'),
  598. notFound=_('Item %(value)s was not found in the list'))
  599. def _convert_to_python(self, value, state):
  600. try:
  601. value = int(value)
  602. except (ValueError, TypeError):
  603. raise Invalid(self.message('integer', state), value, state)
  604. try:
  605. return self.list[value]
  606. except IndexError:
  607. raise Invalid(self.message('outOfRange', state), value, state)
  608. def _convert_from_python(self, value, state):
  609. for i, v in enumerate(self.list):
  610. if v == value:
  611. return i
  612. raise Invalid(
  613. self.message('notFound', state, value=repr(value)), value, state)
  614. class DateValidator(FancyValidator):
  615. """
  616. Validates that a date is within the given range. Be sure to call
  617. DateConverter first if you aren't expecting mxDateTime input.
  618. ``earliest_date`` and ``latest_date`` may be functions; if so,
  619. they will be called each time before validating.
  620. ``after_now`` means a time after the current timestamp; note that
  621. just a few milliseconds before now is invalid! ``today_or_after``
  622. is more permissive, and ignores hours and minutes.
  623. Examples::
  624. >>> from datetime import datetime, timedelta
  625. >>> d = DateValidator(earliest_date=datetime(2003, 1, 1))
  626. >>> d.to_python(datetime(2004, 1, 1))
  627. datetime.datetime(2004, 1, 1, 0, 0)
  628. >>> d.to_python(datetime(2002, 1, 1))
  629. Traceback (most recent call last):
  630. ...
  631. Invalid: Date must be after Wednesday, 01 January 2003
  632. >>> d.to_python(datetime(2003, 1, 1))
  633. datetime.datetime(2003, 1, 1, 0, 0)
  634. >>> d = DateValidator(after_now=True)
  635. >>> now = datetime.now()
  636. >>> d.to_python(now+timedelta(seconds=5)) == now+timedelta(seconds=5)
  637. True
  638. >>> d.to_python(now-timedelta(days=1))
  639. Traceback (most recent call last):
  640. ...
  641. Invalid: The date must be sometime in the future
  642. >>> d.to_python(now+timedelta(days=1)) > now
  643. True
  644. >>> d = DateValidator(today_or_after=True)
  645. >>> d.to_python(now) == now
  646. True
  647. """
  648. earliest_date = None
  649. latest_date = None
  650. after_now = False
  651. # Like after_now, but just after this morning:
  652. today_or_after = False
  653. # Use None or 'datetime' for the datetime module in the standard lib,
  654. # or 'mxDateTime' to force the mxDateTime module
  655. datetime_module = None
  656. messages = dict(
  657. after=_('Date must be after %(date)s'),
  658. before=_('Date must be before %(date)s'),
  659. # Double %'s, because this will be substituted twice:
  660. date_format=_('%%A, %%d %%B %%Y'),
  661. future=_('The date must be sometime in the future'))
  662. def _validate_python(self, value, state):
  663. date_format = self.message('date_format', state)
  664. if (str is not unicode # Python 2
  665. and isinstance(date_format, unicode)):
  666. # strftime uses the locale encoding, not Unicode
  667. encoding = locale.getlocale(locale.LC_TIME)[1] or 'utf-8'
  668. date_format = date_format.encode(encoding)
  669. else:
  670. encoding = None
  671. if self.earliest_date:
  672. if callable(self.earliest_date):
  673. earliest_date = self.earliest_date()
  674. else:
  675. earliest_date = self.earliest_date
  676. if value < earliest_date:
  677. date_formatted = earliest_date.strftime(date_format)
  678. if encoding:
  679. date_formatted = date_formatted.decode(encoding)
  680. raise Invalid(
  681. self.message('after', state, date=date_formatted),
  682. value, state)
  683. if self.latest_date:
  684. if callable(self.latest_date):
  685. latest_date = self.latest_date()
  686. else:
  687. latest_date = self.latest_date
  688. if value > latest_date:
  689. date_formatted = latest_date.strftime(date_format)
  690. if encoding:
  691. date_formatted = date_formatted.decode(encoding)
  692. raise Invalid(
  693. self.message('before', state, date=date_formatted),
  694. value, state)
  695. if self.after_now:
  696. dt_mod = import_datetime(self.datetime_module)
  697. now = datetime_now(dt_mod)
  698. if value < now:
  699. date_formatted = now.strftime(date_format)
  700. if encoding:
  701. date_formatted = date_formatted.decode(encoding)
  702. raise Invalid(
  703. self.message('future', state, date=date_formatted),
  704. value, state)
  705. if self.today_or_after:
  706. dt_mod = import_datetime(self.datetime_module)
  707. now = datetime_now(dt_mod)
  708. today = datetime_makedate(dt_mod,
  709. now.year, now.month, now.day)
  710. value_as_date = datetime_makedate(
  711. dt_mod, value.year, value.month, value.day)
  712. if value_as_date < today:
  713. date_formatted = now.strftime(date_format)
  714. if encoding:
  715. date_formatted = date_formatted.decode(encoding)
  716. raise Invalid(
  717. self.message('future', state, date=date_formatted),
  718. value, state)
  719. class Bool(FancyValidator):
  720. """
  721. Always Valid, returns True or False based on the value and the
  722. existance of the value.
  723. If you want to convert strings like ``'true'`` to booleans, then
  724. use ``StringBool``.
  725. Examples::
  726. >>> Bool.to_python(0)
  727. False
  728. >>> Bool.to_python(1)
  729. True
  730. >>> Bool.to_python('')
  731. False
  732. >>> Bool.to_python(None)
  733. False
  734. """
  735. if_missing = False
  736. def _convert_to_python(self, value, state):
  737. return bool(value)
  738. _convert_from_python = _convert_to_python
  739. def empty_value(self, value):
  740. return False
  741. class RangeValidator(FancyValidator):
  742. """This is an abstract base class for Int and Number.
  743. It verifies that a value is within range. It accepts min and max
  744. values in the constructor.
  745. (Since this is an abstract base class, the tests are in Int and Number.)
  746. """
  747. messages = dict(
  748. tooLow=_('Please enter a number that is %(min)s or greater'),
  749. tooHigh=_('Please enter a number that is %(max)s or smaller'))
  750. min = None
  751. max = None
  752. def _validate_python(self, value, state):
  753. if self.min is not None:
  754. if value < self.min:
  755. msg = self.message('tooLow', state, min=self.min)
  756. raise Invalid(msg, value, state)
  757. if self.max is not None:
  758. if value > self.max:
  759. msg = self.message('tooHigh', state, max=self.max)
  760. raise Invalid(msg, value, state)
  761. class Int(RangeValidator):
  762. """Convert a value to an integer.
  763. Example::
  764. >>> Int.to_python('10')
  765. 10
  766. >>> Int.to_python('ten')
  767. Traceback (most recent call last):
  768. ...
  769. Invalid: Please enter an integer value
  770. >>> Int(min=5).to_python('6')
  771. 6
  772. >>> Int(max=10).to_python('11')
  773. Traceback (most recent call last):
  774. ...
  775. Invalid: Please enter a number that is 10 or smaller
  776. """
  777. messages = dict(
  778. integer=_('Please enter an integer value'))
  779. def _convert_to_python(self, value, state):
  780. try:
  781. return int(value)
  782. except (ValueError, TypeError):
  783. raise Invalid(self.message('integer', state), value, state)
  784. _convert_from_python = _convert_to_python
  785. class Number(RangeValidator):
  786. """Convert a value to a float or integer.
  787. Tries to convert it to an integer if no information is lost.
  788. Example::
  789. >>> Number.to_python('10')
  790. 10
  791. >>> Number.to_python('10.5')
  792. 10.5
  793. >>> Number.to_python('ten')
  794. Traceback (most recent call last):
  795. ...
  796. Invalid: Please enter a number
  797. >>> Number(min=5).to_python('6.5')
  798. 6.5
  799. >>> Number(max=10.5).to_python('11.5')
  800. Traceback (most recent call last):
  801. ...
  802. Invalid: Please enter a number that is 10.5 or smaller
  803. """
  804. messages = dict(
  805. number=_('Please enter a number'))
  806. def _convert_to_python(self, value, state):
  807. try:
  808. value = float(value)
  809. try:
  810. int_value = int(value)
  811. except OverflowError:
  812. int_value = None
  813. if value == int_value:
  814. return int_value
  815. return value
  816. except ValueError:
  817. raise Invalid(self.message('number', state), value, state)
  818. class String(FancyValidator):
  819. """
  820. Converts things to string, but treats empty things as the empty string.
  821. Also takes a `max` and `min` argument, and the string length must fall
  822. in that range.
  823. Also you may give an `encoding` argument, which will encode any unicode
  824. that is found. Lists and tuples are joined with `list_joiner`
  825. (default ``', '``) in ``from_python``.
  826. ::
  827. >>> String(min=2).to_python('a')
  828. Traceback (most recent call last):
  829. ...
  830. Invalid: Enter a value 2 characters long or more
  831. >>> String(max=10).to_python('xxxxxxxxxxx')
  832. Traceback (most recent call last):
  833. ...
  834. Invalid: Enter a value not more than 10 characters long
  835. >>> String().from_python(None)
  836. ''
  837. >>> String().from_python([])
  838. ''
  839. >>> String().to_python(None)
  840. ''
  841. >>> String(min=3).to_python(None)
  842. Traceback (most recent call last):
  843. ...
  844. Invalid: Please enter a value
  845. >>> String(min=1).to_python('')
  846. Traceback (most recent call last):
  847. ...
  848. Invalid: Please enter a value
  849. """
  850. min = None
  851. max = None
  852. not_empty = None
  853. encoding = None
  854. list_joiner = ', '
  855. messages = dict(
  856. tooLong=_('Enter a value not more than %(max)i characters long'),
  857. tooShort=_('Enter a value %(min)i characters long or more'))
  858. def __initargs__(self, new_attrs):
  859. if self.not_empty is None and self.min:
  860. self.not_empty = True
  861. def _convert_to_python(self, value, state):
  862. if value is None:
  863. value = ''
  864. elif not isinstance(value, basestring):
  865. try:
  866. value = str(value)
  867. except UnicodeEncodeError:
  868. value = unicode(value)
  869. if self.encoding is not None and isinstance(value, unicode):
  870. value = value.encode(self.encoding)
  871. return value
  872. def _convert_from_python(self, value, state):
  873. if value is None:
  874. value = ''
  875. elif not isinstance(value, basestring):
  876. if isinstance(value, (list, tuple)):
  877. value = self.list_joiner.join(
  878. self._convert_from_python(v, state) for v in value)
  879. try:
  880. value = str(value)
  881. except UnicodeEncodeError:
  882. value = unicode(value)
  883. if self.encoding is not None and isinstance(value, unicode):
  884. value = value.encode(self.encoding)
  885. if self.strip:
  886. value = value.strip()
  887. return value
  888. def _validate_other(self, value, state):
  889. if self.max is None and self.min is None:
  890. return
  891. if value is None:
  892. value = ''
  893. elif not isinstance(value, basestring):
  894. try:
  895. value = str(value)
  896. except UnicodeEncodeError:
  897. value = unicode(value)
  898. if self.max is not None and len(value) > self.max:
  899. raise Invalid(
  900. self.message('tooLong', state, max=self.max), value, state)
  901. if self.min is not None and len(value) < self.min:
  902. raise Invalid(
  903. self.message('tooShort', state, min=self.min), value, state)
  904. def empty_value(self, value):
  905. return ''
  906. class UnicodeString(String):
  907. """
  908. Converts things to unicode string, this is a specialization of
  909. the String class.
  910. In addition to the String arguments, an encoding argument is also
  911. accepted. By default the encoding will be utf-8. You can overwrite
  912. this using the encoding parameter. You can also set inputEncoding
  913. and outputEncoding differently. An inputEncoding of None means
  914. "do not decode", an outputEncoding of None means "do not encode".
  915. All converted strings are returned as Unicode strings.
  916. ::
  917. >>> UnicodeString().to_python(None)
  918. u''
  919. >>> UnicodeString().to_python([])
  920. u''
  921. >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni')
  922. u'Ni Ni Ni'
  923. """
  924. encoding = 'utf-8'
  925. inputEncoding = NoDefault
  926. outputEncoding = NoDefault
  927. messages = dict(
  928. badEncoding=_('Invalid data or incorrect encoding'))
  929. def __init__(self, **kw):
  930. String.__init__(self, **kw)
  931. if self.inputEncoding is NoDefault:
  932. self.inputEncoding = self.encoding
  933. if self.outputEncoding is NoDefault:
  934. self.outputEncoding = self.encoding
  935. def _convert_to_python(self, value, state):
  936. if not value:
  937. return u''
  938. if isinstance(value, unicode):
  939. return value
  940. if not isinstance(value, unicode):
  941. if hasattr(value, '__unicode__'):
  942. value = unicode(value)
  943. return value
  944. if not (unicode is str # Python 3
  945. and isinstance(value, bytes) and self.inputEncoding):
  946. value = str(value)
  947. if self.inputEncoding and not isinstance(value, unicode):
  948. try:
  949. value = unicode(value, self.inputEncoding)
  950. except UnicodeDecodeError:
  951. raise Invalid(self.message('badEncoding', state), value, state)
  952. except TypeError:
  953. raise Invalid(
  954. self.message('badType', state,
  955. type=type(value), value=value), value, state)
  956. return value
  957. def _convert_from_python(self, value, state):
  958. if not isinstance(value, unicode):
  959. if hasattr(value, '__unicode__'):
  960. value = unicode(value)
  961. else:
  962. value = str(value)
  963. if self.outputEncoding and isinstance(value, unicode):
  964. value = value.encode(self.outputEncoding)
  965. return value
  966. def empty_value(self, value):
  967. return u''
  968. class Set(FancyValidator):
  969. """
  970. This is for when you think you may return multiple values for a
  971. certain field.
  972. This way the result will always be a list, even if there's only
  973. one result. It's equivalent to ForEach(convert_to_list=True).
  974. If you give ``use_set=True``, then it will return an actual
  975. ``set`` object.
  976. ::
  977. >>> Set.to_python(None)
  978. []
  979. >>> Set.to_python('this')
  980. ['this']
  981. >>> Set.to_python(('this', 'that'))
  982. ['this', 'that']
  983. >>> s = Set(use_set=True)
  984. >>> s.to_python(None)
  985. set([])
  986. >>> s.to_python('this')
  987. set(['this'])
  988. >>> s.to_python(('this',))
  989. set(['this'])
  990. """
  991. use_set = False
  992. if_missing = ()
  993. accept_iterator = True
  994. def _convert_to_python(self, value, state):
  995. if self.use_set:
  996. if isinstance(value, set):
  997. return value
  998. elif isinstance(value, (list, tuple)):
  999. return set(value)
  1000. elif value is None:
  1001. return set()
  1002. else:
  1003. return set([value])
  1004. else:
  1005. if isinstance(value, list):
  1006. return value
  1007. elif isinstance(value, set):
  1008. return list(value)
  1009. elif isinstance(value, tuple):
  1010. return list(value)
  1011. elif value is None:
  1012. return []
  1013. else:
  1014. return [value]
  1015. def empty_value(self, value):
  1016. if self.use_set:
  1017. return set()
  1018. else:
  1019. return []
  1020. class Email(FancyValidator):
  1021. r"""
  1022. Validate an email address.
  1023. If you pass ``resolve_domain=True``, then it will try to resolve
  1024. the domain name to make sure it's valid. This takes longer, of
  1025. course. You must have the `dnspython <http://www.dnspython.org/>`__ modules
  1026. installed to look up DNS (MX and A) records.
  1027. ::
  1028. >>> e = Email()
  1029. >>> e.to_python(' test@foo.com ')
  1030. 'test@foo.com'
  1031. >>> e.to_python('test')
  1032. Traceback (most recent call last):
  1033. ...
  1034. Invalid: An email address must contain a single @
  1035. >>> e.to_python('test@foobar')
  1036. Traceback (most recent call last):
  1037. ...
  1038. Invalid: The domain portion of the email address is invalid (the portion after the @: foobar)
  1039. >>> e.to_python('test@foobar.com.5')
  1040. Traceback (most recent call last):
  1041. ...
  1042. Invalid: The domain portion of the email address is invalid (the portion after the @: foobar.com.5)
  1043. >>> e.to_python('test@foo..bar.com')
  1044. Traceback (most recent call last):
  1045. ...
  1046. Invalid: The domain portion of the email address is invalid (the portion after the @: foo..bar.com)
  1047. >>> e.to_python('test@.foo.bar.com')
  1048. Traceback (most recent call last):
  1049. ...
  1050. Invalid: The domain portion of the email address is invalid (the portion after the @: .foo.bar.com)
  1051. >>> e.to_python('nobody@xn--m7r7ml7t24h.com')
  1052. 'nobody@xn--m7r7ml7t24h.com'
  1053. >>> e.to_python('o*reilly@test.com')
  1054. 'o*reilly@test.com'
  1055. >>> e = Email(resolve_domain=True)
  1056. >>> e.resolve_domain
  1057. True
  1058. >>> e.to_python('doesnotexist@colorstudy.com')
  1059. 'doesnotexist@colorstudy.com'
  1060. >>> e.to_python('test@nyu.edu')
  1061. 'test@nyu.edu'
  1062. >>> # NOTE: If you do not have dnspython installed this example won't work:
  1063. >>> e.to_python('test@thisdomaindoesnotexistithinkforsure.com')
  1064. Traceback (most recent call last):
  1065. ...
  1066. Invalid: The domain of the email address does not exist (the portion after the @: thisdomaindoesnotexistithinkforsure.com)
  1067. >>> e.to_python(u'test@google.com')
  1068. u'test@google.com'
  1069. >>> e = Email(not_empty=False)
  1070. >>> e.to_python('')
  1071. """
  1072. resolve_domain = False
  1073. resolve_timeout = 10 # timeout in seconds when resolving domains
  1074. usernameRE = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
  1075. domainRE = re.compile(r'''
  1076. ^(?:[a-z0-9][a-z0-9\-]{,62}\.)+ # subdomain
  1077. (?:[a-z]{2,63}|xn--[a-z0-9\-]{2,59})$ # top level domain
  1078. ''', re.I | re.VERBOSE)
  1079. messages = dict(
  1080. empty=_('Please enter an email address'),
  1081. noAt=_('An email address must contain a single @'),
  1082. badUsername=_('The username portion of the email address is invalid'
  1083. ' (the portion before the @: %(username)s)'),
  1084. socketError=_('An error occured when trying to connect to the server:'
  1085. ' %(error)s'),
  1086. badDomain=_('The domain portion of the email address is invalid'
  1087. ' (the portion after the @: %(domain)s)'),
  1088. domainDoesNotExist=_('The domain of the email address does not exist'
  1089. ' (the portion after the @: %(domain)s)'))
  1090. def __init__(self, *args, **kw):
  1091. FancyValidator.__init__(self, *args, **kw)
  1092. if self.resolve_domain:
  1093. if not have_dns:
  1094. warnings.warn(
  1095. "dnspython <http://www.dnspython.org/> is not installed on"
  1096. " your system (or the dns.resolver package cannot be found)."
  1097. " I cannot resolve domain names in addresses")
  1098. raise ImportError("no module named dns.resolver")
  1099. def _validate_python(self, value, state):
  1100. if not value:
  1101. raise Invalid(self.message('empty', state), value, state)
  1102. value = value.strip()
  1103. splitted = value.split('@', 1)
  1104. try:
  1105. username, domain = splitted
  1106. except ValueError:
  1107. raise Invalid(self.message('noAt', state), value, state)
  1108. if not self.usernameRE.search(username):
  1109. raise Invalid(
  1110. self.message('badUsername', state, username=username),
  1111. value, state)
  1112. try:
  1113. idna_domain = [idna.ToASCII(p) for p in domain.split('.')]
  1114. if unicode is str: # Python 3
  1115. idna_domain = [p.decode('ascii') for p in idna_domain]
  1116. idna_domain = '.'.join(idna_domain)
  1117. except UnicodeError:
  1118. # UnicodeError: label empty or too long
  1119. # This exception might happen if we have an invalid domain name part
  1120. # (for example test@.foo.bar.com)
  1121. raise Invalid(
  1122. self.message('badDomain', state, domain=domain),
  1123. value, state)
  1124. if not self.domainRE.search(idna_domain):
  1125. raise Invalid(
  1126. self.message('badDomain', state, domain=domain),
  1127. value, state)
  1128. if self.resolve_domain:
  1129. assert have_dns, "dnspython should be available"
  1130. global socket
  1131. if socket is None:
  1132. import socket
  1133. try:
  1134. try:
  1135. dns.resolver.query(domain, 'MX')
  1136. except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer), e:
  1137. try:
  1138. dns.resolver.query(domain, 'A')
  1139. except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer), e:
  1140. raise Invalid(
  1141. self.message('domainDoesNotExist',
  1142. state, domain=domain), value, state)
  1143. except (socket.error, dns.exception.DNSException), e:
  1144. raise Invalid(
  1145. self.message('socketError', state, error=e), value, state)
  1146. def _convert_to_python(self, value, state):
  1147. return value.strip()
  1148. class URL(FancyValidator):
  1149. """
  1150. Validate a URL, either http://... or https://. If check_exists
  1151. is true, then we'll actually make a request for the page.
  1152. If add_http is true, then if no scheme is present we'll add
  1153. http://
  1154. ::
  1155. >>> u = URL(add_http=True)
  1156. >>> u.to_python('foo.com')
  1157. 'http://foo.com'
  1158. >>> u.to_python('http://hahaha.ha/bar.html')
  1159. 'http://hahaha.ha/bar.html'
  1160. >>> u.to_python('http://xn--m7r7ml7t24h.com')
  1161. 'http://xn--m7r7ml7t24h.com'
  1162. >>> u.to_python('http://xn--c1aay4a.xn--p1ai')
  1163. 'http://xn--c1aay4a.xn--p1ai'
  1164. >>> u.to_python('http://foo.com/test?bar=baz&fleem=morx')
  1165. 'http://foo.com/test?bar=baz&fleem=morx'
  1166. >>> u.to_python('http://foo.com/login?came_from=http%3A%2F%2Ffoo.com%2Ftest')
  1167. 'http://foo.com/login?came_from=http%3A%2F%2Ffoo.com%2Ftest'
  1168. >>> u.to_python('http://foo.com:8000/test.html')
  1169. 'http://foo.com:8000/test.html'
  1170. >>> u.to_python('http://foo.com/something\\nelse')
  1171. Traceback (most recent call last):
  1172. ...
  1173. Invalid: That is not a valid URL
  1174. >>> u.to_python('https://test.com')
  1175. 'https://test.com'
  1176. >>> u.to_python('http://test')
  1177. Traceback (most recent call last):
  1178. ...
  1179. Invalid: You must provide a full domain name (like test.com)
  1180. >>> u.to_python('http://test..com')
  1181. Traceback (most recent call last):
  1182. ...
  1183. Invalid: That is not a valid URL
  1184. >>> u = URL(add_http=False, check_exists=True)
  1185. >>> u.to_python('http://google.com')
  1186. 'http://google.com'
  1187. >>> u.to_python('google.com')
  1188. Traceback (most recent call last):
  1189. ...
  1190. Invalid: You must start your URL with http://, https://, etc
  1191. >>> u.to_python('http://www.formencode.org/does/not/exist/page.html')
  1192. Traceback (most recent call last):
  1193. ...
  1194. Invalid: The server responded that the page could not be found
  1195. >>> u.to_python('http://this.domain.does.not.exist.example.org/test.html')
  1196. ... # doctest: +ELLIPSIS
  1197. Traceback (most recent call last):
  1198. ...
  1199. Invalid: An error occured when trying to connect to the server: ...
  1200. If you want to allow addresses without a TLD (e.g., ``localhost``) you can do::
  1201. >>> URL(require_tld=False).to_python('http://localhost')
  1202. 'http://localhost'
  1203. By default, internationalized domain names (IDNA) in Unicode will be
  1204. accepted and encoded to ASCII using Punycode (as described in RFC 3490).
  1205. You may set allow_idna to False to change this behavior::
  1206. >>> URL(allow_idna=True).to_python(
  1207. ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444')
  1208. 'http://xn--c1aay4a.xn--p1ai'
  1209. >>> URL(allow_idna=True, add_http=True).to_python(
  1210. ... u'\u0433\u0443\u0433\u043b.\u0440\u0444')
  1211. 'http://xn--c1aay4a.xn--p1ai'
  1212. >>> URL(allow_idna=False).to_python(
  1213. ... u'http://\u0433\u0443\u0433\u043b.\u0440\u0444')
  1214. Traceback (most recent call last):
  1215. ...
  1216. Invalid: That is not a valid URL
  1217. """
  1218. add_http = True
  1219. allow_idna = True
  1220. check_exists = False
  1221. require_tld = True
  1222. url_re = re.compile(r'''
  1223. ^(http|https)://
  1224. (?:[%:\w]*@)? # authenticator
  1225. (?: # ip or domain
  1226. (?P<ip>(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|
  1227. (?P<domain>[a-z0-9][a-z0-9\-]{,62}\.)* # subdomain
  1228. (?P<tld>[a-z]{2,63}|xn--[a-z0-9\-]{2,59}) # top level domain
  1229. )
  1230. (?::[0-9]{1,5})? # port
  1231. # files/delims/etc
  1232. (?P<path>/[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]*)?
  1233. $
  1234. ''', re.I | re.VERBOSE)
  1235. scheme_re = re.compile(r'^[a-zA-Z]+:')
  1236. messages = dict(
  1237. noScheme=_('You must start your URL with http://, https://, etc'),
  1238. badURL=_('That is not a valid URL'),
  1239. httpError=_('An error occurred when trying to access the URL:'
  1240. ' %(error)s'),
  1241. socketError=_('An error occured when trying to connect to the server:'
  1242. ' %(error)s'),
  1243. notFound=_('The server responded that the page could not be found'),
  1244. status=_('The server responded with a bad status code (%(status)s)'),
  1245. noTLD=_('You must provide a full domain name (like %(domain)s.com)'))
  1246. def _convert_to_python(self, value, state):
  1247. value = value.strip()
  1248. if self.add_http:
  1249. if not self.scheme_re.search(value):
  1250. value = 'http://' + value
  1251. if self.allow_idna:
  1252. value = self._encode_idna(value)
  1253. match = self.scheme_re.search(value)
  1254. if not match:
  1255. raise Invalid(self.message('noScheme', state), value, state)
  1256. value = match.group(0).lower() + value[len(match.group(0)):]
  1257. match = self.url_re.search(value)
  1258. if not match:
  1259. raise Invalid(self.message('badURL', state), value, state)
  1260. if self.require_tld and not match.group('domain'):
  1261. raise Invalid(
  1262. self.message('noTLD', state, domain=match.group('tld')),
  1263. value, state)
  1264. if self.check_exists and value.startswith(('http://', 'https://')):
  1265. self._check_url_exists(value, state)
  1266. return value
  1267. def _encode_idna(self, url):
  1268. global urlparse
  1269. if urlparse is None:
  1270. import urlparse
  1271. scheme, netloc, path, params, query, fragment = urlparse.urlparse(
  1272. url)
  1273. try:
  1274. netloc = netloc.encode('idna')
  1275. if unicode is str: # Python 3
  1276. netloc = netloc.decode('ascii')
  1277. return str(urlparse.urlunparse((scheme, netloc,
  1278. path, params, query, fragment)))
  1279. except UnicodeError:
  1280. return url
  1281. def _check_url_exists(self, url, state):
  1282. global httplib, urlparse, socket
  1283. if httplib is None:
  1284. import httplib
  1285. if urlparse is None:
  1286. import urlparse
  1287. if socket is None:
  1288. import socket
  1289. scheme, netloc, path, params, query, fragment = urlparse.urlparse(
  1290. url, 'http')
  1291. if scheme == 'https':
  1292. ConnClass = httplib.HTTPSConnection
  1293. else:
  1294. ConnClass = httplib.HTTPConnection
  1295. try:
  1296. conn = ConnClass(netloc)
  1297. if params:
  1298. path += ';' + params
  1299. if query:
  1300. path += '?' + query
  1301. conn.request('HEAD', path)
  1302. res = conn.getresponse()
  1303. except httplib.HTTPException, e:
  1304. raise Invalid(
  1305. self.message('httpError', state, error=e), state, url)
  1306. except socket.error, e:
  1307. raise Invalid(
  1308. self.message('socketError', state, error=e), state, url)
  1309. else:
  1310. if res.status == 404:
  1311. raise Invalid(
  1312. self.message('notFound', state), state, url)
  1313. if not 200 <= res.status < 500:
  1314. raise Invalid(
  1315. self.message('status', state, status=res.status),
  1316. state, url)
  1317. class XRI(FancyValidator):
  1318. r"""
  1319. Validator for XRIs.
  1320. It supports both i-names and i-numbers, of the first version of the XRI
  1321. standard.
  1322. ::
  1323. >>> inames = XRI(xri_type="i-name")
  1324. >>> inames.to_python(" =John.Smith ")
  1325. '=John.Smith'
  1326. >>> inames.to_python("@Free.Software.Foundation")
  1327. '@Free.Software.Foundation'
  1328. >>> inames.to_python("Python.Software.Foundation")
  1329. Traceback (most recent call last):
  1330. ...
  1331. Invalid: The type of i-name is not defined; it may be either individual or organizational
  1332. >>> inames.to_python("http://example.org")
  1333. Traceback (most recent call last):
  1334. ...
  1335. Invalid: The type of i-name is not defined; it may be either individual or organizational
  1336. >>> inames.to_python("=!2C43.1A9F.B6F6.E8E6")
  1337. Traceback (most recent call last):
  1338. ...
  1339. Invalid: "!2C43.1A9F.B6F6.E8E6" is an invalid i-name
  1340. >>> iname_with_schema = XRI(True, xri_type="i-name")
  1341. >>> iname_with_schema.to_python("=Richard.Stallman")
  1342. 'xri://=Richard.Stallman'
  1343. >>> inames.to_python("=John Smith")
  1344. Traceback (most recent call last):
  1345. ...
  1346. Invalid: "John Smith" is an invalid i-name
  1347. >>> inumbers = XRI(xri_type="i-number")
  1348. >>> inumbers.to_python("!!1000!de21.4536.2cb2.8074")
  1349. '!!1000!de21.4536.2cb2.8074'
  1350. >>> inumbers.to_python("@!1000.9554.fabd.129c!2847.df3c")
  1351. '@!1000.9554.fabd.129c!2847.df3c'
  1352. """
  1353. iname_valid_pattern = re.compile(r"""
  1354. ^
  1355. [\w]+ # A global alphanumeric i-name
  1356. (\.[\w]+)* # An i-name with dots
  1357. (\*[\w]+(\.[\w]+)*)* # A community i-name
  1358. $
  1359. """, re.VERBOSE | re.UNICODE)
  1360. iname_invalid_start = re.compile(r"^[\d\.-]", re.UNICODE)
  1361. """@cvar: These characters must not be at the beggining of the i-name"""
  1362. inumber_pattern = re.compile(r"""
  1363. ^
  1364. (
  1365. [=@]! # It's a personal or organization i-number
  1366. |
  1367. !! # It's a network i-number
  1368. )
  1369. [\dA-F]{1,4}(\.[\dA-F]{1,4}){0,3} # A global i-number
  1370. (![\dA-F]{1,4}(\.[\dA-F]{1,4}){0,3})* # Zero or more sub i-numbers
  1371. $
  1372. """, re.VERBOSE | re.IGNORECASE)
  1373. messages = dict(
  1374. noType=_('The type of i-name is not defined;'
  1375. ' it may be either individual or organizational'),
  1376. repeatedChar=_('Dots and dashes may not be repeated consecutively'),
  1377. badIname=_('"%(iname)s" is an invalid i-name'),
  1378. badInameStart=_('i-names may not start with numbers'
  1379. ' nor punctuation marks'),
  1380. badInumber=_('"%(inumber)s" is an invalid i-number'),
  1381. badType=_('The XRI must be a string (not a %(type)s: %(value)r)'),
  1382. badXri=_('"%(xri_type)s" is not a valid type of XRI'))
  1383. def __init__(self, add_xri=False, xri_type="i-name", **kwargs):
  1384. """Create an XRI validator.
  1385. @param add_xri: Should the schema be added if not present?
  1386. Officially it's optional.
  1387. @type add_xri: C{bool}
  1388. @param xri_type: What type of XRI should be validated?
  1389. Possible values: C{i-name} or C{i-number}.
  1390. @type xri_type: C{str}
  1391. """
  1392. self.add_xri = add_xri
  1393. assert xri_type in ('i-name', 'i-number'), (
  1394. 'xri_type must be "i-name" or "i-number"')
  1395. self.xri_type = xri_type
  1396. super(XRI, self).__init__(**kwargs)
  1397. def _convert_to_python(self, value, state):
  1398. """Prepend the 'xri://' schema if needed and remove trailing spaces"""
  1399. value = value.strip()
  1400. if self.add_xri and not value.startswith('xri://'):
  1401. value = 'xri://' + value
  1402. return value
  1403. def _validate_python(self, value, state=None):
  1404. """Validate an XRI
  1405. @raise Invalid: If at least one of the following conditions in met:
  1406. - C{value} is not a string.
  1407. - The XRI is not a personal, organizational or network one.
  1408. - The relevant validator (i-name or i-number) considers the XRI
  1409. is not valid.
  1410. """
  1411. if not isinstance(value, basestring):
  1412. raise Invalid(
  1413. self.message('badType', state,
  1414. type=str(type(value)), value=value), value, state)
  1415. # Let's remove the schema, if any
  1416. if value.startswith('xri://'):
  1417. value = value[6:]
  1418. if not value[0] in ('@', '=') and not (
  1419. self.xri_type == 'i-number' and value[0] == '!'):
  1420. raise Invalid(self.message('noType', state), value, state)
  1421. if self.xri_type == 'i-name':
  1422. self._validate_iname(value, state)
  1423. else:
  1424. self._validate_inumber(value, state)
  1425. def _validate_iname(self, iname, state):
  1426. """Validate an i-name"""
  1427. # The type is not required here:
  1428. iname = iname[1:]
  1429. if '..' in iname or '--' in iname:
  1430. raise Invalid(self.message('repeatedChar', state), iname, state)
  1431. if self.iname_invalid_start.match(iname):
  1432. raise Invalid(self.message('badInameStart', state), iname, state)
  1433. if not self.iname_valid_pattern.match(iname) or '_' in iname:
  1434. raise Invalid(
  1435. self.message('badIname', state, iname=iname), iname, state)
  1436. def _validate_inumber(self, inumber, state):
  1437. """Validate an i-number"""
  1438. if not self.__class__.inumber_pattern.match(inumber):
  1439. raise Invalid(
  1440. self.message('badInumber', state,
  1441. inumber=inumber, value=inumber), inumber, state)
  1442. class OpenId(FancyValidator):
  1443. r"""
  1444. OpenId validator.
  1445. ::
  1446. >>> v = OpenId(add_schema=True)
  1447. >>> v.to_python(' example.net ')
  1448. 'http://example.net'
  1449. >>> v.to_python('@TurboGears')
  1450. 'xri://@TurboGears'
  1451. >>> w = OpenId(add_schema=False)
  1452. >>> w.to_python(' example.net ')
  1453. Traceback (most recent call last):
  1454. ...
  1455. Invalid: "example.net" is not a valid OpenId (it is neither an URL nor an XRI)
  1456. >>> w.to_python('!!1000')
  1457. '!!1000'
  1458. >>> w.to_python('look@me.com')
  1459. Traceback (most recent call last):
  1460. ...
  1461. Invalid: "look@me.com" is not a valid OpenId (it is neither an URL nor an XRI)
  1462. """
  1463. messages = dict(
  1464. badId=_('"%(id)s" is not a valid OpenId'
  1465. ' (it is neither an URL nor an XRI)'))
  1466. def __init__(self, add_schema=False, **kwargs):
  1467. """Create an OpenId validator.
  1468. @param add_schema: Should the schema be added if not present?
  1469. @type add_schema: C{bool}
  1470. """
  1471. self.url_validator = URL(add_http=add_schema)
  1472. self.iname_validator = XRI(add_schema, xri_type="i-name")
  1473. self.inumber_validator = XRI(add_schema, xri_type="i-number")
  1474. def _convert_to_python(self, value, state):
  1475. value = value.strip()
  1476. try:
  1477. return self.url_validator.to_python(value, state)
  1478. except Invalid:
  1479. try:
  1480. return self.iname_validator.to_python(value, state)
  1481. except Invalid:
  1482. try:
  1483. return self.inumber_validator.to_python(value, state)
  1484. except Invalid:
  1485. pass
  1486. # It's not an OpenId!
  1487. raise Invalid(self.message('badId', state, id=value), value, state)
  1488. def _validate_python(self, value, state):
  1489. self._convert_to_python(value, state)
  1490. def StateProvince(*kw, **kwargs):
  1491. deprecation_warning("please use formencode.national.USStateProvince")
  1492. from formencode.national import USStateProvince
  1493. return USStateProvince(*kw, **kwargs)
  1494. def PhoneNumber(*kw, **kwargs):
  1495. deprecation_warning("please use formencode.national.USPhoneNumber")
  1496. from formencode.national import USPhoneNumber
  1497. return USPhoneNumber(*kw, **kwargs)
  1498. def IPhoneNumberValidator(*kw, **kwargs):
  1499. deprecation_warning(
  1500. "please use formencode.national.InternationalPhoneNumber")
  1501. from formencode.national import InternationalPhoneNumber
  1502. return InternationalPhoneNumber(*kw, **kwargs)
  1503. class FieldStorageUploadConverter(FancyValidator):
  1504. """
  1505. Handles cgi.FieldStorage instances that are file uploads.
  1506. This doesn't do any conversion, but it can detect empty upload
  1507. fields (which appear like normal fields, but have no filename when
  1508. no upload was given).
  1509. """
  1510. def _convert_to_python(self, value, state=None):
  1511. if isinstance(value, cgi.FieldStorage):
  1512. if getattr(value, 'filename', None):
  1513. return value
  1514. raise Invalid('invalid', value, state)
  1515. else:
  1516. return value
  1517. def is_empty(self, value):
  1518. if isinstance(value, cgi.FieldStorage):
  1519. return not bool(getattr(value, 'filename', None))
  1520. return FancyValidator.is_empty(self, value)
  1521. class FileUploadKeeper(FancyValidator):
  1522. """
  1523. Takes two inputs (a dictionary with keys ``static`` and
  1524. ``upload``) and converts them into one value on the Python side (a
  1525. dictionary with ``filename`` and ``content`` keys). The upload
  1526. takes priority over the static value. The filename may be None if
  1527. it can't be discovered.
  1528. Handles uploads of both text and ``cgi.FieldStorage`` upload
  1529. values.
  1530. This is basically for use when you have an upload field, and you
  1531. want to keep the upload around even if the rest of the form
  1532. submission fails. When converting *back* to the form submission,
  1533. there may be extra values ``'original_filename'`` and
  1534. ``'original_content'``, which may want to use in your form to show
  1535. the user you still have their content around.
  1536. To use this, make sure you are using variabledecode, then use
  1537. something like::
  1538. <input type="file" name="myfield.upload">
  1539. <input type="hidden" name="myfield.static">
  1540. Then in your scheme::
  1541. class MyScheme(Scheme):
  1542. myfield = FileUploadKeeper()
  1543. Note that big file uploads mean big hidden fields, and lots of
  1544. bytes passed back and forth in the case of an error.
  1545. """
  1546. upload_key = 'upload'
  1547. static_key = 'static'
  1548. def _convert_to_python(self, value, state):
  1549. upload = value.get(self.upload_key)
  1550. static = value.get(self.static_key, '').strip()
  1551. filename = content = None
  1552. if isinstance(upload, cgi.FieldStorage):
  1553. filename = upload.filename
  1554. content = upload.value
  1555. elif isinstance(upload, basestring) and upload:
  1556. filename = None
  1557. # @@: Should this encode upload if it is unicode?
  1558. content = upload
  1559. if not content and static:
  1560. filename, content = static.split(None, 1)
  1561. filename = '' if filename == '-' else filename.decode('base64')
  1562. content = content.decode('base64')
  1563. return {'filename': filename, 'content': content}
  1564. def _convert_from_python(self, value, state):
  1565. filename = value.get('filename', '')
  1566. content = value.get('content', '')
  1567. if filename or content:
  1568. result = self.pack_content(filename, content)
  1569. return {self.upload_key: '',
  1570. self.static_key: result,
  1571. 'original_filename': filename,
  1572. 'original_content': content}
  1573. else:
  1574. return {self.upload_key: '',
  1575. self.static_key: ''}
  1576. def pack_content(self, filename, content):
  1577. enc_filename = self.base64encode(filename) or '-'
  1578. enc_content = (content or '').encode('base64')
  1579. result = '%s %s' % (enc_filename, enc_content)
  1580. return result
  1581. class DateConverter(FancyValidator):
  1582. """
  1583. Validates and converts a string date, like mm/yy, dd/mm/yy,
  1584. dd-mm-yy, etc. Using ``month_style`` you can support
  1585. ``'mm/dd/yyyy'`` or ``'dd/mm/yyyy'``. Only these two general
  1586. styles are supported.
  1587. Accepts English month names, also abbreviated. Returns value as a
  1588. datetime object (you can get mx.DateTime objects if you use
  1589. ``datetime_module='mxDateTime'``). Two year dates are assumed to
  1590. be within 1950-2020, with dates from 21-49 being ambiguous and
  1591. signaling an error.
  1592. Use accept_day=False if you just want a month/year (like for a
  1593. credit card expiration date).
  1594. ::
  1595. >>> d = DateConverter()
  1596. >>> d.to_python('12/3/09')
  1597. datetime.date(2009, 12, 3)
  1598. >>> d.to_python('12/3/2009')
  1599. datetime.date(2009, 12, 3)
  1600. >>> d.to_python('2/30/04')
  1601. Traceback (most recent call last):
  1602. ...
  1603. Invalid: That month only has 29 days
  1604. >>> d.to_python('13/2/05')
  1605. Traceback (most recent call last):
  1606. ...
  1607. Invalid: Please enter a month from 1 to 12
  1608. >>> d.to_python('1/1/200')
  1609. Traceback (most recent call last):
  1610. ...
  1611. Invalid: Please enter a four-digit year after 1899
  1612. If you change ``month_style`` you can get European-style dates::
  1613. >>> d = DateConverter(month_style='dd/mm/yyyy')
  1614. >>> date = d.to_python('12/3/09')
  1615. >>> date
  1616. datetime.date(2009, 3, 12)
  1617. >>> d.from_python(date)
  1618. '12/03/2009'
  1619. """
  1620. ## @@: accepts only US-style dates
  1621. accept_day = True
  1622. # also allowed: 'dd/mm/yyyy'
  1623. month_style = 'mm/dd/yyyy'
  1624. # Use 'datetime' to force the Python datetime module, or
  1625. # 'mxDateTime' to force the mxDateTime module (None means use
  1626. # datetime, or if not present mxDateTime)
  1627. datetime_module = None
  1628. _month_names = {
  1629. 'jan': 1, 'january': 1,
  1630. 'feb': 2, 'febuary': 2,
  1631. 'mar': 3, 'march': 3,
  1632. 'apr': 4, 'april': 4,
  1633. 'may': 5,
  1634. 'jun': 6, 'june': 6,
  1635. 'jul': 7, 'july': 7,
  1636. 'aug': 8, 'august': 8,
  1637. 'sep': 9, 'sept': 9, 'september': 9,
  1638. 'oct': 10, 'october': 10,
  1639. 'nov': 11, 'november': 11,
  1640. 'dec': 12, 'december': 12,
  1641. }
  1642. _day_month_date_re = re.compile(r'^\s*(\d\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$' % '|'.join(_month_names), re.I)
  1643. _month_day_date_re = re.compile(r'^\s*(\d\d?|%s)[\-\./\\](\d\d?)[\-\./\\](\d\d\d?\d?)\s*$' % '|'.join(_month_names), re.I)
  1644. _month_date_re = re.compile(r'^\s*(\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$' % '|'.join(_month_names), re.I)
  1645. ## @@: Feb. should be leap-year aware (but mxDateTime does catch that)
  1646. _monthDays = {
  1647. 1: 31, 2: 29, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31,
  1648. 9: 30, 10: 31, 11: 30, 12: 31}
  1649. messages = dict(
  1650. badFormat=_('Please enter the date in the form %(format)s'),
  1651. monthRange=_('Please enter a month from 1 to 12'),
  1652. invalidDay=_('Please enter a valid day'),
  1653. dayRange=_('That month only has %(days)i days'),
  1654. invalidDate=_('That is not a valid day (%(exception)s)'),
  1655. unknownMonthName=_('Unknown month name: %(month)s'),
  1656. invalidYear=_('Please enter a number for the year'),
  1657. fourDigitYear=_('Please enter a four-digit year after 1899'),
  1658. wrongFormat=_('Please enter the date in the form %(format)s'))
  1659. def __init__(self, *args, **kw):
  1660. super(DateConverter, self).__init__(*args, **kw)
  1661. if not self.month_style in ('dd/mm/yyyy', 'mm/dd/yyyy'):
  1662. raise TypeError('Bad month_style: %r' % self.month_style)
  1663. def _convert_to_python(self, value, state):
  1664. return (self.convert_day
  1665. if self.accept_day else self.convert_month)(value, state)
  1666. def convert_day(self, value, state):
  1667. self.assert_string(value, state)
  1668. day_month_style = self.month_style == 'dd/mm/yyyy'
  1669. match = (day_month_style and self._day_month_date_re
  1670. or self._month_day_date_re).search(value)
  1671. if not match:
  1672. raise Invalid(
  1673. self.message('badFormat', state,
  1674. format=self.month_style), value, state)
  1675. month, day = match.group(1), match.group(2)
  1676. if day_month_style:
  1677. month, day = day, month
  1678. day = int(day)
  1679. try:
  1680. month = int(month)
  1681. except ValueError:
  1682. month = self.make_month(month, state)
  1683. year = self.make_year(match.group(3), state)
  1684. if not 1 <= month <= 12:
  1685. raise Invalid(self.message('monthRange', state), value, state)
  1686. if day < 1:
  1687. raise Invalid(self.message('invalidDay', state), value, state)
  1688. if self._monthDays[month] < day:
  1689. raise Invalid(
  1690. self.message('dayRange', state,
  1691. days=self._monthDays[month]), value, state)
  1692. dt_mod = import_datetime(self.datetime_module)
  1693. try:
  1694. return datetime_makedate(dt_mod, year, month, day)
  1695. except ValueError, v:
  1696. raise Invalid(
  1697. self.message('invalidDate', state,
  1698. exception=str(v)), value, state)
  1699. def make_month(self, value, state):
  1700. try:
  1701. return int(value)
  1702. except ValueError:
  1703. value = value.lower().strip()
  1704. if value in self._month_names:
  1705. return self._month_names[value]
  1706. else:
  1707. raise Invalid(
  1708. self.message('unknownMonthName', state,
  1709. month=value), value, state)
  1710. def make_year(self, year, state):
  1711. try:
  1712. year = int(year)
  1713. except ValueError:
  1714. raise Invalid(self.message('invalidYear', state), year, state)
  1715. if year <= 20:
  1716. year += 2000
  1717. elif 50 <= year < 100:
  1718. year += 1900
  1719. if 20 < year < 50 or 99 < year < 1900:
  1720. raise Invalid(self.message('fourDigitYear', state), year, state)
  1721. return year
  1722. def convert_month(self, value, state):
  1723. match = self._month_date_re.search(value)
  1724. if not match:
  1725. raise Invalid(
  1726. self.message('wrongFormat', state,
  1727. format='mm/yyyy'), value, state)
  1728. month = self.make_month(match.group(1), state)
  1729. year = self.make_year(match.group(2), state)
  1730. if not 1 <= month <= 12:
  1731. raise Invalid(self.message('monthRange', state), value, state)
  1732. dt_mod = import_datetime(self.datetime_module)
  1733. return datetime_makedate(dt_mod, year, month, 1)
  1734. def _convert_from_python(self, value, state):
  1735. if self.if_empty is not NoDefault and not value:
  1736. return ''
  1737. return (self.unconvert_day
  1738. if self.accept_day else self.unconvert_month)(value, state)
  1739. def unconvert_day(self, value, state):
  1740. # @@ ib: double-check, improve
  1741. return value.strftime('%m/%d/%Y'
  1742. if self.month_style == 'mm/dd/yyyy' else '%d/%m/%Y')
  1743. def unconvert_month(self, value, state):
  1744. # @@ ib: double-check, improve
  1745. return value.strftime('%m/%Y')
  1746. class TimeConverter(FancyValidator):
  1747. """
  1748. Converts times in the format HH:MM:SSampm to (h, m, s).
  1749. Seconds are optional.
  1750. For ampm, set use_ampm = True. For seconds, use_seconds = True.
  1751. Use 'optional' for either of these to make them optional.
  1752. Examples::
  1753. >>> tim = TimeConverter()
  1754. >>> tim.to_python('8:30')
  1755. (8, 30)
  1756. >>> tim.to_python('20:30')
  1757. (20, 30)
  1758. >>> tim.to_python('30:00')
  1759. Traceback (most recent call last):
  1760. ...
  1761. Invalid: You must enter an hour in the range 0-23
  1762. >>> tim.to_python('13:00pm')
  1763. Traceback (most recent call last):
  1764. ...
  1765. Invalid: You must enter an hour in the range 1-12
  1766. >>> tim.to_python('12:-1')
  1767. Traceback (most recent call last):
  1768. ...
  1769. Invalid: You must enter a minute in the range 0-59
  1770. >>> tim.to_python('12:02pm')
  1771. (12, 2)
  1772. >>> tim.to_python('12:02am')
  1773. (0, 2)
  1774. >>> tim.to_python('1:00PM')
  1775. (13, 0)
  1776. >>> tim.from_python((13, 0))
  1777. '13:00:00'
  1778. >>> tim2 = tim(use_ampm=True, use_seconds=False)
  1779. >>> tim2.from_python((13, 0))
  1780. '1:00pm'
  1781. >>> tim2.from_python((0, 0))
  1782. '12:00am'
  1783. >>> tim2.from_python((12, 0))
  1784. '12:00pm'
  1785. Examples with ``datetime.time``::
  1786. >>> v = TimeConverter(use_datetime=True)
  1787. >>> a = v.to_python('18:00')
  1788. >>> a
  1789. datetime.time(18, 0)
  1790. >>> b = v.to_python('30:00')
  1791. Traceback (most recent call last):
  1792. ...
  1793. Invalid: You must enter an hour in the range 0-23
  1794. >>> v2 = TimeConverter(prefer_ampm=True, use_datetime=True)
  1795. >>> v2.from_python(a)
  1796. '6:00:00pm'
  1797. >>> v3 = TimeConverter(prefer_ampm=True,
  1798. ... use_seconds=False, use_datetime=True)
  1799. >>> a = v3.to_python('18:00')
  1800. >>> a
  1801. datetime.time(18, 0)
  1802. >>> v3.from_python(a)
  1803. '6:00pm'
  1804. >>> a = v3.to_python('18:00:00')
  1805. Traceback (most recent call last):
  1806. ...
  1807. Invalid: You may not enter seconds
  1808. """
  1809. use_ampm = 'optional'
  1810. prefer_ampm = False
  1811. use_seconds = 'optional'
  1812. use_datetime = False
  1813. # This can be set to make it prefer mxDateTime:
  1814. datetime_module = None
  1815. messages = dict(
  1816. noAMPM=_('You must indicate AM or PM'),
  1817. tooManyColon=_('There are too many :\'s'),
  1818. noSeconds=_('You may not enter seconds'),
  1819. secondsRequired=_('You must enter seconds'),
  1820. minutesRequired=_('You must enter minutes (after a :)'),
  1821. badNumber=_('The %(part)s value you gave is not a number: %(number)r'),
  1822. badHour=_('You must enter an hour in the range %(range)s'),
  1823. badMinute=_('You must enter a minute in the range 0-59'),
  1824. badSecond=_('You must enter a second in the range 0-59'))
  1825. def _convert_to_python(self, value, state):
  1826. result = self._to_python_tuple(value, state)
  1827. if self.use_datetime:
  1828. dt_mod = import_datetime(self.datetime_module)
  1829. time_class = datetime_time(dt_mod)
  1830. return time_class(*result)
  1831. else:
  1832. return result
  1833. def _to_python_tuple(self, value, state):
  1834. time = value.strip()
  1835. explicit_ampm = False
  1836. if self.use_ampm:
  1837. last_two = time[-2:].lower()
  1838. if last_two not in ('am', 'pm'):
  1839. if self.use_ampm != 'optional':
  1840. raise Invalid(self.message('noAMPM', state), value, state)
  1841. offset = 0
  1842. else:
  1843. explicit_ampm = True
  1844. offset = 12 if last_two == 'pm' else 0
  1845. time = time[:-2]
  1846. else:
  1847. offset = 0
  1848. parts = time.split(':', 3)
  1849. if len(parts) > 3:
  1850. raise Invalid(self.message('tooManyColon', state), value, state)
  1851. if len(parts) == 3 and not self.use_seconds:
  1852. raise Invalid(self.message('noSeconds', state), value, state)
  1853. if (len(parts) == 2
  1854. and self.use_seconds and self.use_seconds != 'optional'):
  1855. raise Invalid(self.message('secondsRequired', state), value, state)
  1856. if len(parts) == 1:
  1857. raise Invalid(self.message('minutesRequired', state), value, state)
  1858. try:
  1859. hour = int(parts[0])
  1860. except ValueError:
  1861. raise Invalid(
  1862. self.message('badNumber', state,
  1863. number=parts[0], part='hour'), value, state)
  1864. if explicit_ampm:
  1865. if not 1 <= hour <= 12:
  1866. raise Invalid(
  1867. self.message('badHour', state,
  1868. number=hour, range='1-12'), value, state)
  1869. if hour == 12 and offset == 12:
  1870. # 12pm == 12
  1871. pass
  1872. elif hour == 12 and offset == 0:
  1873. # 12am == 0
  1874. hour = 0
  1875. else:
  1876. hour += offset
  1877. else:
  1878. if not 0 <= hour < 24:
  1879. raise Invalid(
  1880. self.message('badHour', state,
  1881. number=hour, range='0-23'), value, state)
  1882. try:
  1883. minute = int(parts[1])
  1884. except ValueError:
  1885. raise Invalid(
  1886. self.message('badNumber', state,
  1887. number=parts[1], part='minute'), value, state)
  1888. if not 0 <= minute < 60:
  1889. raise Invalid(
  1890. self.message('badMinute', state, number=minute),
  1891. value, state)
  1892. if len(parts) == 3:
  1893. try:
  1894. second = int(parts[2])
  1895. except ValueError:
  1896. raise Invalid(
  1897. self.message('badNumber', state,
  1898. number=parts[2], part='second'), value, state)
  1899. if not 0 <= second < 60:
  1900. raise Invalid(
  1901. self.message('badSecond', state, number=second),
  1902. value, state)
  1903. else:
  1904. second = None
  1905. if second is None:
  1906. return (hour, minute)
  1907. else:
  1908. return (hour, minute, second)
  1909. def _convert_from_python(self, value, state):
  1910. if isinstance(value, basestring):
  1911. return value
  1912. if hasattr(value, 'hour'):
  1913. hour, minute = value.hour, value.minute
  1914. second = value.second
  1915. elif len(value) == 3:
  1916. hour, minute, second = value
  1917. elif len(value) == 2:
  1918. hour, minute = value
  1919. second = 0
  1920. ampm = ''
  1921. if (self.use_ampm == 'optional' and self.prefer_ampm) or (
  1922. self.use_ampm and self.use_ampm != 'optional'):
  1923. ampm = 'am'
  1924. if hour > 12:
  1925. hour -= 12
  1926. ampm = 'pm'
  1927. elif hour == 12:
  1928. ampm = 'pm'
  1929. elif hour == 0:
  1930. hour = 12
  1931. if self.use_seconds:
  1932. return '%i:%02i:%02i%s' % (hour, minute, second, ampm)
  1933. else:
  1934. return '%i:%02i%s' % (hour, minute, ampm)
  1935. def PostalCode(*kw, **kwargs):
  1936. deprecation_warning("please use formencode.national.USPostalCode")
  1937. from formencode.national import USPostalCode
  1938. return USPostalCode(*kw, **kwargs)
  1939. class StripField(FancyValidator):
  1940. """
  1941. Take a field from a dictionary, removing the key from the dictionary.
  1942. ``name`` is the key. The field value and a new copy of the dictionary
  1943. with that field removed are returned.
  1944. >>> StripField('test').to_python({'a': 1, 'test': 2})
  1945. (2, {'a': 1})
  1946. >>> StripField('test').to_python({})
  1947. Traceback (most recent call last):
  1948. ...
  1949. Invalid: The name 'test' is missing
  1950. """
  1951. __unpackargs__ = ('name',)
  1952. messages = dict(
  1953. missing=_('The name %(name)s is missing'))
  1954. def _convert_to_python(self, valueDict, state):
  1955. v = valueDict.copy()
  1956. try:
  1957. field = v.pop(self.name)
  1958. except KeyError:
  1959. raise Invalid(
  1960. self.message('missing', state, name=repr(self.name)),
  1961. valueDict, state)
  1962. return field, v
  1963. def is_empty(self, value):
  1964. # empty dictionaries don't really apply here
  1965. return False
  1966. class StringBool(FancyValidator): # originally from TurboGears 1
  1967. """
  1968. Converts a string to a boolean.
  1969. Values like 'true' and 'false' are considered True and False,
  1970. respectively; anything in ``true_values`` is true, anything in
  1971. ``false_values`` is false, case-insensitive). The first item of
  1972. those lists is considered the preferred form.
  1973. ::
  1974. >>> s = StringBool()
  1975. >>> s.to_python('yes'), s.to_python('no')
  1976. (True, False)
  1977. >>> s.to_python(1), s.to_python('N')
  1978. (True, False)
  1979. >>> s.to_python('ye')
  1980. Traceback (most recent call last):
  1981. ...
  1982. Invalid: Value should be 'true' or 'false'
  1983. """
  1984. true_values = ['true', 't', 'yes', 'y', 'on', '1']
  1985. false_values = ['false', 'f', 'no', 'n', 'off', '0']
  1986. messages = dict(
  1987. string=_('Value should be %(true)r or %(false)r'))
  1988. def _convert_to_python(self, value, state):
  1989. if isinstance(value, basestring):
  1990. value = value.strip().lower()
  1991. if value in self.true_values:
  1992. return True
  1993. if not value or value in self.false_values:
  1994. return False
  1995. raise Invalid(
  1996. self.message('string', state,
  1997. true=self.true_values[0], false=self.false_values[0]),
  1998. value, state)
  1999. return bool(value)
  2000. def _convert_from_python(self, value, state):
  2001. return (self.true_values if value else self.false_values)[0]
  2002. # Should deprecate:
  2003. StringBoolean = StringBool
  2004. class SignedString(FancyValidator):
  2005. """
  2006. Encodes a string into a signed string, and base64 encodes both the
  2007. signature string and a random nonce.
  2008. It is up to you to provide a secret, and to keep the secret handy
  2009. and consistent.
  2010. """
  2011. messages = dict(
  2012. malformed=_('Value does not contain a signature'),
  2013. badsig=_('Signature is not correct'))
  2014. secret = None
  2015. nonce_length = 4
  2016. def _convert_to_python(self, value, state):
  2017. global sha1
  2018. if not sha1:
  2019. from hashlib import sha1
  2020. assert self.secret is not None, "You must give a secret"
  2021. parts = value.split(None, 1)
  2022. if not parts or len(parts) == 1:
  2023. raise Invalid(self.message('malformed', state), value, state)
  2024. sig, rest = parts
  2025. sig = sig.decode('base64')
  2026. rest = rest.decode('base64')
  2027. nonce = rest[:self.nonce_length]
  2028. rest = rest[self.nonce_length:]
  2029. expected = sha1(str(self.secret) + nonce + rest).digest()
  2030. if expected != sig:
  2031. raise Invalid(self.message('badsig', state), value, state)
  2032. return rest
  2033. def _convert_from_python(self, value, state):
  2034. global sha1
  2035. if not sha1:
  2036. from hashlib import sha1
  2037. nonce = self.make_nonce()
  2038. value = str(value)
  2039. digest = sha1(self.secret + nonce + value).digest()
  2040. return self.encode(digest) + ' ' + self.encode(nonce + value)
  2041. def encode(self, value):
  2042. return value.encode('base64').strip().replace('\n', '')
  2043. def make_nonce(self):
  2044. global random
  2045. if not random:
  2046. import random
  2047. return ''.join(chr(random.randrange(256))
  2048. for _i in xrange(self.nonce_length))
  2049. class IPAddress(FancyValidator):
  2050. """
  2051. Formencode validator to check whether a string is a correct IP address.
  2052. Examples::
  2053. >>> ip = IPAddress()
  2054. >>> ip.to_python('127.0.0.1')
  2055. '127.0.0.1'
  2056. >>> ip.to_python('299.0.0.1')
  2057. Traceback (most recent call last):
  2058. ...
  2059. Invalid: The octets must be within the range of 0-255 (not '299')
  2060. >>> ip.to_python('192.168.0.1/1')
  2061. Traceback (most recent call last):
  2062. ...
  2063. Invalid: Please enter a valid IP address (a.b.c.d)
  2064. >>> ip.to_python('asdf')
  2065. Traceback (most recent call last):
  2066. ...
  2067. Invalid: Please enter a valid IP address (a.b.c.d)
  2068. """
  2069. messages = dict(
  2070. badFormat=_('Please enter a valid IP address (a.b.c.d)'),
  2071. leadingZeros=_('The octets must not have leading zeros'),
  2072. illegalOctets=_('The octets must be within the range of 0-255'
  2073. ' (not %(octet)r)'))
  2074. leading_zeros = False
  2075. def _validate_python(self, value, state=None):
  2076. try:
  2077. if not value:
  2078. raise ValueError
  2079. octets = value.split('.', 5)
  2080. # Only 4 octets?
  2081. if len(octets) != 4:
  2082. raise ValueError
  2083. # Correct octets?
  2084. for octet in octets:
  2085. if octet.startswith('0') and octet != '0':
  2086. if not self.leading_zeros:
  2087. raise Invalid(
  2088. self.message('leadingZeros', state), value, state)
  2089. # strip zeros so this won't be an octal number
  2090. octet = octet.lstrip('0')
  2091. if not 0 <= int(octet) < 256:
  2092. raise Invalid(
  2093. self.message('illegalOctets', state, octet=octet),
  2094. value, state)
  2095. # Splitting faild: wrong syntax
  2096. except ValueError:
  2097. raise Invalid(self.message('badFormat', state), value, state)
  2098. class CIDR(IPAddress):
  2099. """
  2100. Formencode validator to check whether a string is in correct CIDR
  2101. notation (IP address, or IP address plus /mask).
  2102. Examples::
  2103. >>> cidr = CIDR()
  2104. >>> cidr.to_python('127.0.0.1')
  2105. '127.0.0.1'
  2106. >>> cidr.to_python('299.0.0.1')
  2107. Traceback (most recent call last):
  2108. ...
  2109. Invalid: The octets must be within the range of 0-255 (not '299')
  2110. >>> cidr.to_python('192.168.0.1/1')
  2111. Traceback (most recent call last):
  2112. ...
  2113. Invalid: The network size (bits) must be within the range of 8-32 (not '1')
  2114. >>> cidr.to_python('asdf')
  2115. Traceback (most recent call last):
  2116. ...
  2117. Invalid: Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)
  2118. """
  2119. messages = dict(IPAddress._messages,
  2120. badFormat=_('Please enter a valid IP address (a.b.c.d)'
  2121. ' or IP network (a.b.c.d/e)'),
  2122. illegalBits=_('The network size (bits) must be within the range'
  2123. ' of 8-32 (not %(bits)r)'))
  2124. def _validate_python(self, value, state):
  2125. try:
  2126. # Split into octets and bits
  2127. if '/' in value: # a.b.c.d/e
  2128. addr, bits = value.split('/')
  2129. else: # a.b.c.d
  2130. addr, bits = value, 32
  2131. # Use IPAddress validator to validate the IP part
  2132. IPAddress._validate_python(self, addr, state)
  2133. # Bits (netmask) correct?
  2134. if not 8 <= int(bits) <= 32:
  2135. raise Invalid(
  2136. self.message('illegalBits', state, bits=bits),
  2137. value, state)
  2138. # Splitting faild: wrong syntax
  2139. except ValueError:
  2140. raise Invalid(self.message('badFormat', state), value, state)
  2141. class MACAddress(FancyValidator):
  2142. """
  2143. Formencode validator to check whether a string is a correct hardware
  2144. (MAC) address.
  2145. Examples::
  2146. >>> mac = MACAddress()
  2147. >>> mac.to_python('aa:bb:cc:dd:ee:ff')
  2148. 'aabbccddeeff'
  2149. >>> mac.to_python('aa:bb:cc:dd:ee:ff:e')
  2150. Traceback (most recent call last):
  2151. ...
  2152. Invalid: A MAC address must contain 12 digits and A-F; the value you gave has 13 characters
  2153. >>> mac.to_python('aa:bb:cc:dd:ee:fx')
  2154. Traceback (most recent call last):
  2155. ...
  2156. Invalid: MAC addresses may only contain 0-9 and A-F (and optionally :), not 'x'
  2157. >>> MACAddress(add_colons=True).to_python('aabbccddeeff')
  2158. 'aa:bb:cc:dd:ee:ff'
  2159. """
  2160. strip = True
  2161. valid_characters = '0123456789abcdefABCDEF'
  2162. add_colons = False
  2163. messages = dict(
  2164. badLength=_('A MAC address must contain 12 digits and A-F;'
  2165. ' the value you gave has %(length)s characters'),
  2166. badCharacter=_('MAC addresses may only contain 0-9 and A-F'
  2167. ' (and optionally :), not %(char)r'))
  2168. def _convert_to_python(self, value, state):
  2169. address = value.replace(':', '').lower() # remove colons
  2170. if len(address) != 12:
  2171. raise Invalid(
  2172. self.message('badLength', state,
  2173. length=len(address)), address, state)
  2174. for char in address:
  2175. if char not in self.valid_characters:
  2176. raise Invalid(
  2177. self.message('badCharacter', state,
  2178. char=char), address, state)
  2179. if self.add_colons:
  2180. address = '%s:%s:%s:%s:%s:%s' % (
  2181. address[0:2], address[2:4], address[4:6],
  2182. address[6:8], address[8:10], address[10:12])
  2183. return address
  2184. _convert_from_python = _convert_to_python
  2185. class FormValidator(FancyValidator):
  2186. """
  2187. A FormValidator is something that can be chained with a Schema.
  2188. Unlike normal chaining the FormValidator can validate forms that
  2189. aren't entirely valid.
  2190. The important method is .validate(), of course. It gets passed a
  2191. dictionary of the (processed) values from the form. If you have
  2192. .validate_partial_form set to True, then it will get the incomplete
  2193. values as well -- check with the "in" operator if the form was able
  2194. to process any particular field.
  2195. Anyway, .validate() should return a string or a dictionary. If a
  2196. string, it's an error message that applies to the whole form. If
  2197. not, then it should be a dictionary of fieldName: errorMessage.
  2198. The special key "form" is the error message for the form as a whole
  2199. (i.e., a string is equivalent to {"form": string}).
  2200. Returns None on no errors.
  2201. """
  2202. validate_partial_form = False
  2203. validate_partial_python = None
  2204. validate_partial_other = None
  2205. def is_empty(self, value):
  2206. return False
  2207. def field_is_empty(self, value):
  2208. return is_empty(value)
  2209. class RequireIfMissing(FormValidator):
  2210. """
  2211. Require one field based on another field being present or missing.
  2212. This validator is applied to a form, not an individual field (usually
  2213. using a Schema's ``pre_validators`` or ``chained_validators``) and is
  2214. available under both names ``RequireIfMissing`` and ``RequireIfPresent``.
  2215. If you provide a ``missing`` value (a string key name) then
  2216. if that field is missing the field must be entered.
  2217. This gives you an either/or situation.
  2218. If you provide a ``present`` value (another string key name) then
  2219. if that field is present, the required field must also be present.
  2220. ::
  2221. >>> from formencode import validators
  2222. >>> v = validators.RequireIfPresent('phone_type', present='phone')
  2223. >>> v.to_python(dict(phone_type='', phone='510 420 4577'))
  2224. Traceback (most recent call last):
  2225. ...
  2226. Invalid: You must give a value for phone_type
  2227. >>> v.to_python(dict(phone=''))
  2228. {'phone': ''}
  2229. Note that if you have a validator on the optionally-required
  2230. field, you should probably use ``if_missing=None``. This way you
  2231. won't get an error from the Schema about a missing value. For example::
  2232. class PhoneInput(Schema):
  2233. phone = PhoneNumber()
  2234. phone_type = String(if_missing=None)
  2235. chained_validators = [RequireIfPresent('phone_type', present='phone')]
  2236. """
  2237. # Field that potentially is required:
  2238. required = None
  2239. # If this field is missing, then it is required:
  2240. missing = None
  2241. # If this field is present, then it is required:
  2242. present = None
  2243. __unpackargs__ = ('required',)
  2244. def _convert_to_python(self, value_dict, state):
  2245. is_empty = self.field_is_empty
  2246. if is_empty(value_dict.get(self.required)) and (
  2247. (self.missing and is_empty(value_dict.get(self.missing))) or
  2248. (self.present and not is_empty(value_dict.get(self.present)))):
  2249. raise Invalid(
  2250. _('You must give a value for %s') % self.required,
  2251. value_dict, state,
  2252. error_dict={self.required:
  2253. Invalid(self.message('empty', state),
  2254. value_dict.get(self.required), state)})
  2255. return value_dict
  2256. RequireIfPresent = RequireIfMissing
  2257. class FieldsMatch(FormValidator):
  2258. """
  2259. Tests that the given fields match, i.e., are identical. Useful
  2260. for password+confirmation fields. Pass the list of field names in
  2261. as `field_names`.
  2262. ::
  2263. >>> f = FieldsMatch('pass', 'conf')
  2264. >>> sorted(f.to_python({'pass': 'xx', 'conf': 'xx'}).items())
  2265. [('conf', 'xx'), ('pass', 'xx')]
  2266. >>> f.to_python({'pass': 'xx', 'conf': 'yy'})
  2267. Traceback (most recent call last):
  2268. ...
  2269. Invalid: conf: Fields do not match
  2270. """
  2271. show_match = False
  2272. field_names = None
  2273. validate_partial_form = True
  2274. __unpackargs__ = ('*', 'field_names')
  2275. messages = dict(
  2276. invalid=_('Fields do not match (should be %(match)s)'),
  2277. invalidNoMatch=_('Fields do not match'),
  2278. notDict=_('Fields should be a dictionary'))
  2279. def __init__(self, *args, **kw):
  2280. super(FieldsMatch, self).__init__(*args, **kw)
  2281. if len(self.field_names) < 2:
  2282. raise TypeError('FieldsMatch() requires at least two field names')
  2283. def validate_partial(self, field_dict, state):
  2284. for name in self.field_names:
  2285. if name not in field_dict:
  2286. return
  2287. self._validate_python(field_dict, state)
  2288. def _validate_python(self, field_dict, state):
  2289. try:
  2290. ref = field_dict[self.field_names[0]]
  2291. except TypeError:
  2292. # Generally because field_dict isn't a dict
  2293. raise Invalid(self.message('notDict', state), field_dict, state)
  2294. except KeyError:
  2295. ref = ''
  2296. errors = {}
  2297. for name in self.field_names[1:]:
  2298. if field_dict.get(name, '') != ref:
  2299. if self.show_match:
  2300. errors[name] = self.message('invalid', state,
  2301. match=ref)
  2302. else:
  2303. errors[name] = self.message('invalidNoMatch', state)
  2304. if errors:
  2305. error_list = sorted(errors.iteritems())
  2306. error_message = '<br>\n'.join(
  2307. '%s: %s' % (name, value) for name, value in error_list)
  2308. raise Invalid(error_message, field_dict, state, error_dict=errors)
  2309. class CreditCardValidator(FormValidator):
  2310. """
  2311. Checks that credit card numbers are valid (if not real).
  2312. You pass in the name of the field that has the credit card
  2313. type and the field with the credit card number. The credit
  2314. card type should be one of "visa", "mastercard", "amex",
  2315. "dinersclub", "discover", "jcb".
  2316. You must check the expiration date yourself (there is no
  2317. relation between CC number/types and expiration dates).
  2318. ::
  2319. >>> cc = CreditCardValidator()
  2320. >>> sorted(cc.to_python({'ccType': 'visa', 'ccNumber': '4111111111111111'}).items())
  2321. [('ccNumber', '4111111111111111'), ('ccType', 'visa')]
  2322. >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111111'})
  2323. Traceback (most recent call last):
  2324. ...
  2325. Invalid: ccNumber: You did not enter a valid number of digits
  2326. >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111112'})
  2327. Traceback (most recent call last):
  2328. ...
  2329. Invalid: ccNumber: You did not enter a valid number of digits
  2330. >>> cc().to_python({})
  2331. Traceback (most recent call last):
  2332. ...
  2333. Invalid: The field ccType is missing
  2334. """
  2335. validate_partial_form = True
  2336. cc_type_field = 'ccType'
  2337. cc_number_field = 'ccNumber'
  2338. __unpackargs__ = ('cc_type_field', 'cc_number_field')
  2339. messages = dict(
  2340. notANumber=_('Please enter only the number, no other characters'),
  2341. badLength=_('You did not enter a valid number of digits'),
  2342. invalidNumber=_('That number is not valid'),
  2343. missing_key=_('The field %(key)s is missing'))
  2344. def validate_partial(self, field_dict, state):
  2345. if not field_dict.get(self.cc_type_field, None) \
  2346. or not field_dict.get(self.cc_number_field, None):
  2347. return None
  2348. self._validate_python(field_dict, state)
  2349. def _validate_python(self, field_dict, state):
  2350. errors = self._validateReturn(field_dict, state)
  2351. if errors:
  2352. error_list = sorted(errors.iteritems())
  2353. raise Invalid(
  2354. '<br>\n'.join('%s: %s' % (name, value)
  2355. for name, value in error_list),
  2356. field_dict, state, error_dict=errors)
  2357. def _validateReturn(self, field_dict, state):
  2358. for field in self.cc_type_field, self.cc_number_field:
  2359. if field not in field_dict:
  2360. raise Invalid(
  2361. self.message('missing_key', state, key=field),
  2362. field_dict, state)
  2363. ccType = field_dict[self.cc_type_field].lower().strip()
  2364. number = field_dict[self.cc_number_field].strip()
  2365. number = number.replace(' ', '')
  2366. number = number.replace('-', '')
  2367. try:
  2368. long(number)
  2369. except ValueError:
  2370. return {self.cc_number_field: self.message('notANumber', state)}
  2371. assert ccType in self._cardInfo, (
  2372. "I can't validate that type of credit card")
  2373. foundValid = False
  2374. validLength = False
  2375. for prefix, length in self._cardInfo[ccType]:
  2376. if len(number) == length:
  2377. validLength = True
  2378. if number.startswith(prefix):
  2379. foundValid = True
  2380. break
  2381. if not validLength:
  2382. return {self.cc_number_field: self.message('badLength', state)}
  2383. if not foundValid:
  2384. return {self.cc_number_field: self.message('invalidNumber', state)}
  2385. if not self._validateMod10(number):
  2386. return {self.cc_number_field: self.message('invalidNumber', state)}
  2387. return None
  2388. def _validateMod10(self, s):
  2389. """Check string with the mod 10 algorithm (aka "Luhn formula")."""
  2390. checksum, factor = 0, 1
  2391. for c in reversed(s):
  2392. for c in str(factor * int(c)):
  2393. checksum += int(c)
  2394. factor = 3 - factor
  2395. return checksum % 10 == 0
  2396. _cardInfo = {
  2397. "visa": [('4', 16),
  2398. ('4', 13)],
  2399. "mastercard": [('51', 16),
  2400. ('52', 16),
  2401. ('53', 16),
  2402. ('54', 16),
  2403. ('55', 16)],
  2404. "discover": [('6011', 16)],
  2405. "amex": [('34', 15),
  2406. ('37', 15)],
  2407. "dinersclub": [('300', 14),
  2408. ('301', 14),
  2409. ('302', 14),
  2410. ('303', 14),
  2411. ('304', 14),
  2412. ('305', 14),
  2413. ('36', 14),
  2414. ('38', 14)],
  2415. "jcb": [('3', 16),
  2416. ('2131', 15),
  2417. ('1800', 15)],
  2418. }
  2419. class CreditCardExpires(FormValidator):
  2420. """
  2421. Checks that credit card expiration date is valid relative to
  2422. the current date.
  2423. You pass in the name of the field that has the credit card
  2424. expiration month and the field with the credit card expiration
  2425. year.
  2426. ::
  2427. >>> ed = CreditCardExpires()
  2428. >>> sorted(ed.to_python({'ccExpiresMonth': '11', 'ccExpiresYear': '2250'}).items())
  2429. [('ccExpiresMonth', '11'), ('ccExpiresYear', '2250')]
  2430. >>> ed.to_python({'ccExpiresMonth': '10', 'ccExpiresYear': '2005'})
  2431. Traceback (most recent call last):
  2432. ...
  2433. Invalid: ccExpiresMonth: Invalid Expiration Date<br>
  2434. ccExpiresYear: Invalid Expiration Date
  2435. """
  2436. validate_partial_form = True
  2437. cc_expires_month_field = 'ccExpiresMonth'
  2438. cc_expires_year_field = 'ccExpiresYear'
  2439. __unpackargs__ = ('cc_expires_month_field', 'cc_expires_year_field')
  2440. datetime_module = None
  2441. messages = dict(
  2442. notANumber=_('Please enter numbers only for month and year'),
  2443. invalidNumber=_('Invalid Expiration Date'))
  2444. def validate_partial(self, field_dict, state):
  2445. if not field_dict.get(self.cc_expires_month_field, None) \
  2446. or not field_dict.get(self.cc_expires_year_field, None):
  2447. return None
  2448. self._validate_python(field_dict, state)
  2449. def _validate_python(self, field_dict, state):
  2450. errors = self._validateReturn(field_dict, state)
  2451. if errors:
  2452. error_list = sorted(errors.iteritems())
  2453. raise Invalid(
  2454. '<br>\n'.join('%s: %s' % (name, value)
  2455. for name, value in error_list),
  2456. field_dict, state, error_dict=errors)
  2457. def _validateReturn(self, field_dict, state):
  2458. ccExpiresMonth = str(field_dict[self.cc_expires_month_field]).strip()
  2459. ccExpiresYear = str(field_dict[self.cc_expires_year_field]).strip()
  2460. try:
  2461. ccExpiresMonth = int(ccExpiresMonth)
  2462. ccExpiresYear = int(ccExpiresYear)
  2463. dt_mod = import_datetime(self.datetime_module)
  2464. now = datetime_now(dt_mod)
  2465. today = datetime_makedate(dt_mod, now.year, now.month, now.day)
  2466. next_month = ccExpiresMonth % 12 + 1
  2467. next_month_year = ccExpiresYear
  2468. if next_month == 1:
  2469. next_month_year += 1
  2470. expires_date = datetime_makedate(
  2471. dt_mod, next_month_year, next_month, 1)
  2472. assert expires_date > today
  2473. except ValueError:
  2474. return {self.cc_expires_month_field:
  2475. self.message('notANumber', state),
  2476. self.cc_expires_year_field:
  2477. self.message('notANumber', state)}
  2478. except AssertionError:
  2479. return {self.cc_expires_month_field:
  2480. self.message('invalidNumber', state),
  2481. self.cc_expires_year_field:
  2482. self.message('invalidNumber', state)}
  2483. class CreditCardSecurityCode(FormValidator):
  2484. """
  2485. Checks that credit card security code has the correct number
  2486. of digits for the given credit card type.
  2487. You pass in the name of the field that has the credit card
  2488. type and the field with the credit card security code.
  2489. ::
  2490. >>> code = CreditCardSecurityCode()
  2491. >>> sorted(code.to_python({'ccType': 'visa', 'ccCode': '111'}).items())
  2492. [('ccCode', '111'), ('ccType', 'visa')]
  2493. >>> code.to_python({'ccType': 'visa', 'ccCode': '1111'})
  2494. Traceback (most recent call last):
  2495. ...
  2496. Invalid: ccCode: Invalid credit card security code length
  2497. """
  2498. validate_partial_form = True
  2499. cc_type_field = 'ccType'
  2500. cc_code_field = 'ccCode'
  2501. __unpackargs__ = ('cc_type_field', 'cc_code_field')
  2502. messages = dict(
  2503. notANumber=_('Please enter numbers only for credit card security code'),
  2504. badLength=_('Invalid credit card security code length'))
  2505. def validate_partial(self, field_dict, state):
  2506. if (not field_dict.get(self.cc_type_field, None)
  2507. or not field_dict.get(self.cc_code_field, None)):
  2508. return None
  2509. self._validate_python(field_dict, state)
  2510. def _validate_python(self, field_dict, state):
  2511. errors = self._validateReturn(field_dict, state)
  2512. if errors:
  2513. error_list = sorted(errors.iteritems())
  2514. raise Invalid(
  2515. '<br>\n'.join('%s: %s' % (name, value)
  2516. for name, value in error_list),
  2517. field_dict, state, error_dict=errors)
  2518. def _validateReturn(self, field_dict, state):
  2519. ccType = str(field_dict[self.cc_type_field]).strip()
  2520. ccCode = str(field_dict[self.cc_code_field]).strip()
  2521. try:
  2522. int(ccCode)
  2523. except ValueError:
  2524. return {self.cc_code_field: self.message('notANumber', state)}
  2525. length = self._cardInfo[ccType]
  2526. if len(ccCode) != length:
  2527. return {self.cc_code_field: self.message('badLength', state)}
  2528. # key = credit card type, value = length of security code
  2529. _cardInfo = dict(visa=3, mastercard=3, discover=3, amex=4)
  2530. def validators():
  2531. """Return the names of all validators in this module."""
  2532. return [name for name, value in globals().iteritems()
  2533. if isinstance(value, type) and issubclass(value, Validator)]
  2534. __all__ = ['Invalid'] + validators()