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