web/lib/django/contrib/localflavor/ca/forms.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 Canada-specific Form helpers
       
     3 """
       
     4 
       
     5 from django.forms import ValidationError
       
     6 from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES
       
     7 from django.forms.util import smart_unicode
       
     8 from django.utils.translation import ugettext_lazy as _
       
     9 import re
       
    10 
       
    11 phone_digits_re = re.compile(r'^(?:1-?)?(\d{3})[-\.]?(\d{3})[-\.]?(\d{4})$')
       
    12 sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")
       
    13 
       
    14 class CAPostalCodeField(RegexField):
       
    15     """Canadian postal code field."""
       
    16     default_error_messages = {
       
    17         'invalid': _(u'Enter a postal code in the format XXX XXX.'),
       
    18     }
       
    19 
       
    20     def __init__(self, *args, **kwargs):
       
    21         super(CAPostalCodeField, self).__init__(r'^[ABCEGHJKLMNPRSTVXYZ]\d[A-Z] \d[A-Z]\d$',
       
    22             max_length=None, min_length=None, *args, **kwargs)
       
    23 
       
    24 class CAPhoneNumberField(Field):
       
    25     """Canadian phone number field."""
       
    26     default_error_messages = {
       
    27         'invalid': u'Phone numbers must be in XXX-XXX-XXXX format.',
       
    28     }
       
    29 
       
    30     def clean(self, value):
       
    31         """Validate a phone number.
       
    32         """
       
    33         super(CAPhoneNumberField, self).clean(value)
       
    34         if value in EMPTY_VALUES:
       
    35             return u''
       
    36         value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
       
    37         m = phone_digits_re.search(value)
       
    38         if m:
       
    39             return u'%s-%s-%s' % (m.group(1), m.group(2), m.group(3))
       
    40         raise ValidationError(self.error_messages['invalid'])
       
    41 
       
    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 
       
    52     def clean(self, value):
       
    53         from ca_provinces import PROVINCES_NORMALIZED
       
    54         super(CAProvinceField, self).clean(value)
       
    55         if value in EMPTY_VALUES:
       
    56             return u''
       
    57         try:
       
    58             value = value.strip().lower()
       
    59         except AttributeError:
       
    60             pass
       
    61         else:
       
    62             try:
       
    63                 return PROVINCES_NORMALIZED[value.strip().lower()].decode('ascii')
       
    64             except KeyError:
       
    65                 pass
       
    66         raise ValidationError(self.error_messages['invalid'])
       
    67 
       
    68 class CAProvinceSelect(Select):
       
    69     """
       
    70     A Select widget that uses a list of Canadian provinces and
       
    71     territories as its choices.
       
    72     """
       
    73     def __init__(self, attrs=None):
       
    74         from ca_provinces import PROVINCE_CHOICES # relative import
       
    75         super(CAProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
       
    76 
       
    77 class CASocialInsuranceNumberField(Field):
       
    78     """
       
    79     A Canadian Social Insurance Number (SIN).
       
    80 
       
    81     Checks the following rules to determine whether the number is valid:
       
    82 
       
    83         * Conforms to the XXX-XXX-XXX format.
       
    84         * Passes the check digit process "Luhn Algorithm"
       
    85              See: http://en.wikipedia.org/wiki/Social_Insurance_Number
       
    86     """
       
    87     default_error_messages = {
       
    88         'invalid': _('Enter a valid Canadian Social Insurance number in XXX-XXX-XXX format.'),
       
    89     }
       
    90 
       
    91     def clean(self, value):
       
    92         super(CASocialInsuranceNumberField, self).clean(value)
       
    93         if value in EMPTY_VALUES:
       
    94             return u''
       
    95 
       
    96         match = re.match(sin_re, value)
       
    97         if not match:
       
    98             raise ValidationError(self.error_messages['invalid'])
       
    99 
       
   100         number = u'%s-%s-%s' % (match.group(1), match.group(2), match.group(3))
       
   101         check_number = u'%s%s%s' % (match.group(1), match.group(2), match.group(3))
       
   102         if not self.luhn_checksum_is_valid(check_number):
       
   103             raise ValidationError(self.error_messages['invalid'])
       
   104         return number
       
   105 
       
   106     def luhn_checksum_is_valid(self, number):
       
   107         """
       
   108         Checks to make sure that the SIN passes a luhn mod-10 checksum
       
   109         See: http://en.wikipedia.org/wiki/Luhn_algorithm
       
   110         """
       
   111 
       
   112         sum = 0
       
   113         num_digits = len(number)
       
   114         oddeven = num_digits & 1
       
   115 
       
   116         for count in range(0, num_digits):
       
   117             digit = int(number[count])
       
   118 
       
   119             if not (( count & 1 ) ^ oddeven ):
       
   120                 digit = digit * 2
       
   121             if digit > 9:
       
   122                 digit = digit - 9
       
   123 
       
   124             sum = sum + digit
       
   125 
       
   126         return ( (sum % 10) == 0 )