| | 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) |