From 725716b5f5de01e374c9015d02baf25029a0f3c7 Mon Sep 17 00:00:00 2001 From: Jacob Kaplan-Moss Date: Fri, 14 Sep 2007 19:08:19 +0000 Subject: [PATCH] Added missing files from [6202] - sorry. git-svn-id: http://code.djangoproject.com/svn/django/trunk@6203 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/localflavor/ca/__init__.py | 0 django/contrib/localflavor/ca/ca_provinces.py | 57 +++++++++ django/contrib/localflavor/ca/forms.py | 112 ++++++++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 django/contrib/localflavor/ca/__init__.py create mode 100644 django/contrib/localflavor/ca/ca_provinces.py create mode 100644 django/contrib/localflavor/ca/forms.py diff --git a/django/contrib/localflavor/ca/__init__.py b/django/contrib/localflavor/ca/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/ca/ca_provinces.py b/django/contrib/localflavor/ca/ca_provinces.py new file mode 100644 index 0000000000..072159ad57 --- /dev/null +++ b/django/contrib/localflavor/ca/ca_provinces.py @@ -0,0 +1,57 @@ +""" +An alphabetical list of provinces and territories for use as `choices` +in a formfield., and a mapping of province misspellings/abbreviations to +normalized abbreviations + +Source: http://www.canada.gc.ca/othergov/prov_e.html + +This exists in this standalone file so that it's only imported into memory +when explicitly needed. +""" + +PROVINCE_CHOICES = ( + ('AB', 'Alberta'), + ('BC', 'British Columbia'), + ('MB', 'Manitoba'), + ('NB', 'New Brunswick'), + ('NF', 'Newfoundland and Labrador'), + ('NT', 'Northwest Territories'), + ('NS', 'Nova Scotia'), + ('NU', 'Nunavut'), + ('ON', 'Ontario'), + ('PE', 'Prince Edward Island'), + ('QC', 'Quebec'), + ('SK', 'Saskatchewan'), + ('YK', 'Yukon') +) + +PROVINCES_NORMALIZED = { + 'ab': 'AB', + 'alberta': 'AB', + 'bc': 'BC', + 'b.c.': 'BC', + 'british columbia': 'BC', + 'mb': 'MB', + 'manitoba': 'MB', + 'nf': 'NF', + 'newfoundland': 'NF', + 'newfoundland and labrador': 'NF', + 'nt': 'NT', + 'northwest territories': 'NT', + 'ns': 'NS', + 'nova scotia': 'NS', + 'nu': 'NU', + 'nunavut': 'NU', + 'on': 'ON', + 'ontario': 'ON', + 'pe': 'PE', + 'pei': 'PE', + 'p.e.i.': 'PE', + 'prince edward island': 'PE', + 'qc': 'QC', + 'quebec': 'QC', + 'sk': 'SK', + 'saskatchewan': 'SK', + 'yk': 'YK', + 'yukon': 'YK', +} \ No newline at end of file diff --git a/django/contrib/localflavor/ca/forms.py b/django/contrib/localflavor/ca/forms.py new file mode 100644 index 0000000000..98f65a5c6c --- /dev/null +++ b/django/contrib/localflavor/ca/forms.py @@ -0,0 +1,112 @@ +""" +Canada-specific Form helpers +""" + +from django.newforms import ValidationError +from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES +from django.newforms.util import smart_unicode +from django.utils.translation import gettext, ugettext +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.""" + def __init__(self, *args, **kwargs): + super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$', + max_length=None, min_length=None, + error_message=gettext(u'Enter a postal code in the format XXX XXX.'), + *args, **kwargs) + +class CAPhoneNumberField(Field): + """Canadian phone number field.""" + 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(u'Phone numbers must be in XXX-XXX-XXXX format.') + +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. + """ + 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(u'Enter a Canadian province or territory.') + +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-XXXX format. + * Passes the check digit process "Luhn Algorithm" + See: http://en.wikipedia.org/wiki/Social_Insurance_Number + """ + def clean(self, value): + super(CASocialInsuranceNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + msg = ugettext('Enter a valid Canadian Social Insurance number in XXX-XXX-XXXX format.') + match = re.match(sin_re, value) + if not match: + raise ValidationError(msg) + + 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(msg) + 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 ) \ No newline at end of file