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