app/django/contrib/localflavor/ch/forms.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """
       
     2 Swiss-specific Form helpers
       
     3 """
       
     4 
       
     5 from django.newforms import ValidationError
       
     6 from django.newforms.fields import Field, RegexField, Select, EMPTY_VALUES
       
     7 from django.utils.encoding import smart_unicode
       
     8 from django.utils.translation import ugettext
       
     9 import re
       
    10 
       
    11 id_re = re.compile(r"^(?P<idnumber>\w{8})(?P<pos9>(\d{1}|<))(?P<checksum>\d{1})$")
       
    12 phone_digits_re = re.compile(r'^0([1-9]{1})\d{8}$')
       
    13 
       
    14 class CHZipCodeField(RegexField):
       
    15     default_error_messages = {
       
    16         'invalid': ugettext('Enter a zip code in the format XXXX.'),
       
    17     }
       
    18 
       
    19     def __init__(self, *args, **kwargs):
       
    20         super(CHZipCodeField, self).__init__(r'^\d{4}$',
       
    21         max_length=None, min_length=None, *args, **kwargs)
       
    22 
       
    23 class CHPhoneNumberField(Field):
       
    24     """
       
    25     Validate local Swiss phone number (not international ones)
       
    26     The correct format is '0XX XXX XX XX'.
       
    27     '0XX.XXX.XX.XX' and '0XXXXXXXXX' validate but are corrected to
       
    28     '0XX XXX XX XX'.
       
    29     """
       
    30     default_error_messages = {
       
    31         'invalid': 'Phone numbers must be in 0XX XXX XX XX format.',
       
    32     }
       
    33 
       
    34     def clean(self, value):
       
    35         super(CHPhoneNumberField, self).clean(value)
       
    36         if value in EMPTY_VALUES:
       
    37             return u''
       
    38         value = re.sub('(\.|\s|/|-)', '', smart_unicode(value))
       
    39         m = phone_digits_re.search(value)
       
    40         if m:
       
    41             return u'%s %s %s %s' % (value[0:3], value[3:6], value[6:8], value[8:10])
       
    42         raise ValidationError(self.error_messages['invalid'])
       
    43 
       
    44 class CHStateSelect(Select):
       
    45     """
       
    46     A Select widget that uses a list of CH states as its choices.
       
    47     """
       
    48     def __init__(self, attrs=None):
       
    49         from ch_states import STATE_CHOICES # relative import
       
    50         super(CHStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
       
    51 
       
    52 class CHIdentityCardNumberField(Field):
       
    53     """
       
    54     A Swiss identity card number.
       
    55 
       
    56     Checks the following rules to determine whether the number is valid:
       
    57 
       
    58         * Conforms to the X1234567<0 or 1234567890 format.
       
    59         * Included checksums match calculated checksums
       
    60 
       
    61     Algorithm is documented at http://adi.kousz.ch/artikel/IDCHE.htm
       
    62     """
       
    63     default_error_messages = {
       
    64         'invalid': ugettext('Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'),
       
    65     }
       
    66 
       
    67     def has_valid_checksum(self, number):
       
    68         given_number, given_checksum = number[:-1], number[-1]
       
    69         new_number = given_number
       
    70         calculated_checksum = 0
       
    71         fragment = ""
       
    72         parameter = 7
       
    73 
       
    74         first = str(number[:1])
       
    75         if first.isalpha():
       
    76             num = ord(first.upper()) - 65
       
    77             if num < 0 or num > 8:
       
    78                 return False
       
    79             new_number = str(num) + new_number[1:]
       
    80             new_number = new_number[:8] + '0'
       
    81 
       
    82         if not new_number.isdigit():
       
    83             return False
       
    84 
       
    85         for i in range(len(new_number)):
       
    86           fragment = int(new_number[i])*parameter
       
    87           calculated_checksum += fragment
       
    88 
       
    89           if parameter == 1:
       
    90             parameter = 7
       
    91           elif parameter == 3:
       
    92             parameter = 1
       
    93           elif parameter ==7:
       
    94             parameter = 3
       
    95 
       
    96         return str(calculated_checksum)[-1] == given_checksum
       
    97 
       
    98     def clean(self, value):
       
    99         super(CHIdentityCardNumberField, self).clean(value)
       
   100         if value in EMPTY_VALUES:
       
   101             return u''
       
   102 
       
   103         match = re.match(id_re, value)
       
   104         if not match:
       
   105             raise ValidationError(self.error_messages['invalid'])
       
   106 
       
   107         idnumber, pos9, checksum = match.groupdict()['idnumber'], match.groupdict()['pos9'], match.groupdict()['checksum']
       
   108 
       
   109         if idnumber == '00000000' or \
       
   110            idnumber == 'A0000000':
       
   111             raise ValidationError(self.error_messages['invalid'])
       
   112 
       
   113         all_digits = "%s%s%s" % (idnumber, pos9, checksum)
       
   114         if not self.has_valid_checksum(all_digits):
       
   115             raise ValidationError(self.error_messages['invalid'])
       
   116 
       
   117         return u'%s%s%s' % (idnumber, pos9, checksum)
       
   118