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