1 ## FormEncode, a Form processor
2 ## Copyright (C) 2003, Ian Bicking <ianb@colorstudy.com>
5 Validator/Converters for use with FormEncode.
12 from encodings import idna
14 try: # import dnspython
17 except (IOError, ImportError):
23 # These are only imported when needed
30 from .api import (FancyValidator, Identity, Invalid, NoDefault, Validator,
31 deprecation_warning, is_empty)
33 assert Identity and Invalid and NoDefault # silence unused import warnings
35 # Dummy i18n translation function, nothing is translated here.
36 # Instead this is actually done in api.message.
37 # The surrounding _('string') of the strings is only for extracting
38 # the strings automatically.
39 # If you run pygettext with this source comment this function out temporarily.
43 ############################################################
45 ############################################################
47 # These all deal with accepting both datetime and mxDateTime modules and types
48 datetime_module = None
49 mxDateTime_module = None
52 def import_datetime(module_type):
53 global datetime_module, mxDateTime_module
54 module_type = module_type.lower() if module_type else 'datetime'
55 if module_type == 'datetime':
56 if datetime_module is None:
57 import datetime as datetime_module
58 return datetime_module
59 elif module_type == 'mxdatetime':
60 if mxDateTime_module is None:
61 from mx import DateTime as mxDateTime_module
62 return mxDateTime_module
64 raise ImportError('Invalid datetime module %r' % module_type)
67 def datetime_now(module):
68 if module.__name__ == 'datetime':
69 return module.datetime.now()
74 def datetime_makedate(module, year, month, day):
75 if module.__name__ == 'datetime':
76 return module.date(year, month, day)
79 return module.DateTime(year, month, day)
80 except module.RangeError as e:
81 raise ValueError(str(e))
84 def datetime_time(module):
85 if module.__name__ == 'datetime':
91 def datetime_isotime(module):
92 if module.__name__ == 'datetime':
93 return module.time.isoformat
95 return module.ISO.Time
98 ############################################################
100 ############################################################
102 class ConfirmType(FancyValidator):
104 Confirms that the input/output is of the proper type.
109 The class or a tuple of classes; the item must be an instance
110 of the class or a subclass.
112 A type or tuple of types (or classes); the item must be of
113 the exact class or type. Subclasses are not allowed.
117 >>> cint = ConfirmType(subclass=int)
118 >>> cint.to_python(True)
120 >>> cint.to_python('1')
121 Traceback (most recent call last):
123 Invalid: '1' is not a subclass of <type 'int'>
124 >>> cintfloat = ConfirmType(subclass=(float, int))
125 >>> cintfloat.to_python(1.0), cintfloat.from_python(1.0)
127 >>> cintfloat.to_python(1), cintfloat.from_python(1)
129 >>> cintfloat.to_python(None)
130 Traceback (most recent call last):
132 Invalid: None is not a subclass of one of the types <type 'float'>, <type 'int'>
133 >>> cint2 = ConfirmType(type=int)
134 >>> cint2(accept_python=False).from_python(True)
135 Traceback (most recent call last):
137 Invalid: True must be of the type <type 'int'>
140 accept_iterator = True
146 subclass=_('%(object)r is not a subclass of %(subclass)s'),
147 inSubclass=_('%(object)r is not a subclass of one of the types %(subclassList)s'),
148 inType=_('%(object)r must be one of the types %(typeList)s'),
149 type=_('%(object)r must be of the type %(type)s'))
151 def __init__(self, *args, **kw):
152 FancyValidator.__init__(self, *args, **kw)
154 if isinstance(self.subclass, list):
155 self.subclass = tuple(self.subclass)
156 elif not isinstance(self.subclass, tuple):
157 self.subclass = (self.subclass,)
158 self._validate_python = self.confirm_subclass
160 if isinstance(self.type, list):
161 self.type = tuple(self.type)
162 elif not isinstance(self.type, tuple):
163 self.type = (self.type,)
164 self._validate_python = self.confirm_type
166 def confirm_subclass(self, value, state):
167 if not isinstance(value, self.subclass):
168 if len(self.subclass) == 1:
169 msg = self.message('subclass', state, object=value,
170 subclass=self.subclass[0])
172 subclass_list = ', '.join(map(str, self.subclass))
173 msg = self.message('inSubclass', state, object=value,
174 subclassList=subclass_list)
175 raise Invalid(msg, value, state)
177 def confirm_type(self, value, state):
182 if len(self.type) == 1:
183 msg = self.message('type', state, object=value,
186 msg = self.message('inType', state, object=value,
187 typeList=', '.join(map(str, self.type)))
188 raise Invalid(msg, value, state)
191 def is_empty(self, value):
195 class Wrapper(FancyValidator):
197 Used to convert functions to validator/converters.
199 You can give a simple function for `_convert_to_python`,
200 `_convert_from_python`, `_validate_python` or `_validate_other`.
201 If that function raises an exception, the value is considered invalid.
202 Whatever value the function returns is considered the converted value.
204 Unlike validators, the `state` argument is not used. Functions
205 like `int` can be used here, that take a single argument.
207 Note that as Wrapper will generate a FancyValidator, empty
208 values (those who pass ``FancyValidator.is_empty)`` will return ``None``.
209 To override this behavior you can use ``Wrapper(empty_value=callable)``.
210 For example passing ``Wrapper(empty_value=lambda val: val)`` will return
211 the value itself when is considered empty.
217 >>> wrap = Wrapper(convert_to_python=downcase)
218 >>> wrap.to_python('This')
220 >>> wrap.from_python('This')
222 >>> wrap.to_python('') is None
225 ... convert_from_python=downcase, empty_value=lambda value: value)
226 >>> wrap2.from_python('This')
228 >>> wrap2.to_python('')
230 >>> wrap2.from_python(1)
231 Traceback (most recent call last):
233 Invalid: 'int' object has no attribute 'lower'
234 >>> wrap3 = Wrapper(validate_python=int)
235 >>> wrap3.to_python('1')
237 >>> wrap3.to_python('a') # doctest: +ELLIPSIS
238 Traceback (most recent call last):
240 Invalid: invalid literal for int()...
243 func_convert_to_python = None
244 func_convert_from_python = None
245 func_validate_python = None
246 func_validate_other = None
248 _deprecated_methods = (
249 ('func_to_python', 'func_convert_to_python'),
250 ('func_from_python', 'func_convert_from_python'))
252 def __init__(self, *args, **kw):
253 # allow old method names as parameters
254 if 'to_python' in kw and 'convert_to_python' not in kw:
255 kw['convert_to_python'] = kw.pop('to_python')
256 if 'from_python' in kw and 'convert_from_python' not in kw:
257 kw['convert_from_python'] = kw.pop('from_python')
258 for n in ('convert_to_python', 'convert_from_python',
259 'validate_python', 'validate_other'):
261 kw['func_%s' % n] = kw.pop(n)
262 FancyValidator.__init__(self, *args, **kw)
263 self._convert_to_python = self.wrap(self.func_convert_to_python)
264 self._convert_from_python = self.wrap(self.func_convert_from_python)
265 self._validate_python = self.wrap(self.func_validate_python)
266 self._validate_other = self.wrap(self.func_validate_other)
268 def wrap(self, func):
272 def result(value, state, func=func):
275 except Exception as e:
276 raise Invalid(str(e), value, state)
281 class Constant(FancyValidator):
283 This converter converts everything to the same thing.
285 I.e., you pass in the constant value when initializing, then all
286 values get converted to that constant value.
288 This is only really useful for funny situations, like::
290 # Any evaluates sub validators in reverse order for to_python
291 fromEmailValidator = Any(
292 Constant('unknown@localhost'),
295 In this case, the if the email is not valid
296 ``'unknown@localhost'`` will be used instead. Of course, you
297 could use ``if_invalid`` instead.
301 >>> Constant('X').to_python('y')
305 __unpackargs__ = ('value',)
307 def _convert_to_python(self, value, state):
310 _convert_from_python = _convert_to_python
313 ############################################################
315 ############################################################
317 class MaxLength(FancyValidator):
319 Invalid if the value is longer than `maxLength`. Uses len(),
320 so it can work for strings, lists, or anything with length.
324 >>> max5 = MaxLength(5)
325 >>> max5.to_python('12345')
327 >>> max5.from_python('12345')
329 >>> max5.to_python('123456')
330 Traceback (most recent call last):
332 Invalid: Enter a value less than 5 characters long
333 >>> max5(accept_python=False).from_python('123456')
334 Traceback (most recent call last):
336 Invalid: Enter a value less than 5 characters long
337 >>> max5.to_python([1, 2, 3])
339 >>> max5.to_python([1, 2, 3, 4, 5, 6])
340 Traceback (most recent call last):
342 Invalid: Enter a value less than 5 characters long
343 >>> max5.to_python(5)
344 Traceback (most recent call last):
346 Invalid: Invalid value (value with length expected)
349 __unpackargs__ = ('maxLength',)
352 tooLong=_('Enter a value less than %(maxLength)i characters long'),
353 invalid=_('Invalid value (value with length expected)'))
355 def _validate_python(self, value, state):
357 if value and len(value) > self.maxLength:
359 self.message('tooLong', state,
360 maxLength=self.maxLength), value, state)
365 self.message('invalid', state), value, state)
368 class MinLength(FancyValidator):
370 Invalid if the value is shorter than `minlength`. Uses len(), so
371 it can work for strings, lists, or anything with length. Note
372 that you **must** use ``not_empty=True`` if you don't want to
373 accept empty values -- empty values are not tested for length.
377 >>> min5 = MinLength(5)
378 >>> min5.to_python('12345')
380 >>> min5.from_python('12345')
382 >>> min5.to_python('1234')
383 Traceback (most recent call last):
385 Invalid: Enter a value at least 5 characters long
386 >>> min5(accept_python=False).from_python('1234')
387 Traceback (most recent call last):
389 Invalid: Enter a value at least 5 characters long
390 >>> min5.to_python([1, 2, 3, 4, 5])
392 >>> min5.to_python([1, 2, 3])
393 Traceback (most recent call last):
395 Invalid: Enter a value at least 5 characters long
396 >>> min5.to_python(5)
397 Traceback (most recent call last):
399 Invalid: Invalid value (value with length expected)
403 __unpackargs__ = ('minLength',)
406 tooShort=_('Enter a value at least %(minLength)i characters long'),
407 invalid=_('Invalid value (value with length expected)'))
409 def _validate_python(self, value, state):
411 if len(value) < self.minLength:
413 self.message('tooShort', state,
414 minLength=self.minLength), value, state)
417 self.message('invalid', state), value, state)
420 class NotEmpty(FancyValidator):
422 Invalid if value is empty (empty string, empty list, etc).
424 Generally for objects that Python considers false, except zero
425 which is not considered invalid.
429 >>> ne = NotEmpty(messages=dict(empty='enter something'))
431 Traceback (most recent call last):
433 Invalid: enter something
440 empty=_('Please enter a value'))
442 def _validate_python(self, value, state):
444 # This isn't "empty" for this definition.
447 raise Invalid(self.message('empty', state), value, state)
450 class Empty(FancyValidator):
452 Invalid unless the value is empty. Use cleverly, if at all.
456 >>> Empty.to_python(0)
457 Traceback (most recent call last):
459 Invalid: You cannot enter a value here
463 notEmpty=_('You cannot enter a value here'))
465 def _validate_python(self, value, state):
466 if value or value == 0:
467 raise Invalid(self.message('notEmpty', state), value, state)
470 class Regex(FancyValidator):
472 Invalid if the value doesn't match the regular expression `regex`.
474 The regular expression can be a compiled re object, or a string
475 which will be compiled for you.
477 Use strip=True if you want to strip the value before validation,
478 and as a form of conversion (often useful).
482 >>> cap = Regex(r'^[A-Z]+$')
483 >>> cap.to_python('ABC')
486 Note that ``.from_python()`` calls (in general) do not validate
489 >>> cap.from_python('abc')
491 >>> cap(accept_python=False).from_python('abc')
492 Traceback (most recent call last):
494 Invalid: The input is not valid
496 Traceback (most recent call last):
498 Invalid: The input must be a string (not a <type 'int'>: 1)
499 >>> Regex(r'^[A-Z]+$', strip=True).to_python(' ABC ')
501 >>> Regex(r'this', regexOps=('I',)).to_python('THIS')
509 __unpackargs__ = ('regex',)
512 invalid=_('The input is not valid'))
514 def __init__(self, *args, **kw):
515 FancyValidator.__init__(self, *args, **kw)
516 if isinstance(self.regex, str):
518 assert not isinstance(self.regexOps, str), (
519 "regexOps should be a list of options from the re module "
520 "(names, or actual values)")
521 for op in self.regexOps:
522 if isinstance(op, str):
523 ops |= getattr(re, op)
526 self.regex = re.compile(self.regex, ops)
528 def _validate_python(self, value, state):
529 self.assert_string(value, state)
530 if self.strip and isinstance(value, str):
531 value = value.strip()
532 if not self.regex.search(value):
533 raise Invalid(self.message('invalid', state), value, state)
535 def _convert_to_python(self, value, state):
536 if self.strip and isinstance(value, str):
541 class PlainText(Regex):
543 Test that the field contains only letters, numbers, underscore,
544 and the hyphen. Subclasses Regex.
548 >>> PlainText.to_python('_this9_')
550 >>> PlainText.from_python(' this ')
552 >>> PlainText(accept_python=False).from_python(' this ')
553 Traceback (most recent call last):
555 Invalid: Enter only letters, numbers, or _ (underscore)
556 >>> PlainText(strip=True).to_python(' this ')
558 >>> PlainText(strip=True).from_python(' this ')
562 regex = r"^[a-zA-Z_\-0-9]*$"
565 invalid=_('Enter only letters, numbers, or _ (underscore)'))
568 class OneOf(FancyValidator):
570 Tests that the value is one of the members of a given list.
572 If ``testValueList=True``, then if the input value is a list or
573 tuple, all the members of the sequence will be checked (i.e., the
574 input must be a subset of the allowed values).
576 Use ``hideList=True`` to keep the list of valid values out of the
577 error message in exceptions.
581 >>> oneof = OneOf([1, 2, 3])
582 >>> oneof.to_python(1)
584 >>> oneof.to_python(4)
585 Traceback (most recent call last):
587 Invalid: Value must be one of: 1; 2; 3 (not 4)
588 >>> oneof(testValueList=True).to_python([2, 3, [1, 2, 3]])
590 >>> oneof.to_python([2, 3, [1, 2, 3]])
591 Traceback (most recent call last):
593 Invalid: Value must be one of: 1; 2; 3 (not [2, 3, [1, 2, 3]])
597 testValueList = False
600 __unpackargs__ = ('list',)
603 invalid=_('Invalid value'),
604 notIn=_('Value must be one of: %(items)s (not %(value)r)'))
606 def _validate_python(self, value, state):
607 if self.testValueList and isinstance(value, (list, tuple)):
609 self._validate_python(v, state)
611 if not value in self.list:
613 raise Invalid(self.message('invalid', state), value, state)
616 items = '; '.join(map(str, self.list))
618 items = '; '.join(map(str, self.list))
620 self.message('notIn', state,
621 items=items, value=value), value, state)
624 def accept_iterator(self):
625 return self.testValueList
628 class DictConverter(FancyValidator):
630 Converts values based on a dictionary which has values as keys for
631 the resultant values.
633 If ``allowNull`` is passed, it will not balk if a false value
634 (e.g., '' or None) is given (it will return None in these cases).
636 to_python takes keys and gives values, from_python takes values and
639 If you give hideDict=True, then the contents of the dictionary
640 will not show up in error messages.
644 >>> dc = DictConverter({1: 'one', 2: 'two'})
647 >>> dc.from_python('one')
650 Traceback (most recent call last):
652 Invalid: Enter a value from: 1; 2
653 >>> dc2 = dc(hideDict=True)
659 Traceback (most recent call last):
661 Invalid: Choose something
662 >>> dc.from_python('three')
663 Traceback (most recent call last):
665 Invalid: Nothing in my dictionary goes by the value 'three'. Choose one of: 'one'; 'two'
669 keyNotFound=_('Choose something'),
670 chooseKey=_('Enter a value from: %(items)s'),
671 valueNotFound=_('That value is not known'),
672 chooseValue=_('Nothing in my dictionary goes by the value %(value)s.'
673 ' Choose one of: %(items)s'))
678 __unpackargs__ = ('dict',)
680 def _convert_to_python(self, value, state):
682 return self.dict[value]
685 raise Invalid(self.message('keyNotFound', state), value, state)
687 items = sorted(self.dict)
688 items = '; '.join(map(repr, items))
689 raise Invalid(self.message('chooseKey',
690 state, items=items), value, state)
692 def _convert_from_python(self, value, state):
693 for k, v in self.dict.items():
697 raise Invalid(self.message('valueNotFound', state), value, state)
699 items = '; '.join(map(repr, iter(self.dict.values())))
701 self.message('chooseValue', state,
702 value=repr(value), items=items), value, state)
705 class IndexListConverter(FancyValidator):
707 Converts a index (which may be a string like '2') to the value in
712 >>> index = IndexListConverter(['zero', 'one', 'two'])
713 >>> index.to_python(0)
715 >>> index.from_python('zero')
717 >>> index.to_python('1')
719 >>> index.to_python(5)
720 Traceback (most recent call last):
721 Invalid: Index out of range
722 >>> index(not_empty=True).to_python(None)
723 Traceback (most recent call last):
724 Invalid: Please enter a value
725 >>> index.from_python('five')
726 Traceback (most recent call last):
727 Invalid: Item 'five' was not found in the list
732 __unpackargs__ = ('list',)
735 integer=_('Must be an integer index'),
736 outOfRange=_('Index out of range'),
737 notFound=_('Item %(value)s was not found in the list'))
739 def _convert_to_python(self, value, state):
742 except (ValueError, TypeError):
743 raise Invalid(self.message('integer', state), value, state)
745 return self.list[value]
747 raise Invalid(self.message('outOfRange', state), value, state)
749 def _convert_from_python(self, value, state):
750 for i, v in enumerate(self.list):
754 self.message('notFound', state, value=repr(value)), value, state)
757 class DateValidator(FancyValidator):
759 Validates that a date is within the given range. Be sure to call
760 DateConverter first if you aren't expecting mxDateTime input.
762 ``earliest_date`` and ``latest_date`` may be functions; if so,
763 they will be called each time before validating.
765 ``after_now`` means a time after the current timestamp; note that
766 just a few milliseconds before now is invalid! ``today_or_after``
767 is more permissive, and ignores hours and minutes.
771 >>> from datetime import datetime, timedelta
772 >>> d = DateValidator(earliest_date=datetime(2003, 1, 1))
773 >>> d.to_python(datetime(2004, 1, 1))
774 datetime.datetime(2004, 1, 1, 0, 0)
775 >>> d.to_python(datetime(2002, 1, 1))
776 Traceback (most recent call last):
778 Invalid: Date must be after Wednesday, 01 January 2003
779 >>> d.to_python(datetime(2003, 1, 1))
780 datetime.datetime(2003, 1, 1, 0, 0)
781 >>> d = DateValidator(after_now=True)
782 >>> now = datetime.now()
783 >>> d.to_python(now+timedelta(seconds=5)) == now+timedelta(seconds=5)
785 >>> d.to_python(now-timedelta(days=1))
786 Traceback (most recent call last):
788 Invalid: The date must be sometime in the future
789 >>> d.to_python(now+timedelta(days=1)) > now
791 >>> d = DateValidator(today_or_after=True)
792 >>> d.to_python(now) == now
800 # Like after_now, but just after this morning:
801 today_or_after = False
802 # Use None or 'datetime' for the datetime module in the standard lib,
803 # or 'mxDateTime' to force the mxDateTime module
804 datetime_module = None
807 after=_('Date must be after %(date)s'),
808 before=_('Date must be before %(date)s'),
809 # Double %'s, because this will be substituted twice:
810 date_format=_('%%A, %%d %%B %%Y'),
811 future=_('The date must be sometime in the future'))
813 def _validate_python(self, value, state):
814 date_format = self.message('date_format', state)
815 if (str is not str # Python 2
816 and isinstance(date_format, str)):
817 # strftime uses the locale encoding, not Unicode
818 encoding = locale.getlocale(locale.LC_TIME)[1] or 'utf-8'
819 date_format = date_format.encode(encoding)
822 if self.earliest_date:
823 if callable(self.earliest_date):
824 earliest_date = self.earliest_date()
826 earliest_date = self.earliest_date
827 if value < earliest_date:
828 date_formatted = earliest_date.strftime(date_format)
830 date_formatted = date_formatted.decode(encoding)
832 self.message('after', state, date=date_formatted),
835 if callable(self.latest_date):
836 latest_date = self.latest_date()
838 latest_date = self.latest_date
839 if value > latest_date:
840 date_formatted = latest_date.strftime(date_format)
842 date_formatted = date_formatted.decode(encoding)
844 self.message('before', state, date=date_formatted),
847 dt_mod = import_datetime(self.datetime_module)
848 now = datetime_now(dt_mod)
850 date_formatted = now.strftime(date_format)
852 date_formatted = date_formatted.decode(encoding)
854 self.message('future', state, date=date_formatted),
856 if self.today_or_after:
857 dt_mod = import_datetime(self.datetime_module)
858 now = datetime_now(dt_mod)
859 today = datetime_makedate(dt_mod,
860 now.year, now.month, now.day)
861 value_as_date = datetime_makedate(
862 dt_mod, value.year, value.month, value.day)
863 if value_as_date < today:
864 date_formatted = now.strftime(date_format)
866 date_formatted = date_formatted.decode(encoding)
868 self.message('future', state, date=date_formatted),
872 class Bool(FancyValidator):
874 Always Valid, returns True or False based on the value and the
875 existance of the value.
877 If you want to convert strings like ``'true'`` to booleans, then
882 >>> Bool.to_python(0)
884 >>> Bool.to_python(1)
886 >>> Bool.to_python('')
888 >>> Bool.to_python(None)
894 def _convert_to_python(self, value, state):
897 _convert_from_python = _convert_to_python
899 def empty_value(self, value):
903 class RangeValidator(FancyValidator):
904 """This is an abstract base class for Int and Number.
906 It verifies that a value is within range. It accepts min and max
907 values in the constructor.
909 (Since this is an abstract base class, the tests are in Int and Number.)
914 tooLow=_('Please enter a number that is %(min)s or greater'),
915 tooHigh=_('Please enter a number that is %(max)s or smaller'))
920 def _validate_python(self, value, state):
921 if self.min is not None:
923 msg = self.message('tooLow', state, min=self.min)
924 raise Invalid(msg, value, state)
925 if self.max is not None:
927 msg = self.message('tooHigh', state, max=self.max)
928 raise Invalid(msg, value, state)
931 class Int(RangeValidator):
932 """Convert a value to an integer.
936 >>> Int.to_python('10')
938 >>> Int.to_python('ten')
939 Traceback (most recent call last):
941 Invalid: Please enter an integer value
942 >>> Int(min=5).to_python('6')
944 >>> Int(max=10).to_python('11')
945 Traceback (most recent call last):
947 Invalid: Please enter a number that is 10 or smaller
952 integer=_('Please enter an integer value'))
954 def _convert_to_python(self, value, state):
957 except (ValueError, TypeError):
958 raise Invalid(self.message('integer', state), value, state)
960 _convert_from_python = _convert_to_python
963 class Number(RangeValidator):
964 """Convert a value to a float or integer.
966 Tries to convert it to an integer if no information is lost.
970 >>> Number.to_python('10')
972 >>> Number.to_python('10.5')
974 >>> Number.to_python('ten')
975 Traceback (most recent call last):
977 Invalid: Please enter a number
978 >>> Number.to_python([1.2])
979 Traceback (most recent call last):
981 Invalid: Please enter a number
982 >>> Number(min=5).to_python('6.5')
984 >>> Number(max=10.5).to_python('11.5')
985 Traceback (most recent call last):
987 Invalid: Please enter a number that is 10.5 or smaller
992 number=_('Please enter a number'))
994 def _convert_to_python(self, value, state):
998 int_value = int(value)
999 except OverflowError:
1001 if value == int_value:
1004 except (ValueError, TypeError):
1005 raise Invalid(self.message('number', state), value, state)
1008 class ByteString(FancyValidator):
1009 """Convert to byte string, treating empty things as the empty string.
1011 Under Python 2.x you can also use the alias `String` for this validator.
1013 Also takes a `max` and `min` argument, and the string length must fall
1016 Also you may give an `encoding` argument, which will encode any unicode
1017 that is found. Lists and tuples are joined with `list_joiner`
1018 (default ``', '``) in ``from_python``.
1022 >>> ByteString(min=2).to_python('a')
1023 Traceback (most recent call last):
1025 Invalid: Enter a value 2 characters long or more
1026 >>> ByteString(max=10).to_python('xxxxxxxxxxx')
1027 Traceback (most recent call last):
1029 Invalid: Enter a value not more than 10 characters long
1030 >>> ByteString().from_python(None)
1032 >>> ByteString().from_python([])
1034 >>> ByteString().to_python(None)
1036 >>> ByteString(min=3).to_python(None)
1037 Traceback (most recent call last):
1039 Invalid: Please enter a value
1040 >>> ByteString(min=1).to_python('')
1041 Traceback (most recent call last):
1043 Invalid: Please enter a value
1054 tooLong=_('Enter a value not more than %(max)i characters long'),
1055 tooShort=_('Enter a value %(min)i characters long or more'))
1057 def __initargs__(self, new_attrs):
1058 if self.not_empty is None and self.min:
1059 self.not_empty = True
1061 def _convert_to_python(self, value, state):
1064 elif not isinstance(value, str):
1066 value = bytes(value)
1067 except UnicodeEncodeError:
1069 if self.encoding is not None and isinstance(value, str):
1070 value = value.encode(self.encoding)
1073 def _convert_from_python(self, value, state):
1076 elif not isinstance(value, str):
1077 if isinstance(value, (list, tuple)):
1078 value = self.list_joiner.join(
1079 self._convert_from_python(v, state) for v in value)
1082 except UnicodeEncodeError:
1084 if self.encoding is not None and isinstance(value, str):
1085 value = value.encode(self.encoding)
1087 value = value.strip()
1090 def _validate_other(self, value, state):
1091 if self.max is None and self.min is None:
1095 elif not isinstance(value, str):
1098 except UnicodeEncodeError:
1100 if self.max is not None and len(value) > self.max:
1102 self.message('tooLong', state, max=self.max), value, state)
1103 if self.min is not None and len(value) < self.min:
1105 self.message('tooShort', state, min=self.min), value, state)
1107 def empty_value(self, value):
1111 class UnicodeString(ByteString):
1112 """Convert things to unicode string.
1114 This is implemented as a specialization of the ByteString class.
1116 Under Python 3.x you can also use the alias `String` for this validator.
1118 In addition to the String arguments, an encoding argument is also
1119 accepted. By default the encoding will be utf-8. You can overwrite
1120 this using the encoding parameter. You can also set inputEncoding
1121 and outputEncoding differently. An inputEncoding of None means
1122 "do not decode", an outputEncoding of None means "do not encode".
1124 All converted strings are returned as Unicode strings.
1128 >>> UnicodeString().to_python(None)
1130 >>> UnicodeString().to_python([])
1132 >>> UnicodeString(encoding='utf-7').to_python('Ni Ni Ni')
1137 inputEncoding = NoDefault
1138 outputEncoding = NoDefault
1140 badEncoding=_('Invalid data or incorrect encoding'))
1142 def __init__(self, **kw):
1143 ByteString.__init__(self, **kw)
1144 if self.inputEncoding is NoDefault:
1145 self.inputEncoding = self.encoding
1146 if self.outputEncoding is NoDefault:
1147 self.outputEncoding = self.encoding
1149 def _convert_to_python(self, value, state):
1152 if isinstance(value, str):
1154 if not isinstance(value, str):
1155 if hasattr(value, '__unicode__'):
1158 if not (str is str # Python 3
1159 and isinstance(value, bytes) and self.inputEncoding):
1161 if self.inputEncoding and not isinstance(value, str):
1163 value = str(value, self.inputEncoding)
1164 except UnicodeDecodeError:
1165 raise Invalid(self.message('badEncoding', state), value, state)
1168 self.message('badType', state,
1169 type=type(value), value=value), value, state)
1172 def _convert_from_python(self, value, state):
1173 if not isinstance(value, str):
1174 if hasattr(value, '__unicode__'):
1178 if self.outputEncoding and isinstance(value, str):
1179 value = value.encode(self.outputEncoding)
1182 def empty_value(self, value):
1186 # Provide proper alias for native strings
1188 String = UnicodeString if str is str else ByteString
1191 class Set(FancyValidator):
1193 This is for when you think you may return multiple values for a
1196 This way the result will always be a list, even if there's only
1197 one result. It's equivalent to ForEach(convert_to_list=True).
1199 If you give ``use_set=True``, then it will return an actual
1204 >>> Set.to_python(None)
1206 >>> Set.to_python('this')
1208 >>> Set.to_python(('this', 'that'))
1210 >>> s = Set(use_set=True)
1211 >>> s.to_python(None)
1213 >>> s.to_python('this')
1215 >>> s.to_python(('this',))
1222 accept_iterator = True
1224 def _convert_to_python(self, value, state):
1226 if isinstance(value, set):
1228 elif isinstance(value, (list, tuple)):
1235 if isinstance(value, list):
1237 elif isinstance(value, set):
1239 elif isinstance(value, tuple):
1246 def empty_value(self, value):
1253 class Email(FancyValidator):
1255 Validate an email address.
1257 If you pass ``resolve_domain=True``, then it will try to resolve
1258 the domain name to make sure it's valid. This takes longer, of
1259 course. You must have the `dnspython <http://www.dnspython.org/>`__ modules
1260 installed to look up DNS (MX and A) records.
1265 >>> e.to_python(' test@foo.com ')
1267 >>> e.to_python('test')
1268 Traceback (most recent call last):
1270 Invalid: An email address must contain a single @
1271 >>> e.to_python('test@foobar')
1272 Traceback (most recent call last):
1274 Invalid: The domain portion of the email address is invalid (the portion after the @: foobar)
1275 >>> e.to_python('test@foobar.com.5')
1276 Traceback (most recent call last):
1278 Invalid: The domain portion of the email address is invalid (the portion after the @: foobar.com.5)
1279 >>> e.to_python('test@foo..bar.com')
1280 Traceback (most recent call last):
1282 Invalid: The domain portion of the email address is invalid (the portion after the @: foo..bar.com)
1283 >>> e.to_python('test@.foo.bar.com')
1284 Traceback (most recent call last):
1286 Invalid: The domain portion of the email address is invalid (the portion after the @: .foo.bar.com)
1287 >>> e.to_python('nobody@xn--m7r7ml7t24h.com')
1288 'nobody@xn--m7r7ml7t24h.com'
1289 >>> e.to_python('o*reilly@test.com')
1291 >>> e = Email(resolve_domain=True)
1292 >>> e.resolve_domain
1294 >>> e.to_python('doesnotexist@colorstudy.com')
1295 'doesnotexist@colorstudy.com'
1296 >>> e.to_python('test@nyu.edu')
1298 >>> # NOTE: If you do not have dnspython installed this example won't work:
1299 >>> e.to_python('test@thisdomaindoesnotexistithinkforsure.com')
1300 Traceback (most recent call last):
1302 Invalid: The domain of the email address does not exist (the portion after the @: thisdomaindoesnotexistithinkforsure.com)
1303 >>> e.to_python('test@google.com')
1305 >>> e = Email(not_empty=False)
1310 resolve_domain = False
1311 resolve_timeout = 10 # timeout in seconds when resolving domains
1313 usernameRE = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$")
1314 domainRE = re.compile(r'''
1315 ^(?:[a-z0-9][a-z0-9\-]{,62}\.)+ # subdomain
1316 (?:[a-z]{2,63}|xn--[a-z0-9\-]{2,59})$ # top level domain
1317 ''', re.I | re.VERBOSE)
1320 empty=_('Please enter an email address'),
1321 noAt=_('An email address must contain a single @'),
1322 badUsername=_('The username portion of the email address is invalid'
1323 ' (the portion before the @: %(username)s)'),
1324 socketError=_('An error occured when trying to connect to the server:'
1326 badDomain=_('The domain portion of the email address is invalid'
1327 ' (the portion after the @: %(domain)s)'),
1328 domainDoesNotExist=_('The domain of the email address does not exist'
1329 ' (the portion after the @: %(domain)s)'))
1331 def __init__(self, *args, **kw):
1332 FancyValidator.__init__(self, *args, **kw)
1333 if self.resolve_domain:
1336 "dnspython <http://www.dnspython.org/> is not installed on"
1337 " your system (or the dns.resolver package cannot be found)."
1338 " I cannot resolve domain names in addresses")
1339 raise ImportError("no module named dns.resolver")
1341 def _validate_python(self, value, state):
1343 raise Invalid(self.message('empty', state), value, state)
1344 value = value.strip()
1345 splitted = value.split('@', 1)
1347 username, domain = splitted
1349 raise Invalid(self.message('noAt', state), value, state)
1350 if not self.usernameRE.search(username):
1352 self.message('badUsername', state, username=username),
1355 idna_domain = [idna.ToASCII(p) for p in domain.split('.')]
1356 if str is str: # Python 3
1357 idna_domain = [p.decode('ascii') for p in idna_domain]
1358 idna_domain = '.'.join(idna_domain)
1359 except UnicodeError:
1360 # UnicodeError: label empty or too long
1361 # This exception might happen if we have an invalid domain name part
1362 # (for example test@.foo.bar.com)
1364 self.message('badDomain', state, domain=domain),
1366 if not self.domainRE.search(idna_domain):
1368 self.message('badDomain', state, domain=domain),
1370 if self.resolve_domain:
1371 assert have_dns, "dnspython should be available"
1377 dns.resolver.query(domain, 'MX')
1378 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
1380 dns.resolver.query(domain, 'A')
1381 except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer) as e:
1383 self.message('domainDoesNotExist',
1384 state, domain=domain), value, state)
1385 except (socket.error, dns.exception.DNSException) as e:
1387 self.message('socketError', state, error=e), value, state)
1389 def _convert_to_python(self, value, state):
1390 return value.strip()
1393 class URL(FancyValidator):
1395 Validate a URL, either http://... or https://. If check_exists
1396 is true, then we'll actually make a request for the page.
1398 If add_http is true, then if no scheme is present we'll add
1403 >>> u = URL(add_http=True)
1404 >>> u.to_python('foo.com')
1406 >>> u.to_python('http://hahaha.ha/bar.html')
1407 'http://hahaha.ha/bar.html'
1408 >>> u.to_python('http://xn--m7r7ml7t24h.com')
1409 'http://xn--m7r7ml7t24h.com'
1410 >>> u.to_python('http://xn--c1aay4a.xn--p1ai')
1411 'http://xn--c1aay4a.xn--p1ai'
1412 >>> u.to_python('http://foo.com/test?bar=baz&fleem=morx')
1413 'http://foo.com/test?bar=baz&fleem=morx'
1414 >>> u.to_python('http://foo.com/login?came_from=http%3A%2F%2Ffoo.com%2Ftest')
1415 'http://foo.com/login?came_from=http%3A%2F%2Ffoo.com%2Ftest'
1416 >>> u.to_python('http://foo.com:8000/test.html')
1417 'http://foo.com:8000/test.html'
1418 >>> u.to_python('http://foo.com/something\\nelse')
1419 Traceback (most recent call last):
1421 Invalid: That is not a valid URL
1422 >>> u.to_python('https://test.com')
1424 >>> u.to_python('http://test')
1425 Traceback (most recent call last):
1427 Invalid: You must provide a full domain name (like test.com)
1428 >>> u.to_python('http://test..com')
1429 Traceback (most recent call last):
1431 Invalid: That is not a valid URL
1432 >>> u = URL(add_http=False, check_exists=True)
1433 >>> u.to_python('http://google.com')
1435 >>> u.to_python('google.com')
1436 Traceback (most recent call last):
1438 Invalid: You must start your URL with http://, https://, etc
1439 >>> u.to_python('http://www.formencode.org/does/not/exist/page.html')
1440 Traceback (most recent call last):
1442 Invalid: The server responded that the page could not be found
1443 >>> u.to_python('http://this.domain.does.not.exist.example.org/test.html')
1444 ... # doctest: +ELLIPSIS
1445 Traceback (most recent call last):
1447 Invalid: An error occured when trying to connect to the server: ...
1449 If you want to allow addresses without a TLD (e.g., ``localhost``) you can do::
1451 >>> URL(require_tld=False).to_python('http://localhost')
1454 By default, internationalized domain names (IDNA) in Unicode will be
1455 accepted and encoded to ASCII using Punycode (as described in RFC 3490).
1456 You may set allow_idna to False to change this behavior::
1458 >>> URL(allow_idna=True).to_python(
1459 ... 'http://\\u0433\\u0443\\u0433\\u043b.\\u0440\\u0444')
1460 'http://xn--c1aay4a.xn--p1ai'
1461 >>> URL(allow_idna=True, add_http=True).to_python(
1462 ... '\\u0433\\u0443\\u0433\\u043b.\\u0440\\u0444')
1463 'http://xn--c1aay4a.xn--p1ai'
1464 >>> URL(allow_idna=False).to_python(
1465 ... 'http://\\u0433\\u0443\\u0433\\u043b.\\u0440\\u0444')
1466 Traceback (most recent call last):
1468 Invalid: That is not a valid URL
1474 check_exists = False
1477 url_re = re.compile(r'''
1479 (?:[%:\w]*@)? # authenticator
1481 (?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]?))|
1482 (?P<domain>[a-z0-9][a-z0-9\-]{,62}\.)* # subdomain
1483 (?P<tld>[a-z]{2,63}|xn--[a-z0-9\-]{2,59}) # top level domain
1485 (?::[0-9]{1,5})? # port
1487 (?P<path>/[a-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]*)?
1489 ''', re.I | re.VERBOSE)
1491 scheme_re = re.compile(r'^[a-zA-Z]+:')
1494 noScheme=_('You must start your URL with http://, https://, etc'),
1495 badURL=_('That is not a valid URL'),
1496 httpError=_('An error occurred when trying to access the URL:'
1498 socketError=_('An error occured when trying to connect to the server:'
1500 notFound=_('The server responded that the page could not be found'),
1501 status=_('The server responded with a bad status code (%(status)s)'),
1502 noTLD=_('You must provide a full domain name (like %(domain)s.com)'))
1504 def _convert_to_python(self, value, state):
1505 value = value.strip()
1507 if not self.scheme_re.search(value):
1508 value = 'http://' + value
1510 value = self._encode_idna(value)
1511 match = self.scheme_re.search(value)
1513 raise Invalid(self.message('noScheme', state), value, state)
1514 value = match.group(0).lower() + value[len(match.group(0)):]
1515 match = self.url_re.search(value)
1517 raise Invalid(self.message('badURL', state), value, state)
1518 if self.require_tld and not match.group('domain'):
1520 self.message('noTLD', state, domain=match.group('tld')),
1522 if self.check_exists and value.startswith(('http://', 'https://')):
1523 self._check_url_exists(value, state)
1526 def _encode_idna(self, url):
1528 if urlparse is None:
1531 scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(
1536 netloc = netloc.encode('idna')
1537 if str is str: # Python 3
1538 netloc = netloc.decode('ascii')
1539 return str(urllib.parse.urlunparse((scheme, netloc,
1540 path, params, query, fragment)))
1541 except UnicodeError:
1544 def _check_url_exists(self, url, state):
1545 global httplib, urlparse, socket
1548 if urlparse is None:
1552 scheme, netloc, path, params, query, fragment = urllib.parse.urlparse(
1554 if scheme == 'https':
1555 ConnClass = http.client.HTTPSConnection
1557 ConnClass = http.client.HTTPConnection
1559 conn = ConnClass(netloc)
1561 path += ';' + params
1564 conn.request('HEAD', path)
1565 res = conn.getresponse()
1566 except http.client.HTTPException as e:
1568 self.message('httpError', state, error=e), state, url)
1569 except socket.error as e:
1571 self.message('socketError', state, error=e), state, url)
1573 if res.status == 404:
1575 self.message('notFound', state), state, url)
1576 if not 200 <= res.status < 500:
1578 self.message('status', state, status=res.status),
1582 class XRI(FancyValidator):
1586 It supports both i-names and i-numbers, of the first version of the XRI
1591 >>> inames = XRI(xri_type="i-name")
1592 >>> inames.to_python(" =John.Smith ")
1594 >>> inames.to_python("@Free.Software.Foundation")
1595 '@Free.Software.Foundation'
1596 >>> inames.to_python("Python.Software.Foundation")
1597 Traceback (most recent call last):
1599 Invalid: The type of i-name is not defined; it may be either individual or organizational
1600 >>> inames.to_python("http://example.org")
1601 Traceback (most recent call last):
1603 Invalid: The type of i-name is not defined; it may be either individual or organizational
1604 >>> inames.to_python("=!2C43.1A9F.B6F6.E8E6")
1605 Traceback (most recent call last):
1607 Invalid: "!2C43.1A9F.B6F6.E8E6" is an invalid i-name
1608 >>> iname_with_schema = XRI(True, xri_type="i-name")
1609 >>> iname_with_schema.to_python("=Richard.Stallman")
1610 'xri://=Richard.Stallman'
1611 >>> inames.to_python("=John Smith")
1612 Traceback (most recent call last):
1614 Invalid: "John Smith" is an invalid i-name
1615 >>> inumbers = XRI(xri_type="i-number")
1616 >>> inumbers.to_python("!!1000!de21.4536.2cb2.8074")
1617 '!!1000!de21.4536.2cb2.8074'
1618 >>> inumbers.to_python("@!1000.9554.fabd.129c!2847.df3c")
1619 '@!1000.9554.fabd.129c!2847.df3c'
1623 iname_valid_pattern = re.compile(r"""
1625 [\w]+ # A global alphanumeric i-name
1626 (\.[\w]+)* # An i-name with dots
1627 (\*[\w]+(\.[\w]+)*)* # A community i-name
1629 """, re.VERBOSE | re.UNICODE)
1631 iname_invalid_start = re.compile(r"^[\d\.-]", re.UNICODE)
1632 """@cvar: These characters must not be at the beggining of the i-name"""
1634 inumber_pattern = re.compile(r"""
1637 [=@]! # It's a personal or organization i-number
1639 !! # It's a network i-number
1641 [\dA-F]{1,4}(\.[\dA-F]{1,4}){0,3} # A global i-number
1642 (![\dA-F]{1,4}(\.[\dA-F]{1,4}){0,3})* # Zero or more sub i-numbers
1644 """, re.VERBOSE | re.IGNORECASE)
1647 noType=_('The type of i-name is not defined;'
1648 ' it may be either individual or organizational'),
1649 repeatedChar=_('Dots and dashes may not be repeated consecutively'),
1650 badIname=_('"%(iname)s" is an invalid i-name'),
1651 badInameStart=_('i-names may not start with numbers'
1652 ' nor punctuation marks'),
1653 badInumber=_('"%(inumber)s" is an invalid i-number'),
1654 badType=_('The XRI must be a string (not a %(type)s: %(value)r)'),
1655 badXri=_('"%(xri_type)s" is not a valid type of XRI'))
1657 def __init__(self, add_xri=False, xri_type="i-name", **kwargs):
1658 """Create an XRI validator.
1660 @param add_xri: Should the schema be added if not present?
1661 Officially it's optional.
1662 @type add_xri: C{bool}
1663 @param xri_type: What type of XRI should be validated?
1664 Possible values: C{i-name} or C{i-number}.
1665 @type xri_type: C{str}
1668 self.add_xri = add_xri
1669 assert xri_type in ('i-name', 'i-number'), (
1670 'xri_type must be "i-name" or "i-number"')
1671 self.xri_type = xri_type
1672 super(XRI, self).__init__(**kwargs)
1674 def _convert_to_python(self, value, state):
1675 """Prepend the 'xri://' schema if needed and remove trailing spaces"""
1676 value = value.strip()
1677 if self.add_xri and not value.startswith('xri://'):
1678 value = 'xri://' + value
1681 def _validate_python(self, value, state=None):
1684 @raise Invalid: If at least one of the following conditions in met:
1685 - C{value} is not a string.
1686 - The XRI is not a personal, organizational or network one.
1687 - The relevant validator (i-name or i-number) considers the XRI
1691 if not isinstance(value, str):
1693 self.message('badType', state,
1694 type=str(type(value)), value=value), value, state)
1696 # Let's remove the schema, if any
1697 if value.startswith('xri://'):
1700 if not value[0] in ('@', '=') and not (
1701 self.xri_type == 'i-number' and value[0] == '!'):
1702 raise Invalid(self.message('noType', state), value, state)
1704 if self.xri_type == 'i-name':
1705 self._validate_iname(value, state)
1707 self._validate_inumber(value, state)
1709 def _validate_iname(self, iname, state):
1710 """Validate an i-name"""
1711 # The type is not required here:
1713 if '..' in iname or '--' in iname:
1714 raise Invalid(self.message('repeatedChar', state), iname, state)
1715 if self.iname_invalid_start.match(iname):
1716 raise Invalid(self.message('badInameStart', state), iname, state)
1717 if not self.iname_valid_pattern.match(iname) or '_' in iname:
1719 self.message('badIname', state, iname=iname), iname, state)
1721 def _validate_inumber(self, inumber, state):
1722 """Validate an i-number"""
1723 if not self.__class__.inumber_pattern.match(inumber):
1725 self.message('badInumber', state,
1726 inumber=inumber, value=inumber), inumber, state)
1729 class OpenId(FancyValidator):
1734 >>> v = OpenId(add_schema=True)
1735 >>> v.to_python(' example.net ')
1736 'http://example.net'
1737 >>> v.to_python('@TurboGears')
1739 >>> w = OpenId(add_schema=False)
1740 >>> w.to_python(' example.net ')
1741 Traceback (most recent call last):
1743 Invalid: "example.net" is not a valid OpenId (it is neither an URL nor an XRI)
1744 >>> w.to_python('!!1000')
1746 >>> w.to_python('look@me.com')
1747 Traceback (most recent call last):
1749 Invalid: "look@me.com" is not a valid OpenId (it is neither an URL nor an XRI)
1754 badId=_('"%(id)s" is not a valid OpenId'
1755 ' (it is neither an URL nor an XRI)'))
1757 def __init__(self, add_schema=False, **kwargs):
1758 """Create an OpenId validator.
1760 @param add_schema: Should the schema be added if not present?
1761 @type add_schema: C{bool}
1764 self.url_validator = URL(add_http=add_schema)
1765 self.iname_validator = XRI(add_schema, xri_type="i-name")
1766 self.inumber_validator = XRI(add_schema, xri_type="i-number")
1768 def _convert_to_python(self, value, state):
1769 value = value.strip()
1771 return self.url_validator.to_python(value, state)
1774 return self.iname_validator.to_python(value, state)
1777 return self.inumber_validator.to_python(value, state)
1780 # It's not an OpenId!
1781 raise Invalid(self.message('badId', state, id=value), value, state)
1783 def _validate_python(self, value, state):
1784 self._convert_to_python(value, state)
1787 def StateProvince(*kw, **kwargs):
1788 deprecation_warning("please use formencode.national.USStateProvince")
1789 from formencode.national import USStateProvince
1790 return USStateProvince(*kw, **kwargs)
1793 def PhoneNumber(*kw, **kwargs):
1794 deprecation_warning("please use formencode.national.USPhoneNumber")
1795 from formencode.national import USPhoneNumber
1796 return USPhoneNumber(*kw, **kwargs)
1799 def IPhoneNumberValidator(*kw, **kwargs):
1800 deprecation_warning(
1801 "please use formencode.national.InternationalPhoneNumber")
1802 from formencode.national import InternationalPhoneNumber
1803 return InternationalPhoneNumber(*kw, **kwargs)
1806 class FieldStorageUploadConverter(FancyValidator):
1808 Handles cgi.FieldStorage instances that are file uploads.
1810 This doesn't do any conversion, but it can detect empty upload
1811 fields (which appear like normal fields, but have no filename when
1812 no upload was given).
1814 def _convert_to_python(self, value, state=None):
1815 if isinstance(value, cgi.FieldStorage):
1816 if getattr(value, 'filename', None):
1818 raise Invalid('invalid', value, state)
1822 def is_empty(self, value):
1823 if isinstance(value, cgi.FieldStorage):
1824 return not bool(getattr(value, 'filename', None))
1825 return FancyValidator.is_empty(self, value)
1828 class FileUploadKeeper(FancyValidator):
1830 Takes two inputs (a dictionary with keys ``static`` and
1831 ``upload``) and converts them into one value on the Python side (a
1832 dictionary with ``filename`` and ``content`` keys). The upload
1833 takes priority over the static value. The filename may be None if
1834 it can't be discovered.
1836 Handles uploads of both text and ``cgi.FieldStorage`` upload
1839 This is basically for use when you have an upload field, and you
1840 want to keep the upload around even if the rest of the form
1841 submission fails. When converting *back* to the form submission,
1842 there may be extra values ``'original_filename'`` and
1843 ``'original_content'``, which may want to use in your form to show
1844 the user you still have their content around.
1846 To use this, make sure you are using variabledecode, then use
1849 <input type="file" name="myfield.upload">
1850 <input type="hidden" name="myfield.static">
1852 Then in your scheme::
1854 class MyScheme(Scheme):
1855 myfield = FileUploadKeeper()
1857 Note that big file uploads mean big hidden fields, and lots of
1858 bytes passed back and forth in the case of an error.
1861 upload_key = 'upload'
1862 static_key = 'static'
1864 def _convert_to_python(self, value, state):
1865 upload = value.get(self.upload_key)
1866 static = value.get(self.static_key, '').strip()
1867 filename = content = None
1868 if isinstance(upload, cgi.FieldStorage):
1869 filename = upload.filename
1870 content = upload.value
1871 elif isinstance(upload, str) and upload:
1873 # @@: Should this encode upload if it is unicode?
1875 if not content and static:
1876 filename, content = static.split(None, 1)
1877 filename = '' if filename == '-' else filename.decode('base64')
1878 content = content.decode('base64')
1879 return {'filename': filename, 'content': content}
1881 def _convert_from_python(self, value, state):
1882 filename = value.get('filename', '')
1883 content = value.get('content', '')
1884 if filename or content:
1885 result = self.pack_content(filename, content)
1886 return {self.upload_key: '',
1887 self.static_key: result,
1888 'original_filename': filename,
1889 'original_content': content}
1891 return {self.upload_key: '',
1892 self.static_key: ''}
1894 def pack_content(self, filename, content):
1895 enc_filename = self.base64encode(filename) or '-'
1896 enc_content = (content or '').encode('base64')
1897 result = '%s %s' % (enc_filename, enc_content)
1901 class DateConverter(FancyValidator):
1903 Validates and converts a string date, like mm/yy, dd/mm/yy,
1904 dd-mm-yy, etc. Using ``month_style`` you can support
1905 the three general styles ``mdy`` = ``us`` = ``mm/dd/yyyy``,
1906 ``dmy`` = ``euro`` = ``dd/mm/yyyy`` and
1907 ``ymd`` = ``iso`` = ``yyyy/mm/dd``.
1909 Accepts English month names, also abbreviated. Returns value as a
1910 datetime object (you can get mx.DateTime objects if you use
1911 ``datetime_module='mxDateTime'``). Two year dates are assumed to
1912 be within 1950-2020, with dates from 21-49 being ambiguous and
1915 Use accept_day=False if you just want a month/year (like for a
1916 credit card expiration date).
1920 >>> d = DateConverter()
1921 >>> d.to_python('12/3/09')
1922 datetime.date(2009, 12, 3)
1923 >>> d.to_python('12/3/2009')
1924 datetime.date(2009, 12, 3)
1925 >>> d.to_python('2/30/04')
1926 Traceback (most recent call last):
1928 Invalid: That month only has 29 days
1929 >>> d.to_python('13/2/05')
1930 Traceback (most recent call last):
1932 Invalid: Please enter a month from 1 to 12
1933 >>> d.to_python('1/1/200')
1934 Traceback (most recent call last):
1936 Invalid: Please enter a four-digit year after 1899
1938 If you change ``month_style`` you can get European-style dates::
1940 >>> d = DateConverter(month_style='dd/mm/yyyy')
1941 >>> date = d.to_python('12/3/09')
1943 datetime.date(2009, 3, 12)
1944 >>> d.from_python(date)
1948 # set to False if you want only month and year
1950 # allowed month styles: 'mdy' = 'us', 'dmy' = 'euro', 'ymd' = 'iso'
1951 # also allowed: 'mm/dd/yyyy', 'dd/mm/yyyy', 'yyyy/mm/dd'
1953 # preferred separator for reverse conversion: '/', '.' or '-'
1956 # Use 'datetime' to force the Python datetime module, or
1957 # 'mxDateTime' to force the mxDateTime module (None means use
1958 # datetime, or if not present mxDateTime)
1959 datetime_module = None
1962 'jan': 1, 'january': 1,
1963 'feb': 2, 'febuary': 2,
1964 'mar': 3, 'march': 3,
1965 'apr': 4, 'april': 4,
1967 'jun': 6, 'june': 6,
1968 'jul': 7, 'july': 7,
1969 'aug': 8, 'august': 8,
1970 'sep': 9, 'sept': 9, 'september': 9,
1971 'oct': 10, 'october': 10,
1972 'nov': 11, 'november': 11,
1973 'dec': 12, 'december': 12,
1978 r'^\s*(\d\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$'
1979 % '|'.join(_month_names), re.I),
1981 r'^\s*(\d\d?|%s)[\-\./\\](\d\d?)[\-\./\\](\d\d\d?\d?)\s*$'
1982 % '|'.join(_month_names), re.I),
1984 r'^\s*(\d\d\d?\d?)[\-\./\\](\d\d?|%s)[\-\./\\](\d\d?)\s*$'
1985 % '|'.join(_month_names), re.I),
1987 r'^\s*(\d\d?|%s)[\-\./\\](\d\d\d?\d?)\s*$'
1988 % '|'.join(_month_names), re.I),
1990 r'^\s*(\d\d\d?\d?)[\-\./\\](\d\d?|%s)\s*$'
1991 % '|'.join(_month_names), re.I))
1993 _formats = dict(d='%d', m='%m', y='%Y')
1995 _human_formats = dict(d=_('DD'), m=_('MM'), y=_('YYYY'))
1997 # Feb. should be leap-year aware (but mxDateTime does catch that)
1999 1: 31, 2: 29, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31,
2000 9: 30, 10: 31, 11: 30, 12: 31}
2003 badFormat=_('Please enter the date in the form %(format)s'),
2004 monthRange=_('Please enter a month from 1 to 12'),
2005 invalidDay=_('Please enter a valid day'),
2006 dayRange=_('That month only has %(days)i days'),
2007 invalidDate=_('That is not a valid day (%(exception)s)'),
2008 unknownMonthName=_('Unknown month name: %(month)s'),
2009 invalidYear=_('Please enter a number for the year'),
2010 fourDigitYear=_('Please enter a four-digit year after 1899'),
2011 wrongFormat=_('Please enter the date in the form %(format)s'))
2013 def __init__(self, *args, **kw):
2014 super(DateConverter, self).__init__(*args, **kw)
2015 month_style = (self.month_style or DateConverter.month_style).lower()
2016 accept_day = bool(self.accept_day)
2017 self.accept_day = self.accept_day
2018 if month_style in ('mdy',
2019 'md', 'mm/dd/yyyy', 'mm/dd', 'us', 'american'):
2021 elif month_style in ('dmy',
2022 'dm', 'dd/mm/yyyy', 'dd/mm', 'euro', 'european'):
2024 elif month_style in ('ymd',
2025 'ym', 'yyyy/mm/dd', 'yyyy/mm', 'iso', 'china', 'chinese'):
2028 raise TypeError('Bad month_style: %r' % month_style)
2029 self.month_style = month_style
2030 separator = self.separator
2031 if not separator or separator == 'auto':
2032 separator = dict(mdy='/', dmy='.', ymd='-')[month_style]
2033 elif separator not in ('-', '.', '/', '\\'):
2034 raise TypeError('Bad separator: %r' % separator)
2035 self.separator = separator
2036 self.format = separator.join(self._formats[part]
2037 for part in month_style if part != 'd' or accept_day)
2038 self.human_format = separator.join(self._human_formats[part]
2039 for part in month_style if part != 'd' or accept_day)
2041 def _convert_to_python(self, value, state):
2042 self.assert_string(value, state)
2043 month_style = self.month_style
2044 if not self.accept_day:
2045 month_style = 'ym' if month_style == 'ymd' else 'my'
2046 match = self._date_re[month_style].search(value)
2049 self.message('badFormat', state,
2050 format=self.human_format), value, state)
2051 groups = match.groups()
2053 if month_style == 'mdy':
2054 month, day, year = groups
2055 elif month_style == 'dmy':
2056 day, month, year = groups
2058 year, month, day = groups
2060 if not 1 <= day <= 31:
2061 raise Invalid(self.message('invalidDay', state), value, state)
2064 if month_style == 'my':
2065 month, year = groups
2067 year, month = groups
2068 month = self.make_month(month, state)
2069 if not 1 <= month <= 12:
2070 raise Invalid(self.message('monthRange', state), value, state)
2071 if self._monthDays[month] < day:
2073 self.message('dayRange', state,
2074 days=self._monthDays[month]), value, state)
2075 year = self.make_year(year, state)
2076 dt_mod = import_datetime(self.datetime_module)
2078 return datetime_makedate(dt_mod, year, month, day)
2079 except ValueError as v:
2081 self.message('invalidDate', state,
2082 exception=str(v)), value, state)
2084 def make_month(self, value, state):
2089 return self._month_names[value.lower().strip()]
2092 self.message('unknownMonthName', state,
2093 month=value), value, state)
2095 def make_year(self, year, state):
2099 raise Invalid(self.message('invalidYear', state), year, state)
2102 elif 50 <= year < 100:
2104 if 20 < year < 50 or 99 < year < 1900:
2105 raise Invalid(self.message('fourDigitYear', state), year, state)
2108 def _convert_from_python(self, value, state):
2109 if self.if_empty is not NoDefault and not value:
2111 return value.strftime(self.format)
2114 class TimeConverter(FancyValidator):
2116 Converts times in the format HH:MM:SSampm to (h, m, s).
2117 Seconds are optional.
2119 For ampm, set use_ampm = True. For seconds, use_seconds = True.
2120 Use 'optional' for either of these to make them optional.
2124 >>> tim = TimeConverter()
2125 >>> tim.to_python('8:30')
2127 >>> tim.to_python('20:30')
2129 >>> tim.to_python('30:00')
2130 Traceback (most recent call last):
2132 Invalid: You must enter an hour in the range 0-23
2133 >>> tim.to_python('13:00pm')
2134 Traceback (most recent call last):
2136 Invalid: You must enter an hour in the range 1-12
2137 >>> tim.to_python('12:-1')
2138 Traceback (most recent call last):
2140 Invalid: You must enter a minute in the range 0-59
2141 >>> tim.to_python('12:02pm')
2143 >>> tim.to_python('12:02am')
2145 >>> tim.to_python('1:00PM')
2147 >>> tim.from_python((13, 0))
2149 >>> tim2 = tim(use_ampm=True, use_seconds=False)
2150 >>> tim2.from_python((13, 0))
2152 >>> tim2.from_python((0, 0))
2154 >>> tim2.from_python((12, 0))
2157 Examples with ``datetime.time``::
2159 >>> v = TimeConverter(use_datetime=True)
2160 >>> a = v.to_python('18:00')
2162 datetime.time(18, 0)
2163 >>> b = v.to_python('30:00')
2164 Traceback (most recent call last):
2166 Invalid: You must enter an hour in the range 0-23
2167 >>> v2 = TimeConverter(prefer_ampm=True, use_datetime=True)
2168 >>> v2.from_python(a)
2170 >>> v3 = TimeConverter(prefer_ampm=True,
2171 ... use_seconds=False, use_datetime=True)
2172 >>> a = v3.to_python('18:00')
2174 datetime.time(18, 0)
2175 >>> v3.from_python(a)
2177 >>> a = v3.to_python('18:00:00')
2178 Traceback (most recent call last):
2180 Invalid: You may not enter seconds
2183 use_ampm = 'optional'
2185 use_seconds = 'optional'
2186 use_datetime = False
2187 # This can be set to make it prefer mxDateTime:
2188 datetime_module = None
2191 noAMPM=_('You must indicate AM or PM'),
2192 tooManyColon=_('There are too many :\'s'),
2193 noSeconds=_('You may not enter seconds'),
2194 secondsRequired=_('You must enter seconds'),
2195 minutesRequired=_('You must enter minutes (after a :)'),
2196 badNumber=_('The %(part)s value you gave is not a number: %(number)r'),
2197 badHour=_('You must enter an hour in the range %(range)s'),
2198 badMinute=_('You must enter a minute in the range 0-59'),
2199 badSecond=_('You must enter a second in the range 0-59'))
2201 def _convert_to_python(self, value, state):
2202 result = self._to_python_tuple(value, state)
2203 if self.use_datetime:
2204 dt_mod = import_datetime(self.datetime_module)
2205 time_class = datetime_time(dt_mod)
2206 return time_class(*result)
2210 def _to_python_tuple(self, value, state):
2211 time = value.strip()
2212 explicit_ampm = False
2214 last_two = time[-2:].lower()
2215 if last_two not in ('am', 'pm'):
2216 if self.use_ampm != 'optional':
2217 raise Invalid(self.message('noAMPM', state), value, state)
2220 explicit_ampm = True
2221 offset = 12 if last_two == 'pm' else 0
2225 parts = time.split(':', 3)
2227 raise Invalid(self.message('tooManyColon', state), value, state)
2228 if len(parts) == 3 and not self.use_seconds:
2229 raise Invalid(self.message('noSeconds', state), value, state)
2231 and self.use_seconds and self.use_seconds != 'optional'):
2232 raise Invalid(self.message('secondsRequired', state), value, state)
2234 raise Invalid(self.message('minutesRequired', state), value, state)
2236 hour = int(parts[0])
2239 self.message('badNumber', state,
2240 number=parts[0], part='hour'), value, state)
2242 if not 1 <= hour <= 12:
2244 self.message('badHour', state,
2245 number=hour, range='1-12'), value, state)
2246 if hour == 12 and offset == 12:
2249 elif hour == 12 and offset == 0:
2255 if not 0 <= hour < 24:
2257 self.message('badHour', state,
2258 number=hour, range='0-23'), value, state)
2260 minute = int(parts[1])
2263 self.message('badNumber', state,
2264 number=parts[1], part='minute'), value, state)
2265 if not 0 <= minute < 60:
2267 self.message('badMinute', state, number=minute),
2271 second = int(parts[2])
2274 self.message('badNumber', state,
2275 number=parts[2], part='second'), value, state)
2276 if not 0 <= second < 60:
2278 self.message('badSecond', state, number=second),
2283 return (hour, minute)
2285 return (hour, minute, second)
2287 def _convert_from_python(self, value, state):
2288 if isinstance(value, str):
2290 if hasattr(value, 'hour'):
2291 hour, minute = value.hour, value.minute
2292 second = value.second
2293 elif len(value) == 3:
2294 hour, minute, second = value
2295 elif len(value) == 2:
2296 hour, minute = value
2299 if (self.use_ampm == 'optional' and self.prefer_ampm) or (
2300 self.use_ampm and self.use_ampm != 'optional'):
2309 if self.use_seconds:
2310 return '%i:%02i:%02i%s' % (hour, minute, second, ampm)
2312 return '%i:%02i%s' % (hour, minute, ampm)
2315 def PostalCode(*kw, **kwargs):
2316 deprecation_warning("please use formencode.national.USPostalCode")
2317 from formencode.national import USPostalCode
2318 return USPostalCode(*kw, **kwargs)
2321 class StripField(FancyValidator):
2323 Take a field from a dictionary, removing the key from the dictionary.
2325 ``name`` is the key. The field value and a new copy of the dictionary
2326 with that field removed are returned.
2328 >>> StripField('test').to_python({'a': 1, 'test': 2})
2330 >>> StripField('test').to_python({})
2331 Traceback (most recent call last):
2333 Invalid: The name 'test' is missing
2337 __unpackargs__ = ('name',)
2340 missing=_('The name %(name)s is missing'))
2342 def _convert_to_python(self, valueDict, state):
2343 v = valueDict.copy()
2345 field = v.pop(self.name)
2348 self.message('missing', state, name=repr(self.name)),
2352 def is_empty(self, value):
2353 # empty dictionaries don't really apply here
2357 class StringBool(FancyValidator): # originally from TurboGears 1
2359 Converts a string to a boolean.
2361 Values like 'true' and 'false' are considered True and False,
2362 respectively; anything in ``true_values`` is true, anything in
2363 ``false_values`` is false, case-insensitive). The first item of
2364 those lists is considered the preferred form.
2368 >>> s = StringBool()
2369 >>> s.to_python('yes'), s.to_python('no')
2371 >>> s.to_python(1), s.to_python('N')
2373 >>> s.to_python('ye')
2374 Traceback (most recent call last):
2376 Invalid: Value should be 'true' or 'false'
2379 true_values = ['true', 't', 'yes', 'y', 'on', '1']
2380 false_values = ['false', 'f', 'no', 'n', 'off', '0']
2383 string=_('Value should be %(true)r or %(false)r'))
2385 def _convert_to_python(self, value, state):
2386 if isinstance(value, str):
2387 value = value.strip().lower()
2388 if value in self.true_values:
2390 if not value or value in self.false_values:
2393 self.message('string', state,
2394 true=self.true_values[0], false=self.false_values[0]),
2398 def _convert_from_python(self, value, state):
2399 return (self.true_values if value else self.false_values)[0]
2402 StringBoolean = StringBool
2405 class SignedString(FancyValidator):
2407 Encodes a string into a signed string, and base64 encodes both the
2408 signature string and a random nonce.
2410 It is up to you to provide a secret, and to keep the secret handy
2415 malformed=_('Value does not contain a signature'),
2416 badsig=_('Signature is not correct'))
2421 def _convert_to_python(self, value, state):
2424 from hashlib import sha1
2425 assert self.secret is not None, "You must give a secret"
2426 parts = value.split(None, 1)
2427 if not parts or len(parts) == 1:
2428 raise Invalid(self.message('malformed', state), value, state)
2430 sig = sig.decode('base64')
2431 rest = rest.decode('base64')
2432 nonce = rest[:self.nonce_length]
2433 rest = rest[self.nonce_length:]
2434 expected = sha1(str(self.secret) + nonce + rest).digest()
2436 raise Invalid(self.message('badsig', state), value, state)
2439 def _convert_from_python(self, value, state):
2442 from hashlib import sha1
2443 nonce = self.make_nonce()
2445 digest = sha1(self.secret + nonce + value).digest()
2446 return self.encode(digest) + ' ' + self.encode(nonce + value)
2448 def encode(self, value):
2449 return value.encode('base64').strip().replace('\n', '')
2451 def make_nonce(self):
2455 return ''.join(chr(random.randrange(256))
2456 for _i in range(self.nonce_length))
2459 class IPAddress(FancyValidator):
2461 Formencode validator to check whether a string is a correct IP address.
2465 >>> ip = IPAddress()
2466 >>> ip.to_python('127.0.0.1')
2468 >>> ip.to_python('299.0.0.1')
2469 Traceback (most recent call last):
2471 Invalid: The octets must be within the range of 0-255 (not '299')
2472 >>> ip.to_python('192.168.0.1/1')
2473 Traceback (most recent call last):
2475 Invalid: Please enter a valid IP address (a.b.c.d)
2476 >>> ip.to_python('asdf')
2477 Traceback (most recent call last):
2479 Invalid: Please enter a valid IP address (a.b.c.d)
2483 badFormat=_('Please enter a valid IP address (a.b.c.d)'),
2484 leadingZeros=_('The octets must not have leading zeros'),
2485 illegalOctets=_('The octets must be within the range of 0-255'
2486 ' (not %(octet)r)'))
2488 leading_zeros = False
2490 def _validate_python(self, value, state=None):
2494 octets = value.split('.', 5)
2496 if len(octets) != 4:
2499 for octet in octets:
2500 if octet.startswith('0') and octet != '0':
2501 if not self.leading_zeros:
2503 self.message('leadingZeros', state), value, state)
2504 # strip zeros so this won't be an octal number
2505 octet = octet.lstrip('0')
2506 if not 0 <= int(octet) < 256:
2508 self.message('illegalOctets', state, octet=octet),
2510 # Splitting faild: wrong syntax
2512 raise Invalid(self.message('badFormat', state), value, state)
2515 class CIDR(IPAddress):
2517 Formencode validator to check whether a string is in correct CIDR
2518 notation (IP address, or IP address plus /mask).
2523 >>> cidr.to_python('127.0.0.1')
2525 >>> cidr.to_python('299.0.0.1')
2526 Traceback (most recent call last):
2528 Invalid: The octets must be within the range of 0-255 (not '299')
2529 >>> cidr.to_python('192.168.0.1/1')
2530 Traceback (most recent call last):
2532 Invalid: The network size (bits) must be within the range of 8-32 (not '1')
2533 >>> cidr.to_python('asdf')
2534 Traceback (most recent call last):
2536 Invalid: Please enter a valid IP address (a.b.c.d) or IP network (a.b.c.d/e)
2539 messages = dict(IPAddress._messages,
2540 badFormat=_('Please enter a valid IP address (a.b.c.d)'
2541 ' or IP network (a.b.c.d/e)'),
2542 illegalBits=_('The network size (bits) must be within the range'
2543 ' of 8-32 (not %(bits)r)'))
2545 def _validate_python(self, value, state):
2547 # Split into octets and bits
2548 if '/' in value: # a.b.c.d/e
2549 addr, bits = value.split('/')
2551 addr, bits = value, 32
2552 # Use IPAddress validator to validate the IP part
2553 IPAddress._validate_python(self, addr, state)
2554 # Bits (netmask) correct?
2555 if not 8 <= int(bits) <= 32:
2557 self.message('illegalBits', state, bits=bits),
2559 # Splitting faild: wrong syntax
2561 raise Invalid(self.message('badFormat', state), value, state)
2564 class MACAddress(FancyValidator):
2566 Formencode validator to check whether a string is a correct hardware
2571 >>> mac = MACAddress()
2572 >>> mac.to_python('aa:bb:cc:dd:ee:ff')
2574 >>> mac.to_python('aa:bb:cc:dd:ee:ff:e')
2575 Traceback (most recent call last):
2577 Invalid: A MAC address must contain 12 digits and A-F; the value you gave has 13 characters
2578 >>> mac.to_python('aa:bb:cc:dd:ee:fx')
2579 Traceback (most recent call last):
2581 Invalid: MAC addresses may only contain 0-9 and A-F (and optionally :), not 'x'
2582 >>> MACAddress(add_colons=True).to_python('aabbccddeeff')
2587 valid_characters = '0123456789abcdefABCDEF'
2591 badLength=_('A MAC address must contain 12 digits and A-F;'
2592 ' the value you gave has %(length)s characters'),
2593 badCharacter=_('MAC addresses may only contain 0-9 and A-F'
2594 ' (and optionally :), not %(char)r'))
2596 def _convert_to_python(self, value, state):
2597 address = value.replace(':', '').lower() # remove colons
2598 if len(address) != 12:
2600 self.message('badLength', state,
2601 length=len(address)), address, state)
2602 for char in address:
2603 if char not in self.valid_characters:
2605 self.message('badCharacter', state,
2606 char=char), address, state)
2608 address = '%s:%s:%s:%s:%s:%s' % (
2609 address[0:2], address[2:4], address[4:6],
2610 address[6:8], address[8:10], address[10:12])
2613 _convert_from_python = _convert_to_python
2616 class FormValidator(FancyValidator):
2618 A FormValidator is something that can be chained with a Schema.
2620 Unlike normal chaining the FormValidator can validate forms that
2621 aren't entirely valid.
2623 The important method is .validate(), of course. It gets passed a
2624 dictionary of the (processed) values from the form. If you have
2625 .validate_partial_form set to True, then it will get the incomplete
2626 values as well -- check with the "in" operator if the form was able
2627 to process any particular field.
2629 Anyway, .validate() should return a string or a dictionary. If a
2630 string, it's an error message that applies to the whole form. If
2631 not, then it should be a dictionary of fieldName: errorMessage.
2632 The special key "form" is the error message for the form as a whole
2633 (i.e., a string is equivalent to {"form": string}).
2635 Returns None on no errors.
2638 validate_partial_form = False
2640 validate_partial_python = None
2641 validate_partial_other = None
2643 def is_empty(self, value):
2646 def field_is_empty(self, value):
2647 return is_empty(value)
2650 class RequireIfMissing(FormValidator):
2652 Require one field based on another field being present or missing.
2654 This validator is applied to a form, not an individual field (usually
2655 using a Schema's ``pre_validators`` or ``chained_validators``) and is
2656 available under both names ``RequireIfMissing`` and ``RequireIfPresent``.
2658 If you provide a ``missing`` value (a string key name) then
2659 if that field is missing the field must be entered.
2660 This gives you an either/or situation.
2662 If you provide a ``present`` value (another string key name) then
2663 if that field is present, the required field must also be present.
2667 >>> from formencode import validators
2668 >>> v = validators.RequireIfPresent('phone_type', present='phone')
2669 >>> v.to_python(dict(phone_type='', phone='510 420 4577'))
2670 Traceback (most recent call last):
2672 Invalid: You must give a value for phone_type
2673 >>> v.to_python(dict(phone=''))
2676 Note that if you have a validator on the optionally-required
2677 field, you should probably use ``if_missing=None``. This way you
2678 won't get an error from the Schema about a missing value. For example::
2680 class PhoneInput(Schema):
2681 phone = PhoneNumber()
2682 phone_type = String(if_missing=None)
2683 chained_validators = [RequireIfPresent('phone_type', present='phone')]
2686 # Field that potentially is required:
2688 # If this field is missing, then it is required:
2690 # If this field is present, then it is required:
2693 __unpackargs__ = ('required',)
2695 def _convert_to_python(self, value_dict, state):
2696 is_empty = self.field_is_empty
2697 if is_empty(value_dict.get(self.required)) and (
2698 (self.missing and is_empty(value_dict.get(self.missing))) or
2699 (self.present and not is_empty(value_dict.get(self.present)))):
2701 _('You must give a value for %s') % self.required,
2703 error_dict={self.required:
2704 Invalid(self.message('empty', state),
2705 value_dict.get(self.required), state)})
2708 RequireIfPresent = RequireIfMissing
2710 class RequireIfMatching(FormValidator):
2712 Require a list of fields based on the value of another field.
2714 This validator is applied to a form, not an individual field (usually
2715 using a Schema's ``pre_validators`` or ``chained_validators``).
2717 You provide a field name, an expected value and a list of required fields
2718 (a list of string key names). If the value of the field, if present,
2719 matches the value of ``expected_value``, then the validator will raise an
2720 ``Invalid`` exception for every field in ``required_fields`` that is
2725 >>> from formencode import validators
2726 >>> v = validators.RequireIfMatching('phone_type', expected_value='mobile', required_fields=['mobile'])
2727 >>> v.to_python(dict(phone_type='mobile'))
2728 Traceback (most recent call last):
2730 formencode.api.Invalid: You must give a value for mobile
2731 >>> v.to_python(dict(phone_type='someothervalue'))
2732 {'phone_type': 'someothervalue'}
2735 # Field that we will check for its value:
2737 # Value that the field shall have
2738 expected_value = None
2739 # If this field is present, then these fields are required:
2740 required_fields = []
2742 __unpackargs__ = ('field', 'expected_value')
2744 def _convert_to_python(self, value_dict, state):
2745 is_empty = self.field_is_empty
2747 if self.field in value_dict and value_dict.get(self.field) == self.expected_value:
2748 for required_field in self.required_fields:
2749 if required_field not in value_dict or is_empty(value_dict.get(required_field)):
2751 _('You must give a value for %s') % required_field,
2753 error_dict={required_field:
2754 Invalid(self.message('empty', state),
2755 value_dict.get(required_field), state)})
2758 class FieldsMatch(FormValidator):
2760 Tests that the given fields match, i.e., are identical. Useful
2761 for password+confirmation fields. Pass the list of field names in
2766 >>> f = FieldsMatch('pass', 'conf')
2767 >>> sorted(f.to_python({'pass': 'xx', 'conf': 'xx'}).items())
2768 [('conf', 'xx'), ('pass', 'xx')]
2769 >>> f.to_python({'pass': 'xx', 'conf': 'yy'})
2770 Traceback (most recent call last):
2772 Invalid: conf: Fields do not match
2777 validate_partial_form = True
2779 __unpackargs__ = ('*', 'field_names')
2782 invalid=_('Fields do not match (should be %(match)s)'),
2783 invalidNoMatch=_('Fields do not match'),
2784 notDict=_('Fields should be a dictionary'))
2786 def __init__(self, *args, **kw):
2787 super(FieldsMatch, self).__init__(*args, **kw)
2788 if len(self.field_names) < 2:
2789 raise TypeError('FieldsMatch() requires at least two field names')
2791 def validate_partial(self, field_dict, state):
2792 for name in self.field_names:
2793 if name not in field_dict:
2795 self._validate_python(field_dict, state)
2797 def _validate_python(self, field_dict, state):
2799 ref = field_dict[self.field_names[0]]
2801 # Generally because field_dict isn't a dict
2802 raise Invalid(self.message('notDict', state), field_dict, state)
2806 for name in self.field_names[1:]:
2807 if field_dict.get(name, '') != ref:
2809 errors[name] = self.message('invalid', state,
2812 errors[name] = self.message('invalidNoMatch', state)
2814 error_list = sorted(errors.items())
2815 error_message = '<br>\n'.join(
2816 '%s: %s' % (name, value) for name, value in error_list)
2817 raise Invalid(error_message, field_dict, state, error_dict=errors)
2820 class CreditCardValidator(FormValidator):
2822 Checks that credit card numbers are valid (if not real).
2824 You pass in the name of the field that has the credit card
2825 type and the field with the credit card number. The credit
2826 card type should be one of "visa", "mastercard", "amex",
2827 "dinersclub", "discover", "jcb".
2829 You must check the expiration date yourself (there is no
2830 relation between CC number/types and expiration dates).
2834 >>> cc = CreditCardValidator()
2835 >>> sorted(cc.to_python({'ccType': 'visa', 'ccNumber': '4111111111111111'}).items())
2836 [('ccNumber', '4111111111111111'), ('ccType', 'visa')]
2837 >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111111'})
2838 Traceback (most recent call last):
2840 Invalid: ccNumber: You did not enter a valid number of digits
2841 >>> cc.to_python({'ccType': 'visa', 'ccNumber': '411111111111112'})
2842 Traceback (most recent call last):
2844 Invalid: ccNumber: You did not enter a valid number of digits
2845 >>> cc().to_python({})
2846 Traceback (most recent call last):
2848 Invalid: The field ccType is missing
2851 validate_partial_form = True
2853 cc_type_field = 'ccType'
2854 cc_number_field = 'ccNumber'
2856 __unpackargs__ = ('cc_type_field', 'cc_number_field')
2859 notANumber=_('Please enter only the number, no other characters'),
2860 badLength=_('You did not enter a valid number of digits'),
2861 invalidNumber=_('That number is not valid'),
2862 missing_key=_('The field %(key)s is missing'))
2864 def validate_partial(self, field_dict, state):
2865 if not field_dict.get(self.cc_type_field, None) \
2866 or not field_dict.get(self.cc_number_field, None):
2868 self._validate_python(field_dict, state)
2870 def _validate_python(self, field_dict, state):
2871 errors = self._validateReturn(field_dict, state)
2873 error_list = sorted(errors.items())
2875 '<br>\n'.join('%s: %s' % (name, value)
2876 for name, value in error_list),
2877 field_dict, state, error_dict=errors)
2879 def _validateReturn(self, field_dict, state):
2880 for field in self.cc_type_field, self.cc_number_field:
2881 if field not in field_dict:
2883 self.message('missing_key', state, key=field),
2885 ccType = field_dict[self.cc_type_field].lower().strip()
2886 number = field_dict[self.cc_number_field].strip()
2887 number = number.replace(' ', '')
2888 number = number.replace('-', '')
2892 return {self.cc_number_field: self.message('notANumber', state)}
2893 assert ccType in self._cardInfo, (
2894 "I can't validate that type of credit card")
2897 for prefix, length in self._cardInfo[ccType]:
2898 if len(number) == length:
2900 if number.startswith(prefix):
2904 return {self.cc_number_field: self.message('badLength', state)}
2906 return {self.cc_number_field: self.message('invalidNumber', state)}
2907 if not self._validateMod10(number):
2908 return {self.cc_number_field: self.message('invalidNumber', state)}
2911 def _validateMod10(self, s):
2912 """Check string with the mod 10 algorithm (aka "Luhn formula")."""
2913 checksum, factor = 0, 1
2914 for c in reversed(s):
2915 for c in str(factor * int(c)):
2918 return checksum % 10 == 0
2923 "mastercard": [('51', 16),
2928 "discover": [('6011', 16)],
2929 "amex": [('34', 15),
2931 "dinersclub": [('300', 14),
2945 class CreditCardExpires(FormValidator):
2947 Checks that credit card expiration date is valid relative to
2950 You pass in the name of the field that has the credit card
2951 expiration month and the field with the credit card expiration
2956 >>> ed = CreditCardExpires()
2957 >>> sorted(ed.to_python({'ccExpiresMonth': '11', 'ccExpiresYear': '2250'}).items())
2958 [('ccExpiresMonth', '11'), ('ccExpiresYear', '2250')]
2959 >>> ed.to_python({'ccExpiresMonth': '10', 'ccExpiresYear': '2005'})
2960 Traceback (most recent call last):
2962 Invalid: ccExpiresMonth: Invalid Expiration Date<br>
2963 ccExpiresYear: Invalid Expiration Date
2966 validate_partial_form = True
2968 cc_expires_month_field = 'ccExpiresMonth'
2969 cc_expires_year_field = 'ccExpiresYear'
2971 __unpackargs__ = ('cc_expires_month_field', 'cc_expires_year_field')
2973 datetime_module = None
2976 notANumber=_('Please enter numbers only for month and year'),
2977 invalidNumber=_('Invalid Expiration Date'))
2979 def validate_partial(self, field_dict, state):
2980 if not field_dict.get(self.cc_expires_month_field, None) \
2981 or not field_dict.get(self.cc_expires_year_field, None):
2983 self._validate_python(field_dict, state)
2985 def _validate_python(self, field_dict, state):
2986 errors = self._validateReturn(field_dict, state)
2988 error_list = sorted(errors.items())
2990 '<br>\n'.join('%s: %s' % (name, value)
2991 for name, value in error_list),
2992 field_dict, state, error_dict=errors)
2994 def _validateReturn(self, field_dict, state):
2995 ccExpiresMonth = str(field_dict[self.cc_expires_month_field]).strip()
2996 ccExpiresYear = str(field_dict[self.cc_expires_year_field]).strip()
2999 ccExpiresMonth = int(ccExpiresMonth)
3000 ccExpiresYear = int(ccExpiresYear)
3001 dt_mod = import_datetime(self.datetime_module)
3002 now = datetime_now(dt_mod)
3003 today = datetime_makedate(dt_mod, now.year, now.month, now.day)
3004 next_month = ccExpiresMonth % 12 + 1
3005 next_month_year = ccExpiresYear
3007 next_month_year += 1
3008 expires_date = datetime_makedate(
3009 dt_mod, next_month_year, next_month, 1)
3010 assert expires_date > today
3012 return {self.cc_expires_month_field:
3013 self.message('notANumber', state),
3014 self.cc_expires_year_field:
3015 self.message('notANumber', state)}
3016 except AssertionError:
3017 return {self.cc_expires_month_field:
3018 self.message('invalidNumber', state),
3019 self.cc_expires_year_field:
3020 self.message('invalidNumber', state)}
3023 class CreditCardSecurityCode(FormValidator):
3025 Checks that credit card security code has the correct number
3026 of digits for the given credit card type.
3028 You pass in the name of the field that has the credit card
3029 type and the field with the credit card security code.
3033 >>> code = CreditCardSecurityCode()
3034 >>> sorted(code.to_python({'ccType': 'visa', 'ccCode': '111'}).items())
3035 [('ccCode', '111'), ('ccType', 'visa')]
3036 >>> code.to_python({'ccType': 'visa', 'ccCode': '1111'})
3037 Traceback (most recent call last):
3039 Invalid: ccCode: Invalid credit card security code length
3042 validate_partial_form = True
3044 cc_type_field = 'ccType'
3045 cc_code_field = 'ccCode'
3047 __unpackargs__ = ('cc_type_field', 'cc_code_field')
3050 notANumber=_('Please enter numbers only for credit card security code'),
3051 badLength=_('Invalid credit card security code length'))
3053 def validate_partial(self, field_dict, state):
3054 if (not field_dict.get(self.cc_type_field, None)
3055 or not field_dict.get(self.cc_code_field, None)):
3057 self._validate_python(field_dict, state)
3059 def _validate_python(self, field_dict, state):
3060 errors = self._validateReturn(field_dict, state)
3062 error_list = sorted(errors.items())
3064 '<br>\n'.join('%s: %s' % (name, value)
3065 for name, value in error_list),
3066 field_dict, state, error_dict=errors)
3068 def _validateReturn(self, field_dict, state):
3069 ccType = str(field_dict[self.cc_type_field]).strip()
3070 ccCode = str(field_dict[self.cc_code_field]).strip()
3074 return {self.cc_code_field: self.message('notANumber', state)}
3075 length = self._cardInfo[ccType]
3076 if len(ccCode) != length:
3077 return {self.cc_code_field: self.message('badLength', state)}
3079 # key = credit card type, value = length of security code
3080 _cardInfo = dict(visa=3, mastercard=3, discover=3, amex=4)
3084 """Return the names of all validators in this module."""
3085 return [name for name, value in globals().items()
3086 if isinstance(value, type) and issubclass(value, Validator)]
3088 __all__ = ['Invalid'] + validators()