class CanadianPostalCode(Regex): """ Canadian Postal codes. :: >>> CanadianPostalCode.to_python('V3H 1Z7') 'V3H 1Z7' >>> CanadianPostalCode.to_python('v3h1z7') 'V3H 1Z7' >>> CanadianPostalCode.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a zip code (LnL nLn) """ regex = re.compile(r'^([a-zA-Z]\d[a-zA-Z])\s?(\d[a-zA-Z]\d)$') strip = True messages = dict( invalid=_('Please enter a zip code (%s)') % _('LnL nLn')) def _to_python(self, value, state): self.assert_string(value, state) match = self.regex.search(value) if not match: raise Invalid( self.message('invalid', state), value, state) return '%s %s' % (match.group(1).upper(), match.group(2).upper())
class USStateProvince(FancyValidator): """ Valid state or province code (two-letter). Well, for now I don't know the province codes, but it does state codes. Give your own `states` list to validate other state-like codes; give `extra_states` to add values without losing the current state values. :: >>> s = USStateProvince('XX') >>> s.to_python('IL') 'IL' >>> s.to_python('XX') 'XX' >>> s.to_python('xx') 'XX' >>> s.to_python('YY') Traceback (most recent call last): ... Invalid: That is not a valid state code """ states = ['AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE', 'FL', 'GA', 'HI', 'IA', 'ID', 'IN', 'IL', 'KS', 'KY', 'LA', 'MA', 'MD', 'ME', 'MI', 'MN', 'MO', 'MS', 'MT', 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH', 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT', 'VA', 'VT', 'WA', 'WI', 'WV', 'WY'] extra_states = [] __unpackargs__ = ('extra_states',) messages = { 'empty': _('Please enter a state code'), 'wrongLength': _('Please enter a state code with TWO letters'), 'invalid': _('That is not a valid state code'), } def validate_python(self, value, state): value = str(value).strip().upper() if not value: raise Invalid( self.message('empty', state), value, state) if not value or len(value) != 2: raise Invalid( self.message('wrongLength', state), value, state) if value not in self.states \ and not (self.extra_states and value in self.extra_states): raise Invalid( self.message('invalid', state), value, state) def _to_python(self, value, state): return str(value).strip().upper()
class ArgentinianPostalCode(Regex): """ Argentinian Postal codes. :: >>> ArgentinianPostalCode.to_python('C1070AAM') 'C1070AAM' >>> ArgentinianPostalCode.to_python('c 1070 aam') 'C1070AAM' >>> ArgentinianPostalCode.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a zip code (LnnnnLLL) """ regex = re.compile(r'^([a-zA-Z]{1})\s*(\d{4})\s*([a-zA-Z]{3})$') strip = True messages = dict( invalid=_('Please enter a zip code (%s)') % _('LnnnnLLL')) def _to_python(self, value, state): self.assert_string(value, state) match = self.regex.search(value) if not match: raise Invalid( self.message('invalid', state), value, state) return '%s%s%s' % (match.group(1).upper(), match.group(2), match.group(3).upper())
class UKPostalCode(Regex): """ UK Postal codes. Please see BS 7666. :: >>> UKPostalCode.to_python('BFPO 3') 'BFPO 3' >>> UKPostalCode.to_python('LE11 3GR') 'LE11 3GR' >>> UKPostalCode.to_python('l1a 3gr') 'L1A 3GR' >>> UKPostalCode.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a valid postal code (for format see BS 7666) """ regex = re.compile(r'^((ASCN|BBND|BIQQ|FIQQ|PCRN|SIQQ|STHL|TDCU|TKCA) 1ZZ|BFPO (c\/o )?[1-9]{1,4}|GIR 0AA|[A-PR-UWYZ]([0-9]{1,2}|([A-HK-Y][0-9]|[A-HK-Y][0-9]([0-9]|[ABEHMNPRV-Y]))|[0-9][A-HJKS-UW]) [0-9][ABD-HJLNP-UW-Z]{2})$', re.I) strip = True messages = dict( invalid=_('Please enter a valid postal code (for format see BS 7666)')) def _to_python(self, value, state): self.assert_string(value, state) match = self.regex.search(value) if not match: raise Invalid( self.message('invalid', state), value, state) return match.group(1).upper()
class LanguageValidator(FancyValidator): """ Converts a given language into its ISO 639 alpha 2 code, if there is any. Returns the language's full name in the reverse. Warning: ISO 639 neither differentiates between languages such as Cantonese and Mandarin nor does it contain all spoken languages. E.g., Lechitic languages are missing. Warning: ISO 639 is a smaller subset of ISO 639-2 @param key_ok accept the language's code instead of its name for input defaults to True :: >>> l = LanguageValidator() >>> l.to_python('German') 'de' >>> l.to_python('Chinese') 'zh' >>> l.to_python('Klingonian') Traceback (most recent call last): ... Invalid: That language is not listed in ISO 639 >>> l.from_python('de') 'German' >>> l.from_python('zh') 'Chinese' """ key_ok = True messages = { 'valueNotFound': _("That language is not listed in ISO 639"), } def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) if no_country: warnings.warn(no_country, Warning, 2) def _to_python(self, value, state): upval = value.upper() if self.key_ok: try: c = get_language(value) return value except: pass for k, v in get_languages(): if v.upper() == upval: return k raise Invalid(self.message('valueNotFound', state), value, state) def _from_python(self, value, state): try: return get_language(value.lower()) except KeyError: return value
class CountryValidator(FancyValidator): """ Will convert a country's name into its ISO-3166 abbreviation for unified storage in databases etc. and return a localized country name in the reverse step. @See http://www.iso.org/iso/country_codes/iso_3166_code_lists.htm :: >>> CountryValidator.to_python('Germany') 'DE' >>> CountryValidator.to_python('Finland') 'FI' >>> CountryValidator.to_python('UNITED STATES') 'US' >>> CountryValidator.to_python('Krakovia') Traceback (most recent call last): ... Invalid: That country is not listed in ISO 3166 >>> CountryValidator.from_python('DE') 'Germany' >>> CountryValidator.from_python('FI') 'Finland' """ key_ok = True messages = { 'valueNotFound': _("That country is not listed in ISO 3166"), } def __init__(self, *args, **kw): FancyValidator.__init__(self, *args, **kw) if no_country: warnings.warn(no_country, Warning, 2) def _to_python(self, value, state): upval = value.upper() if self.key_ok: try: c = get_country(upval) return upval except: pass for k, v in get_countries(): if v.upper() == upval: return k raise Invalid(self.message('valueNotFound', state), value, state) def _from_python(self, value, state): try: return get_country(value.upper()) except KeyError: return value
class USPhoneNumber(FancyValidator): """ Validates, and converts to ###-###-####, optionally with extension (as ext.##...). Only support US phone numbers. See InternationalPhoneNumber for support for that kind of phone number. :: >>> p = USPhoneNumber() >>> p.to_python('333-3333') Traceback (most recent call last): ... Invalid: Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####" >>> p.to_python('555-555-5555') '555-555-5555' >>> p.to_python('1-393-555-3939') '1-393-555-3939' >>> p.to_python('321.555.4949') '321.555.4949' >>> p.to_python('3335550000') '3335550000' """ # for emacs: " _phoneRE = re.compile(r'^\s*(?:1-)?(\d\d\d)[\- \.]?(\d\d\d)[\- \.]?(\d\d\d\d)(?:\s*ext\.?\s*(\d+))?\s*$', re.I) messages = { 'phoneFormat': _('Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####"'), } def _to_python(self, value, state): self.assert_string(value, state) match = self._phoneRE.search(value) if not match: raise Invalid( self.message('phoneFormat', state), value, state) return value def _from_python(self, value, state): self.assert_string(value, state) match = self._phoneRE.search(value) if not match: raise Invalid(self.message('phoneFormat', state), value, state) result = '%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) if match.group(4): result = result + " ext.%s" % match.group(4) return result
class InternationalPhoneNumber(FancyValidator): """ Validates, and converts phone numbers to +##-###-#######. Adapted from RFC 3966 @param default_cc country code for prepending if none is provided can be a paramerless callable :: >>> c = InternationalPhoneNumber(default_cc=lambda: 49) >>> c.to_python('0555/8114100') '+49-555-8114100' >>> p = InternationalPhoneNumber(default_cc=49) >>> p.to_python('333-3333') Traceback (most recent call last): ... Invalid: Please enter a number, with area code, in the form +##-###-#######. >>> p.to_python('0555/4860-300') '+49-555-4860-300' >>> p.to_python('0555-49924-51') '+49-555-49924-51' >>> p.to_python('0555 / 8114100') '+49-555-8114100' >>> p.to_python('0555/8114100') '+49-555-8114100' >>> p.to_python('0555 8114100') '+49-555-8114100' >>> p.to_python(' +49 (0)555 350 60 0') '+49-555-35060-0' >>> p.to_python('+49 555 350600') '+49-555-350600' >>> p.to_python('0049/ 555/ 871 82 96') '+49-555-87182-96' >>> p.to_python('0555-2 50-30') '+49-555-250-30' >>> p.to_python('0555 43-1200') '+49-555-43-1200' >>> p.to_python('(05 55)4 94 33 47') '+49-555-49433-47' >>> p.to_python('(00 48-555)2 31 72 41') '+48-555-23172-41' >>> p.to_python('+973-555431') '+973-555431' >>> p.to_python('1-393-555-3939') '+1-393-555-3939' >>> p.to_python('+43 (1) 55528/0') '+43-1-55528-0' >>> p.to_python('+43 5555 429 62-0') '+43-5555-42962-0' >>> p.to_python('00 218 55 33 50 317 321') '+218-55-3350317-321' >>> p.to_python('+218 (0)55-3636639/38') '+218-55-3636639-38' >>> p.to_python('032 555555 367') '+49-32-555555-367' >>> p.to_python('(+86) 555 3876693') '+86-555-3876693' """ strip = True # Use if there's a default country code you want to use: default_cc = None _mark_chars_re = re.compile(r"[_.!~*'/]") _preTransformations = [ (re.compile(r'^(\(?)(?:00\s*)(.+)$'), '%s+%s'), (re.compile(r'^\(\s*(\+?\d+)\s*(\d+)\s*\)(.+)$'), '(%s%s)%s'), (re.compile(r'^\((\+?[-\d]+)\)\s?(\d.+)$'), '%s-%s'), (re.compile(r'^(?:1-)(\d+.+)$'), '+1-%s'), (re.compile(r'^(\+\d+)\s+\(0\)\s*(\d+.+)$'), '%s-%s'), (re.compile(r'^([0+]\d+)[-\s](\d+)$'), '%s-%s'), (re.compile(r'^([0+]\d+)[-\s](\d+)[-\s](\d+)$'), '%s-%s-%s'), ] _ccIncluder = [ (re.compile(r'^\(?0([1-9]\d*)[-)](\d.*)$'), '+%d-%s-%s'), ] _postTransformations = [ (re.compile(r'^(\+\d+)[-\s]\(?(\d+)\)?[-\s](\d+.+)$'), '%s-%s-%s'), (re.compile(r'^(.+)\s(\d+)$'), '%s-%s'), ] _phoneIsSane = re.compile(r'^(\+[1-9]\d*)-([\d\-]+)$') messages = dict( phoneFormat=_('Please enter a number, with area code,' ' in the form +##-###-#######.')) def _perform_rex_transformation(self, value, transformations): for rex, trf in transformations: match = rex.search(value) if match: value = trf % match.groups() return value def _prepend_country_code(self, value, transformations, country_code): for rex, trf in transformations: match = rex.search(value) if match: return trf % ((country_code,)+match.groups()) return value def _to_python(self, value, state): self.assert_string(value, state) try: value = value.encode('ascii', 'replace') except: raise Invalid(self.message('phoneFormat', state), value, state) value = self._mark_chars_re.sub('-', value) for f, t in [(' ', ' '), ('--', '-'), (' - ', '-'), ('- ', '-'), (' -', '-')]: value = value.replace(f, t) value = self._perform_rex_transformation(value, self._preTransformations) if self.default_cc: if callable(self.default_cc): cc = self.default_cc() else: cc = self.default_cc value = self._prepend_country_code(value, self._ccIncluder, cc) value = self._perform_rex_transformation(value, self._postTransformations) value = value.replace(' ', '') # did we successfully transform that phone number? Thus, is it valid? if not self._phoneIsSane.search(value): raise Invalid(self.message('phoneFormat', state), value, state) return value
class PostalCodeInCountryFormat(FancyValidator): """ Makes sure the postal code is in the country's format by chosing postal code validator by provided country code. Does convert it into the preferred format, too. :: >>> fs = PostalCodeInCountryFormat('country', 'zip') >>> sorted(fs.to_python(dict(country='DE', zip='30167')).items()) [('country', 'DE'), ('zip', '30167')] >>> fs.to_python(dict(country='DE', zip='3008')) Traceback (most recent call last): ... Invalid: Given postal code does not match the country's format. >>> sorted(fs.to_python(dict(country='PL', zip='34343')).items()) [('country', 'PL'), ('zip', '34-343')] >>> fs = PostalCodeInCountryFormat('staat', 'plz') >>> sorted(fs.to_python(dict(staat='GB', plz='l1a 3gr')).items()) [('plz', 'L1A 3GR'), ('staat', 'GB')] """ country_field = 'country' zip_field = 'zip' __unpackargs__ = ('country_field', 'zip_field') messages = dict( badFormat=_("Given postal code does not match the country's format.")) _vd = { 'AR': ArgentinianPostalCode, 'AT': FourDigitsPostalCode, 'BE': FourDigitsPostalCode, 'BG': FourDigitsPostalCode, 'CA': CanadianPostalCode, 'CL': lambda: DelimitedDigitsPostalCode(7), 'CN': lambda: DelimitedDigitsPostalCode(6), 'CR': FourDigitsPostalCode, 'DE': GermanPostalCode, 'DK': FourDigitsPostalCode, 'DO': lambda: DelimitedDigitsPostalCode(5), 'ES': lambda: DelimitedDigitsPostalCode(5), 'FI': lambda: DelimitedDigitsPostalCode(5), 'FR': lambda: DelimitedDigitsPostalCode(5), 'GB': UKPostalCode, 'GF': lambda: DelimitedDigitsPostalCode(5), 'GR': lambda: DelimitedDigitsPostalCode([2, 3], ' '), 'HN': lambda: DelimitedDigitsPostalCode(5), 'HT': FourDigitsPostalCode, 'HU': FourDigitsPostalCode, 'IS': lambda: DelimitedDigitsPostalCode(3), 'IT': lambda: DelimitedDigitsPostalCode(5), 'JP': lambda: DelimitedDigitsPostalCode([3, 4], '-'), 'KR': lambda: DelimitedDigitsPostalCode([3, 3], '-'), 'LI': FourDigitsPostalCode, 'LU': FourDigitsPostalCode, 'MC': lambda: DelimitedDigitsPostalCode(5), 'NI': lambda: DelimitedDigitsPostalCode([3, 3, 1], '-'), 'NO': FourDigitsPostalCode, 'PL': PolishPostalCode, 'PT': lambda: DelimitedDigitsPostalCode([4, 3], '-'), 'PY': FourDigitsPostalCode, 'RO': lambda: DelimitedDigitsPostalCode(6), 'SE': lambda: DelimitedDigitsPostalCode([3, 2], ' '), 'SG': lambda: DelimitedDigitsPostalCode(6), 'US': USPostalCode, 'UY': lambda: DelimitedDigitsPostalCode(5), } def validate_python(self, fields_dict, state): if fields_dict[self.country_field] in self._vd: try: zip_validator = self._vd[fields_dict[self.country_field]]() fields_dict[self.zip_field] = zip_validator.to_python( fields_dict[self.zip_field]) except Invalid, e: message = self.message('badFormat', state) raise Invalid(message, fields_dict, state, error_dict={self.zip_field: e.msg, self.country_field: message})
def assembly_formatstring(self, partition_lengths, delimiter): if len(partition_lengths) == 1: return _('%d digits') % partition_lengths[0] else: return delimiter.join(['n'*l for l in partition_lengths])
class DelimitedDigitsPostalCode(Regex): """ Abstraction of common postal code formats, such as 55555, 55-555 etc. With constant amount of digits. By providing a single digit as partition you can obtain a trivial 'x digits' postal code validator. :: >>> german = DelimitedDigitsPostalCode(5) >>> german.to_python('55555') '55555' >>> german.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a zip code (5 digits) >>> polish = DelimitedDigitsPostalCode([2, 3], '-') >>> polish.to_python('55555') '55-555' >>> polish.to_python('55-555') '55-555' >>> polish.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a zip code (nn-nnn) >>> nicaragua = DelimitedDigitsPostalCode([3, 3, 1], '-') >>> nicaragua.to_python('5554443') '555-444-3' >>> nicaragua.to_python('555-4443') '555-444-3' >>> nicaragua.to_python('5555') Traceback (most recent call last): ... Invalid: Please enter a zip code (nnn-nnn-n) """ strip = True def assembly_formatstring(self, partition_lengths, delimiter): if len(partition_lengths) == 1: return _('%d digits') % partition_lengths[0] else: return delimiter.join(['n'*l for l in partition_lengths]) def assembly_regex(self, partition_lengths, delimiter): mg = [r'(\d{%d})' % l for l in partition_lengths] rd = r'\%s?' % delimiter return rd.join(mg) def __init__(self, partition_lengths, delimiter = None, *args, **kw): if type(partition_lengths) == type(1): partition_lengths = [partition_lengths] if not delimiter: delimiter = '' self.format = self.assembly_formatstring(partition_lengths, delimiter) self.regex = self.assembly_regex(partition_lengths, delimiter) (self.partition_lengths, self.delimiter) = (partition_lengths, delimiter) Regex.__init__(self, *args, **kw) messages = dict( invalid=_('Please enter a zip code (%(format)s)')) def _to_python(self, value, state): self.assert_string(value, state) match = self.regex.search(value) if not match: raise Invalid( self.message('invalid', state, format=self.format), value, state) return self.delimiter.join(match.groups())
except: tgformat = None if pycountry or tgformat: no_country = False else: import warnings no_country = ('Please easy_install pycountry or validators handling' ' country names and/or languages will not work.') ############################################################ ## country lists and functions ############################################################ country_additions = [ ('BY', _('Belarus')), ('ME', _('Montenegro')), ('AU', _('Tasmania')), ] fuzzy_countrynames = [ ('US', 'U.S.A'), ('US', 'USA'), ('GB', _('Britain')), ('GB', _('Great Britain')), ('CI', _('Cote de Ivoire')), ] if tgformat: def get_countries():
from turbogears.i18n import format as tgformat has_turbogears = True except: has_turbogears = False no_country = False if not (has_pycountry or has_turbogears): import warnings no_country = ('Please easy_install pycountry or validators handling ' 'country names and/or languages will not work.') ############################################################ ## country lists and functions ############################################################ country_additions = [ ('BY', _("Belarus")), ('ME', _("Montenegro")), ('AU', _("Tasmania")), ] fuzzy_countrynames = [ ('US', 'U.S.A'), ('US', 'USA'), ('GB', _("Britain")), ('GB', _("Great Britain")), ('CI', _("Cote de Ivoire")), ] if has_turbogears: def get_countries(): c1 = tgformat.get_countries('en') c2 = tgformat.get_countries()