PageRenderTime 37ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/django/contrib/localflavor/ca/forms.py

https://code.google.com/p/mango-py/
Python | 134 lines | 114 code | 6 blank | 14 comment | 2 complexity | 00ed24b3f60806812ccae88d9118d920 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. """
  2. Canada-specific Form helpers
  3. """
  4. from django.core.validators import EMPTY_VALUES
  5. from django.forms import ValidationError
  6. from django.forms.fields import Field, RegexField, Select
  7. from django.utils.encoding import smart_unicode
  8. from django.utils.translation import ugettext_lazy as _
  9. import re
  10. phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
  11. sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")
  12. class CAPostalCodeField(RegexField):
  13. """
  14. Canadian postal code field.
  15. Validates against known invalid characters: D, F, I, O, Q, U
  16. Additionally the first character cannot be Z or W.
  17. For more info see:
  18. http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1402170
  19. """
  20. default_error_messages = {
  21. 'invalid': _(u'Enter a postal code in the format XXX XXX.'),
  22. }
  23. def __init__(self, *args, **kwargs):
  24. super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] \d[ABCEGHJKLMNPRSTVWXYZ]\d$',
  25. max_length=None, min_length=None, *args, **kwargs)
  26. class CAPhoneNumberField(Field):
  27. """Canadian phone number field."""
  28. default_error_messages = {
  29. 'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.',
  30. }
  31. def clean(self, value):
  32. """Validate a phone number.
  33. """
  34. super(CAPhoneNumberField, self).clean(value)
  35. if value in EMPTY_VALUES:
  36. return u''
  37. value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
  38. m = phone_digits_re.search(value)
  39. if m:
  40. return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
  41. raise ValidationError(self.error_messages['invalid'])
  42. class CAProvinceField(Field):
  43. """
  44. A form field that validates its input is a Canadian province name or abbreviation.
  45. It normalizes the input to the standard two-leter postal service
  46. abbreviation for the given province.
  47. """
  48. default_error_messages = {
  49. 'invalid': u'Enter a Canadian province or territory.',
  50. }
  51. def clean(self, value):
  52. from ca_provinces import PROVINCES_NORMALIZED
  53. super(CAProvinceField, self).clean(value)
  54. if value in EMPTY_VALUES:
  55. return u''
  56. try:
  57. value = value.strip().lower()
  58. except AttributeError:
  59. pass
  60. else:
  61. try:
  62. return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii')
  63. except KeyError:
  64. pass
  65. raise ValidationError(self.error_messages['invalid'])
  66. class CAProvinceSelect(Select):
  67. """
  68. A Select widget that uses a list of Canadian provinces and
  69. territories as its choices.
  70. """
  71. def __init__(self, attrs=None):
  72. from ca_provinces import PROVINCE_CHOICES # relative import
  73. super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
  74. class CASocialInsuranceNumberField(Field):
  75. """
  76. A Canadian Social Insurance Number (SIN).
  77. Checks the following rules to determine whether the number is valid:
  78. * Conforms to the XXX-XXX-XXX format.
  79. * Passes the check digit process "Luhn Algorithm"
  80. See: http://en.wikipedia.org/wiki/Social_Insurance_Number
  81. """
  82. default_error_messages = {
  83. 'invalid': _('Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'),
  84. }
  85. def clean(self, value):
  86. super(CASocialInsuranceNumberField, self).clean(value)
  87. if value in EMPTY_VALUES:
  88. return u''
  89. match = re.match(sin_re, value)
  90. if not match:
  91. raise ValidationError(self.error_messages['invalid'])
  92. number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3))
  93. check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3))
  94. if not self.luhn_checksum_is_valid(check_number):
  95. raise ValidationError(self.error_messages['invalid'])
  96. return number
  97. def luhn_checksum_is_valid(self, number):
  98. """
  99. Checks to make sure that the SIN passes a luhn mod-10 checksum
  100. See: http://en.wikipedia.org/wiki/Luhn_algorithm
  101. """
  102. sum = 0
  103. num_digits = len(number)
  104. oddeven = num_digits & 1
  105. for count in range(0, num_digits):
  106. digit = int(number[count])
  107. if not (( count & 1 ) ^ oddeven ):
  108. digit = digit * 2
  109. if digit > 9:
  110. digit = digit - 9
  111. sum = sum + digit
  112. return ( (sum % 10) == 0 )