diff -r 8d941af65caf -r 77b6da96e6f1 web/lib/django/contrib/localflavor/ca/forms.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/web/lib/django/contrib/localflavor/ca/forms.py Wed Jun 02 18:57:35 2010 +0200 @@ -0,0 +1,134 @@ +""" +Canada-specific Form helpers +""" + +from django.core.validators import EMPTY_VALUES +from django.forms import ValidationError +from django.forms.fields import Field, RegexField, Select +from django.utils.encoding import smart_unicode +from django.utils.translation import ugettext_lazy as _ +import re + +phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$') +sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$") + +class CAPostalCodeField(RegexField): + """ + Canadian postal code field. + + Validates against known invalid characters: D, F, I, O, Q, U + Additionally the first character cannot be Z or W. + For more info see: + http://www.canadapost.ca/tools/pg/manual/PGaddress-e.asp#1402170 + """ + default_error_messages = { + 'invalid': _(u'Enter a postal code in the format XXX XXX.'), + } + + def __init__(self, *args, **kwargs): + super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] \d[ABCEGHJKLMNPRSTVWXYZ]\d$', + max_length=None, min_length=None, *args, **kwargs) + +class CAPhoneNumberField(Field): + """Canadian phone number field.""" + default_error_messages = { + 'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.', + } + + def clean(self, value): + """Validate a phone number. + """ + super(CAPhoneNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + value = re.sub('(\(|\)|\s+)', '', smart_unicode(value)) + m = phone_digits_re.search(value) + if m: + return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3)) + raise ValidationError(self.error_messages['invalid']) + +class CAProvinceField(Field): + """ + A form field that validates its input is a Canadian province name or abbreviation. + It normalizes the input to the standard two-leter postal service + abbreviation for the given province. + """ + default_error_messages = { + 'invalid': u'Enter a Canadian province or territory.', + } + + def clean(self, value): + from ca_provinces import PROVINCES_NORMALIZED + super(CAProvinceField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + try: + value = value.strip().lower() + except AttributeError: + pass + else: + try: + return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii') + except KeyError: + pass + raise ValidationError(self.error_messages['invalid']) + +class CAProvinceSelect(Select): + """ + A Select widget that uses a list of Canadian provinces and + territories as its choices. + """ + def __init__(self, attrs=None): + from ca_provinces import PROVINCE_CHOICES # relative import + super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES) + +class CASocialInsuranceNumberField(Field): + """ + A Canadian Social Insurance Number (SIN). + + Checks the following rules to determine whether the number is valid: + + * Conforms to the XXX-XXX-XXX format. + * Passes the check digit process "Luhn Algorithm" + See: http://en.wikipedia.org/wiki/Social_Insurance_Number + """ + default_error_messages = { + 'invalid': _('Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'), + } + + def clean(self, value): + super(CASocialInsuranceNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + match = re.match(sin_re, value) + if not match: + raise ValidationError(self.error_messages['invalid']) + + number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3)) + check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3)) + if not self.luhn_checksum_is_valid(check_number): + raise ValidationError(self.error_messages['invalid']) + return number + + def luhn_checksum_is_valid(self, number): + """ + Checks to make sure that the SIN passes a luhn mod-10 checksum + See: http://en.wikipedia.org/wiki/Luhn_algorithm + """ + + sum = 0 + num_digits = len(number) + oddeven = num_digits & 1 + + for count in range(0, num_digits): + digit = int(number[count]) + + if not (( count & 1 ) ^ oddeven ): + digit = digit * 2 + if digit > 9: + digit = digit - 9 + + sum = sum + digit + + return ( (sum % 10) == 0 )