PageRenderTime 50ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/formencode/national.py

https://github.com/mofukuma/gae_onlinegamebase
Python | 765 lines | 699 code | 37 blank | 29 comment | 41 complexity | f1695d008435e22a2dd2336bfe64fd99 MD5 | raw file
  1. """
  2. Country specific validators for use with FormEncode.
  3. """
  4. import re
  5. from api import FancyValidator
  6. from compound import Any
  7. from validators import Regex, Invalid, _
  8. try:
  9. import pycountry
  10. except ImportError:
  11. pycountry = None
  12. try:
  13. from turbogears.i18n import format as tgformat
  14. except ImportError:
  15. tgformat = None
  16. if pycountry or tgformat:
  17. no_country = False
  18. else:
  19. import warnings
  20. no_country = ('Please easy_install pycountry or validators handling'
  21. ' country names and/or languages will not work.')
  22. ############################################################
  23. ## country lists and functions
  24. ############################################################
  25. country_additions = [
  26. ('BY', _('Belarus')),
  27. ('ME', _('Montenegro')),
  28. ('AU', _('Tasmania')),
  29. ]
  30. fuzzy_countrynames = [
  31. ('US', 'U.S.A'),
  32. ('US', 'USA'),
  33. ('GB', _('Britain')),
  34. ('GB', _('Great Britain')),
  35. ('CI', _('Cote de Ivoire')),
  36. ]
  37. if tgformat:
  38. def get_countries():
  39. c1 = tgformat.get_countries('en')
  40. c2 = tgformat.get_countries()
  41. if len(c1) > len(c2):
  42. d = dict(country_additions)
  43. d.update(dict(c1))
  44. d.update(dict(c2))
  45. else:
  46. d = dict(country_additions)
  47. d.update(dict(c2))
  48. ret = d.items() + fuzzy_countrynames
  49. return ret
  50. def get_country(code):
  51. return dict(get_countries())[code]
  52. def get_languages():
  53. c1 = tgformat.get_languages('en')
  54. c2 = tgformat.get_languages()
  55. if len(c1) > len(c2):
  56. d = dict(c1)
  57. d.update(dict(c2))
  58. return d.items()
  59. else:
  60. return c2
  61. def get_language(code):
  62. try:
  63. return tgformat.get_language(code)
  64. except KeyError:
  65. return tgformat.get_language(code, 'en')
  66. elif pycountry:
  67. # @@ mark: interestingly, common gettext notation does not work here
  68. import gettext
  69. gettext.bindtextdomain('iso3166', pycountry.LOCALES_DIR)
  70. _c = lambda t: gettext.dgettext('iso3166', t)
  71. gettext.bindtextdomain('iso639', pycountry.LOCALES_DIR)
  72. _l = lambda t: gettext.dgettext('iso639', t)
  73. def get_countries():
  74. c1 = set([(e.alpha2, _c(e.name)) for e in pycountry.countries])
  75. ret = c1.union(country_additions + fuzzy_countrynames)
  76. return ret
  77. def get_country(code):
  78. return _c(pycountry.countries.get(alpha2=code).name)
  79. def get_languages():
  80. return [(e.alpha2, _l(e.name)) for e in pycountry.languages
  81. if e.name and getattr(e, 'alpha2', None)]
  82. def get_language(code):
  83. return _l(pycountry.languages.get(alpha2=code).name)
  84. ############################################################
  85. ## country, state and postal code validators
  86. ############################################################
  87. class DelimitedDigitsPostalCode(Regex):
  88. """
  89. Abstraction of common postal code formats, such as 55555, 55-555 etc.
  90. With constant amount of digits. By providing a single digit as partition you
  91. can obtain a trivial 'x digits' postal code validator.
  92. ::
  93. >>> german = DelimitedDigitsPostalCode(5)
  94. >>> german.to_python('55555')
  95. '55555'
  96. >>> german.to_python('5555')
  97. Traceback (most recent call last):
  98. ...
  99. Invalid: Please enter a zip code (5 digits)
  100. >>> polish = DelimitedDigitsPostalCode([2, 3], '-')
  101. >>> polish.to_python('55555')
  102. '55-555'
  103. >>> polish.to_python('55-555')
  104. '55-555'
  105. >>> polish.to_python('5555')
  106. Traceback (most recent call last):
  107. ...
  108. Invalid: Please enter a zip code (nn-nnn)
  109. >>> nicaragua = DelimitedDigitsPostalCode([3, 3, 1], '-')
  110. >>> nicaragua.to_python('5554443')
  111. '555-444-3'
  112. >>> nicaragua.to_python('555-4443')
  113. '555-444-3'
  114. >>> nicaragua.to_python('5555')
  115. Traceback (most recent call last):
  116. ...
  117. Invalid: Please enter a zip code (nnn-nnn-n)
  118. """
  119. strip = True
  120. def assembly_formatstring(self, partition_lengths, delimiter):
  121. if len(partition_lengths) == 1:
  122. return _('%d digits') % partition_lengths[0]
  123. else:
  124. return delimiter.join('n' * l for l in partition_lengths)
  125. def assembly_regex(self, partition_lengths, delimiter):
  126. mg = [r'(\d{%d})' % l for l in partition_lengths]
  127. rd = r'\%s?' % delimiter
  128. return rd.join(mg)
  129. def __init__(self, partition_lengths, delimiter=None,
  130. *args, **kw):
  131. if isinstance(partition_lengths, (int, long)):
  132. partition_lengths = [partition_lengths]
  133. if not delimiter:
  134. delimiter = ''
  135. self.format = self.assembly_formatstring(partition_lengths, delimiter)
  136. self.regex = self.assembly_regex(partition_lengths, delimiter)
  137. self.partition_lengths, self.delimiter = partition_lengths, delimiter
  138. Regex.__init__(self, *args, **kw)
  139. messages = dict(
  140. invalid=_('Please enter a zip code (%(format)s)'))
  141. def _convert_to_python(self, value, state):
  142. self.assert_string(value, state)
  143. match = self.regex.search(value)
  144. if not match:
  145. raise Invalid(
  146. self.message('invalid', state, format=self.format),
  147. value, state)
  148. return self.delimiter.join(match.groups())
  149. def USPostalCode(*args, **kw):
  150. """
  151. US Postal codes (aka Zip Codes).
  152. ::
  153. >>> uspc = USPostalCode()
  154. >>> uspc.to_python('55555')
  155. '55555'
  156. >>> uspc.to_python('55555-5555')
  157. '55555-5555'
  158. >>> uspc.to_python('5555')
  159. Traceback (most recent call last):
  160. ...
  161. Invalid: Please enter a zip code (5 digits)
  162. """
  163. return Any(DelimitedDigitsPostalCode(5, None, *args, **kw),
  164. DelimitedDigitsPostalCode([5, 4], '-', *args, **kw))
  165. def GermanPostalCode(*args, **kw):
  166. return DelimitedDigitsPostalCode(5, None, *args, **kw)
  167. def FourDigitsPostalCode(*args, **kw):
  168. return DelimitedDigitsPostalCode(4, None, *args, **kw)
  169. def PolishPostalCode(*args, **kw):
  170. return DelimitedDigitsPostalCode([2, 3], '-', *args, **kw)
  171. class ArgentinianPostalCode(Regex):
  172. """
  173. Argentinian Postal codes.
  174. ::
  175. >>> ArgentinianPostalCode.to_python('C1070AAM')
  176. 'C1070AAM'
  177. >>> ArgentinianPostalCode.to_python('c 1070 aam')
  178. 'C1070AAM'
  179. >>> ArgentinianPostalCode.to_python('5555')
  180. Traceback (most recent call last):
  181. ...
  182. Invalid: Please enter a zip code (LnnnnLLL)
  183. """
  184. regex = re.compile(r'^([a-zA-Z]{1})\s*(\d{4})\s*([a-zA-Z]{3})$')
  185. strip = True
  186. messages = dict(
  187. invalid=_('Please enter a zip code (%s)') % _('LnnnnLLL'))
  188. def _convert_to_python(self, value, state):
  189. self.assert_string(value, state)
  190. match = self.regex.search(value)
  191. if not match:
  192. raise Invalid(
  193. self.message('invalid', state),
  194. value, state)
  195. return '%s%s%s' % (match.group(1).upper(),
  196. match.group(2),
  197. match.group(3).upper())
  198. class CanadianPostalCode(Regex):
  199. """
  200. Canadian Postal codes.
  201. ::
  202. >>> CanadianPostalCode.to_python('V3H 1Z7')
  203. 'V3H 1Z7'
  204. >>> CanadianPostalCode.to_python('v3h1z7')
  205. 'V3H 1Z7'
  206. >>> CanadianPostalCode.to_python('5555')
  207. Traceback (most recent call last):
  208. ...
  209. Invalid: Please enter a zip code (LnL nLn)
  210. """
  211. regex = re.compile(r'^([a-zA-Z]\d[a-zA-Z])\s?(\d[a-zA-Z]\d)$')
  212. strip = True
  213. messages = dict(
  214. invalid=_('Please enter a zip code (%s)') % _('LnL nLn'))
  215. def _convert_to_python(self, value, state):
  216. self.assert_string(value, state)
  217. match = self.regex.search(value)
  218. if not match:
  219. raise Invalid(
  220. self.message('invalid', state),
  221. value, state)
  222. return '%s %s' % (match.group(1).upper(), match.group(2).upper())
  223. class UKPostalCode(Regex):
  224. """
  225. UK Postal codes. Please see BS 7666.
  226. ::
  227. >>> UKPostalCode.to_python('BFPO 3')
  228. 'BFPO 3'
  229. >>> UKPostalCode.to_python('LE11 3GR')
  230. 'LE11 3GR'
  231. >>> UKPostalCode.to_python('l1a 3gr')
  232. 'L1A 3GR'
  233. >>> UKPostalCode.to_python('5555')
  234. Traceback (most recent call last):
  235. ...
  236. Invalid: Please enter a valid postal code (for format see BS 7666)
  237. """
  238. 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)
  239. strip = True
  240. messages = dict(
  241. invalid=_('Please enter a valid postal code (for format see BS 7666)'))
  242. def _convert_to_python(self, value, state):
  243. self.assert_string(value, state)
  244. match = self.regex.search(value)
  245. if not match:
  246. raise Invalid(
  247. self.message('invalid', state),
  248. value, state)
  249. return match.group(1).upper()
  250. class CountryValidator(FancyValidator):
  251. """
  252. Will convert a country's name into its ISO-3166 abbreviation for unified
  253. storage in databases etc. and return a localized country name in the
  254. reverse step.
  255. @See http://www.iso.org/iso/country_codes/iso_3166_code_lists.htm
  256. ::
  257. >>> CountryValidator.to_python('Germany')
  258. u'DE'
  259. >>> CountryValidator.to_python('Finland')
  260. u'FI'
  261. >>> CountryValidator.to_python('UNITED STATES')
  262. u'US'
  263. >>> CountryValidator.to_python('Krakovia')
  264. Traceback (most recent call last):
  265. ...
  266. Invalid: That country is not listed in ISO 3166
  267. >>> CountryValidator.from_python('DE')
  268. u'Germany'
  269. >>> CountryValidator.from_python('FI')
  270. u'Finland'
  271. """
  272. key_ok = True
  273. messages = dict(
  274. valueNotFound=_('That country is not listed in ISO 3166'))
  275. def __init__(self, *args, **kw):
  276. FancyValidator.__init__(self, *args, **kw)
  277. if no_country:
  278. warnings.warn(no_country, Warning, 2)
  279. def _convert_to_python(self, value, state):
  280. upval = value.upper()
  281. if self.key_ok:
  282. try:
  283. get_country(upval)
  284. except Exception:
  285. pass
  286. else:
  287. return upval
  288. for k, v in get_countries():
  289. if v.upper() == upval:
  290. return k
  291. raise Invalid(self.message('valueNotFound', state), value, state)
  292. def _convert_from_python(self, value, state):
  293. try:
  294. return get_country(value.upper())
  295. except KeyError:
  296. return value
  297. class PostalCodeInCountryFormat(FancyValidator):
  298. """
  299. Makes sure the postal code is in the country's format by chosing postal
  300. code validator by provided country code. Does convert it into the preferred
  301. format, too.
  302. ::
  303. >>> fs = PostalCodeInCountryFormat('country', 'zip')
  304. >>> sorted(fs.to_python(dict(country='DE', zip='30167')).items())
  305. [('country', 'DE'), ('zip', '30167')]
  306. >>> fs.to_python(dict(country='DE', zip='3008'))
  307. Traceback (most recent call last):
  308. ...
  309. Invalid: Given postal code does not match the country's format.
  310. >>> sorted(fs.to_python(dict(country='PL', zip='34343')).items())
  311. [('country', 'PL'), ('zip', '34-343')]
  312. >>> fs = PostalCodeInCountryFormat('staat', 'plz')
  313. >>> sorted(fs.to_python(dict(staat='GB', plz='l1a 3gr')).items())
  314. [('plz', 'L1A 3GR'), ('staat', 'GB')]
  315. """
  316. country_field = 'country'
  317. zip_field = 'zip'
  318. __unpackargs__ = ('country_field', 'zip_field')
  319. messages = dict(
  320. badFormat=_("Given postal code does not match the country's format."))
  321. _vd = {
  322. 'AR': ArgentinianPostalCode,
  323. 'AT': FourDigitsPostalCode,
  324. 'BE': FourDigitsPostalCode,
  325. 'BG': FourDigitsPostalCode,
  326. 'CA': CanadianPostalCode,
  327. 'CL': lambda: DelimitedDigitsPostalCode(7),
  328. 'CN': lambda: DelimitedDigitsPostalCode(6),
  329. 'CR': FourDigitsPostalCode,
  330. 'DE': GermanPostalCode,
  331. 'DK': FourDigitsPostalCode,
  332. 'DO': lambda: DelimitedDigitsPostalCode(5),
  333. 'ES': lambda: DelimitedDigitsPostalCode(5),
  334. 'FI': lambda: DelimitedDigitsPostalCode(5),
  335. 'FR': lambda: DelimitedDigitsPostalCode(5),
  336. 'GB': UKPostalCode,
  337. 'GF': lambda: DelimitedDigitsPostalCode(5),
  338. 'GR': lambda: DelimitedDigitsPostalCode([2, 3], ' '),
  339. 'HN': lambda: DelimitedDigitsPostalCode(5),
  340. 'HT': FourDigitsPostalCode,
  341. 'HU': FourDigitsPostalCode,
  342. 'IS': lambda: DelimitedDigitsPostalCode(3),
  343. 'IT': lambda: DelimitedDigitsPostalCode(5),
  344. 'JP': lambda: DelimitedDigitsPostalCode([3, 4], '-'),
  345. 'KR': lambda: DelimitedDigitsPostalCode([3, 3], '-'),
  346. 'LI': FourDigitsPostalCode,
  347. 'LU': FourDigitsPostalCode,
  348. 'MC': lambda: DelimitedDigitsPostalCode(5),
  349. 'NI': lambda: DelimitedDigitsPostalCode([3, 3, 1], '-'),
  350. 'NO': FourDigitsPostalCode,
  351. 'PL': PolishPostalCode,
  352. 'PT': lambda: DelimitedDigitsPostalCode([4, 3], '-'),
  353. 'PY': FourDigitsPostalCode,
  354. 'RO': lambda: DelimitedDigitsPostalCode(6),
  355. 'SE': lambda: DelimitedDigitsPostalCode([3, 2], ' '),
  356. 'SG': lambda: DelimitedDigitsPostalCode(6),
  357. 'US': USPostalCode,
  358. 'UY': lambda: DelimitedDigitsPostalCode(5),
  359. }
  360. def _validate_python(self, fields_dict, state):
  361. if fields_dict[self.country_field] in self._vd:
  362. try:
  363. zip_validator = self._vd[fields_dict[self.country_field]]()
  364. fields_dict[self.zip_field] = zip_validator.to_python(
  365. fields_dict[self.zip_field])
  366. except Invalid, e:
  367. message = self.message('badFormat', state)
  368. raise Invalid(message, fields_dict, state,
  369. error_dict={self.zip_field: e.msg,
  370. self.country_field: message})
  371. class USStateProvince(FancyValidator):
  372. """
  373. Valid state or province code (two-letter).
  374. Well, for now I don't know the province codes, but it does state
  375. codes. Give your own `states` list to validate other state-like
  376. codes; give `extra_states` to add values without losing the
  377. current state values.
  378. ::
  379. >>> s = USStateProvince('XX')
  380. >>> s.to_python('IL')
  381. 'IL'
  382. >>> s.to_python('XX')
  383. 'XX'
  384. >>> s.to_python('xx')
  385. 'XX'
  386. >>> s.to_python('YY')
  387. Traceback (most recent call last):
  388. ...
  389. Invalid: That is not a valid state code
  390. """
  391. states = ['AK', 'AL', 'AR', 'AZ', 'CA', 'CO', 'CT', 'DC', 'DE',
  392. 'FL', 'GA', 'HI', 'IA', 'ID', 'IN', 'IL', 'KS', 'KY',
  393. 'LA', 'MA', 'MD', 'ME', 'MI', 'MN', 'MO', 'MS', 'MT',
  394. 'NC', 'ND', 'NE', 'NH', 'NJ', 'NM', 'NV', 'NY', 'OH',
  395. 'OK', 'OR', 'PA', 'RI', 'SC', 'SD', 'TN', 'TX', 'UT',
  396. 'VA', 'VT', 'WA', 'WI', 'WV', 'WY']
  397. extra_states = []
  398. __unpackargs__ = ('extra_states',)
  399. messages = dict(
  400. empty=_('Please enter a state code'),
  401. wrongLength=_('Please enter a state code with TWO letters'),
  402. invalid=_('That is not a valid state code'))
  403. def _validate_python(self, value, state):
  404. value = str(value).strip().upper()
  405. if not value:
  406. raise Invalid(
  407. self.message('empty', state),
  408. value, state)
  409. if not value or len(value) != 2:
  410. raise Invalid(
  411. self.message('wrongLength', state),
  412. value, state)
  413. if value not in self.states and not (
  414. self.extra_states and value in self.extra_states):
  415. raise Invalid(
  416. self.message('invalid', state),
  417. value, state)
  418. def _convert_to_python(self, value, state):
  419. return str(value).strip().upper()
  420. ############################################################
  421. ## phone number validators
  422. ############################################################
  423. class USPhoneNumber(FancyValidator):
  424. """
  425. Validates, and converts to ###-###-####, optionally with extension
  426. (as ext.##...). Only support US phone numbers. See
  427. InternationalPhoneNumber for support for that kind of phone number.
  428. ::
  429. >>> p = USPhoneNumber()
  430. >>> p.to_python('333-3333')
  431. Traceback (most recent call last):
  432. ...
  433. Invalid: Please enter a number, with area code, in the form ###-###-####, optionally with "ext.####"
  434. >>> p.to_python('555-555-5555')
  435. '555-555-5555'
  436. >>> p.to_python('1-393-555-3939')
  437. '1-393-555-3939'
  438. >>> p.to_python('321.555.4949')
  439. '321.555.4949'
  440. >>> p.to_python('3335550000')
  441. '3335550000'
  442. """
  443. # for emacs: "
  444. _phoneRE = re.compile(r'^\s*(?:1-)?(\d\d\d)[\- \.]?(\d\d\d)[\- \.]?(\d\d\d\d)(?:\s*ext\.?\s*(\d+))?\s*$', re.I)
  445. messages = dict(
  446. phoneFormat=_('Please enter a number, with area code,'
  447. ' in the form ###-###-####, optionally with "ext.####"'))
  448. def _convert_to_python(self, value, state):
  449. self.assert_string(value, state)
  450. match = self._phoneRE.search(value)
  451. if not match:
  452. raise Invalid(
  453. self.message('phoneFormat', state),
  454. value, state)
  455. return value
  456. def _convert_from_python(self, value, state):
  457. self.assert_string(value, state)
  458. match = self._phoneRE.search(value)
  459. if not match:
  460. raise Invalid(self.message('phoneFormat', state),
  461. value, state)
  462. result = '%s-%s-%s' % (match.group(1), match.group(2), match.group(3))
  463. if match.group(4):
  464. result += " ext.%s" % match.group(4)
  465. return result
  466. class InternationalPhoneNumber(FancyValidator):
  467. """
  468. Validates, and converts phone numbers to +##-###-#######.
  469. Adapted from RFC 3966
  470. @param default_cc country code for prepending if none is provided
  471. can be a paramerless callable
  472. ::
  473. >>> c = InternationalPhoneNumber(default_cc=lambda: 49)
  474. >>> c.to_python('0555/8114100')
  475. '+49-555-8114100'
  476. >>> p = InternationalPhoneNumber(default_cc=49)
  477. >>> p.to_python('333-3333')
  478. Traceback (most recent call last):
  479. ...
  480. Invalid: Please enter a number, with area code, in the form +##-###-#######.
  481. >>> p.to_python('0555/4860-300')
  482. '+49-555-4860-300'
  483. >>> p.to_python('0555-49924-51')
  484. '+49-555-49924-51'
  485. >>> p.to_python('0555 / 8114100')
  486. '+49-555-8114100'
  487. >>> p.to_python('0555/8114100')
  488. '+49-555-8114100'
  489. >>> p.to_python('0555 8114100')
  490. '+49-555-8114100'
  491. >>> p.to_python(' +49 (0)555 350 60 0')
  492. '+49-555-35060-0'
  493. >>> p.to_python('+49 555 350600')
  494. '+49-555-350600'
  495. >>> p.to_python('0049/ 555/ 871 82 96')
  496. '+49-555-87182-96'
  497. >>> p.to_python('0555-2 50-30')
  498. '+49-555-250-30'
  499. >>> p.to_python('0555 43-1200')
  500. '+49-555-43-1200'
  501. >>> p.to_python('(05 55)4 94 33 47')
  502. '+49-555-49433-47'
  503. >>> p.to_python('(00 48-555)2 31 72 41')
  504. '+48-555-23172-41'
  505. >>> p.to_python('+973-555431')
  506. '+973-555431'
  507. >>> p.to_python('1-393-555-3939')
  508. '+1-393-555-3939'
  509. >>> p.to_python('+43 (1) 55528/0')
  510. '+43-1-55528-0'
  511. >>> p.to_python('+43 5555 429 62-0')
  512. '+43-5555-42962-0'
  513. >>> p.to_python('00 218 55 33 50 317 321')
  514. '+218-55-3350317-321'
  515. >>> p.to_python('+218 (0)55-3636639/38')
  516. '+218-55-3636639-38'
  517. >>> p.to_python('032 555555 367')
  518. '+49-32-555555-367'
  519. >>> p.to_python('(+86) 555 3876693')
  520. '+86-555-3876693'
  521. """
  522. strip = True
  523. # Use if there's a default country code you want to use:
  524. default_cc = None
  525. _mark_chars_re = re.compile(r"[_.!~*'/]")
  526. _preTransformations = [
  527. (re.compile(r'^(\(?)(?:00\s*)(.+)$'), '%s+%s'),
  528. (re.compile(r'^\(\s*(\+?\d+)\s*(\d+)\s*\)(.+)$'), '(%s%s)%s'),
  529. (re.compile(r'^\((\+?[-\d]+)\)\s?(\d.+)$'), '%s-%s'),
  530. (re.compile(r'^(?:1-)(\d+.+)$'), '+1-%s'),
  531. (re.compile(r'^(\+\d+)\s+\(0\)\s*(\d+.+)$'), '%s-%s'),
  532. (re.compile(r'^([0+]\d+)[-\s](\d+)$'), '%s-%s'),
  533. (re.compile(r'^([0+]\d+)[-\s](\d+)[-\s](\d+)$'), '%s-%s-%s'),
  534. ]
  535. _ccIncluder = [
  536. (re.compile(r'^\(?0([1-9]\d*)[-)](\d.*)$'), '+%d-%s-%s'),
  537. ]
  538. _postTransformations = [
  539. (re.compile(r'^(\+\d+)[-\s]\(?(\d+)\)?[-\s](\d+.+)$'), '%s-%s-%s'),
  540. (re.compile(r'^(.+)\s(\d+)$'), '%s-%s'),
  541. ]
  542. _phoneIsSane = re.compile(r'^(\+[1-9]\d*)-([\d\-]+)$')
  543. messages = dict(
  544. phoneFormat=_('Please enter a number, with area code,'
  545. ' in the form +##-###-#######.'))
  546. def _perform_rex_transformation(self, value, transformations):
  547. for rex, trf in transformations:
  548. match = rex.search(value)
  549. if match:
  550. value = trf % match.groups()
  551. return value
  552. def _prepend_country_code(self, value, transformations, country_code):
  553. for rex, trf in transformations:
  554. match = rex.search(value)
  555. if match:
  556. return trf % ((country_code,)+match.groups())
  557. return value
  558. def _convert_to_python(self, value, state):
  559. self.assert_string(value, state)
  560. try:
  561. value = value.encode('ascii', 'strict')
  562. except UnicodeEncodeError:
  563. raise Invalid(self.message('phoneFormat', state), value, state)
  564. if unicode is str: # Python 3
  565. value = value.decode('ascii')
  566. value = self._mark_chars_re.sub('-', value)
  567. for f, t in [(' ', ' '),
  568. ('--', '-'), (' - ', '-'), ('- ', '-'), (' -', '-')]:
  569. value = value.replace(f, t)
  570. value = self._perform_rex_transformation(
  571. value, self._preTransformations)
  572. if self.default_cc:
  573. if callable(self.default_cc):
  574. cc = self.default_cc()
  575. else:
  576. cc = self.default_cc
  577. value = self._prepend_country_code(value, self._ccIncluder, cc)
  578. value = self._perform_rex_transformation(
  579. value, self._postTransformations)
  580. value = value.replace(' ', '')
  581. # did we successfully transform that phone number? Thus, is it valid?
  582. if not self._phoneIsSane.search(value):
  583. raise Invalid(self.message('phoneFormat', state), value, state)
  584. return value
  585. ############################################################
  586. ## language validators
  587. ############################################################
  588. class LanguageValidator(FancyValidator):
  589. """
  590. Converts a given language into its ISO 639 alpha 2 code, if there is any.
  591. Returns the language's full name in the reverse.
  592. Warning: ISO 639 neither differentiates between languages such as Cantonese
  593. and Mandarin nor does it contain all spoken languages. E.g., Lechitic
  594. languages are missing.
  595. Warning: ISO 639 is a smaller subset of ISO 639-2
  596. @param key_ok accept the language's code instead of its name for input
  597. defaults to True
  598. ::
  599. >>> l = LanguageValidator()
  600. >>> l.to_python('German')
  601. u'de'
  602. >>> l.to_python('Chinese')
  603. u'zh'
  604. >>> l.to_python('Klingonian')
  605. Traceback (most recent call last):
  606. ...
  607. Invalid: That language is not listed in ISO 639
  608. >>> l.from_python('de')
  609. u'German'
  610. >>> l.from_python('zh')
  611. u'Chinese'
  612. """
  613. key_ok = True
  614. messages = dict(
  615. valueNotFound=_('That language is not listed in ISO 639'))
  616. def __init__(self, *args, **kw):
  617. FancyValidator.__init__(self, *args, **kw)
  618. if no_country:
  619. warnings.warn(no_country, Warning, 2)
  620. def _convert_to_python(self, value, state):
  621. upval = value.upper()
  622. if self.key_ok:
  623. try:
  624. get_language(value)
  625. except Exception:
  626. pass
  627. else:
  628. return value
  629. for k, v in get_languages():
  630. if v.upper() == upval:
  631. return k
  632. raise Invalid(self.message('valueNotFound', state), value, state)
  633. def _convert_from_python(self, value, state):
  634. try:
  635. return get_language(value.lower())
  636. except KeyError:
  637. return value
  638. def validators():
  639. """Return the names of all validators in this module."""
  640. return [name for name, value in globals().items()
  641. if isinstance(value, type) and issubclass(value, FancyValidator)]
  642. __all__ = ['Invalid'] + validators()