|
0
|
1 |
""" |
|
|
2 |
Czech-specific form helpers |
|
|
3 |
""" |
|
|
4 |
|
|
|
5 |
from django.forms import ValidationError |
|
|
6 |
from django.forms.fields import Select, RegexField, Field, EMPTY_VALUES |
|
|
7 |
from django.utils.translation import ugettext_lazy as _ |
|
|
8 |
import re |
|
|
9 |
|
|
|
10 |
birth_number = re.compile(r'^(?P<birth>\d{6})/?(?P<id>\d{3,4})$') |
|
|
11 |
ic_number = re.compile(r'^(?P<number>\d{7})(?P<check>\d)$') |
|
|
12 |
|
|
|
13 |
class CZRegionSelect(Select): |
|
|
14 |
""" |
|
|
15 |
A select widget widget with list of Czech regions as choices. |
|
|
16 |
""" |
|
|
17 |
def __init__(self, attrs=None): |
|
|
18 |
from cz_regions import REGION_CHOICES |
|
|
19 |
super(CZRegionSelect, self).__init__(attrs, choices=REGION_CHOICES) |
|
|
20 |
|
|
|
21 |
class CZPostalCodeField(RegexField): |
|
|
22 |
""" |
|
|
23 |
A form field that validates its input as Czech postal code. |
|
|
24 |
Valid form is XXXXX or XXX XX, where X represents integer. |
|
|
25 |
""" |
|
|
26 |
default_error_messages = { |
|
|
27 |
'invalid': _(u'Enter a postal code in the format XXXXX or XXX XX.'), |
|
|
28 |
} |
|
|
29 |
|
|
|
30 |
def __init__(self, *args, **kwargs): |
|
|
31 |
super(CZPostalCodeField, self).__init__(r'^\d{5}$|^\d{3} \d{2}$', |
|
|
32 |
max_length=None, min_length=None, *args, **kwargs) |
|
|
33 |
|
|
|
34 |
def clean(self, value): |
|
|
35 |
""" |
|
|
36 |
Validates the input and returns a string that contains only numbers. |
|
|
37 |
Returns an empty string for empty values. |
|
|
38 |
""" |
|
|
39 |
v = super(CZPostalCodeField, self).clean(value) |
|
|
40 |
return v.replace(' ', '') |
|
|
41 |
|
|
|
42 |
class CZBirthNumberField(Field): |
|
|
43 |
""" |
|
|
44 |
Czech birth number field. |
|
|
45 |
""" |
|
|
46 |
default_error_messages = { |
|
|
47 |
'invalid_format': _(u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'), |
|
|
48 |
'invalid_gender': _(u'Invalid optional parameter Gender, valid values are \'f\' and \'m\''), |
|
|
49 |
'invalid': _(u'Enter a valid birth number.'), |
|
|
50 |
} |
|
|
51 |
|
|
|
52 |
def clean(self, value, gender=None): |
|
|
53 |
super(CZBirthNumberField, self).__init__(value) |
|
|
54 |
|
|
|
55 |
if value in EMPTY_VALUES: |
|
|
56 |
return u'' |
|
|
57 |
|
|
|
58 |
match = re.match(birth_number, value) |
|
|
59 |
if not match: |
|
|
60 |
raise ValidationError(self.error_messages['invalid_format']) |
|
|
61 |
|
|
|
62 |
birth, id = match.groupdict()['birth'], match.groupdict()['id'] |
|
|
63 |
|
|
|
64 |
# Three digits for verificatin number were used until 1. january 1954 |
|
|
65 |
if len(id) == 3: |
|
|
66 |
return u'%s' % value |
|
|
67 |
|
|
|
68 |
# Birth number is in format YYMMDD. Females have month value raised by 50. |
|
|
69 |
# In case that all possible number are already used (for given date), |
|
|
70 |
# the month field is raised by 20. |
|
|
71 |
if gender is not None: |
|
|
72 |
if gender == 'f': |
|
|
73 |
female_const = 50 |
|
|
74 |
elif gender == 'm': |
|
|
75 |
female_const = 0 |
|
|
76 |
else: |
|
|
77 |
raise ValidationError(self.error_messages['invalid_gender']) |
|
|
78 |
|
|
|
79 |
month = int(birth[2:4]) - female_const |
|
|
80 |
if (not 1 <= month <= 12): |
|
|
81 |
if (not 1 <= (month - 20) <= 12): |
|
|
82 |
raise ValidationError(self.error_messages['invalid']) |
|
|
83 |
|
|
|
84 |
day = int(birth[4:6]) |
|
|
85 |
if not (1 <= day <= 31): |
|
|
86 |
raise ValidationError(self.error_messages['invalid']) |
|
|
87 |
|
|
|
88 |
# Fourth digit has been added since 1. January 1954. |
|
|
89 |
# It is modulo of dividing birth number and verification number by 11. |
|
|
90 |
# If the modulo were 10, the last number was 0 (and therefore, the whole |
|
|
91 |
# birth number wasn't divisable by 11. These number are no longer used (since 1985) |
|
|
92 |
# and the condition 'modulo == 10' can be removed in 2085. |
|
|
93 |
|
|
|
94 |
modulo = int(birth + id[:3]) % 11 |
|
|
95 |
|
|
|
96 |
if (modulo == int(id[-1])) or (modulo == 10 and id[-1] == '0'): |
|
|
97 |
return u'%s' % value |
|
|
98 |
else: |
|
|
99 |
raise ValidationError(self.error_messages['invalid']) |
|
|
100 |
|
|
|
101 |
class CZICNumberField(Field): |
|
|
102 |
""" |
|
|
103 |
Czech IC number field. |
|
|
104 |
""" |
|
|
105 |
default_error_messages = { |
|
|
106 |
'invalid': _(u'Enter a valid IC number.'), |
|
|
107 |
} |
|
|
108 |
|
|
|
109 |
def clean(self, value): |
|
|
110 |
super(CZICNumberField, self).__init__(value) |
|
|
111 |
|
|
|
112 |
if value in EMPTY_VALUES: |
|
|
113 |
return u'' |
|
|
114 |
|
|
|
115 |
match = re.match(ic_number, value) |
|
|
116 |
if not match: |
|
|
117 |
raise ValidationError(self.error_messages['invalid']) |
|
|
118 |
|
|
|
119 |
number, check = match.groupdict()['number'], int(match.groupdict()['check']) |
|
|
120 |
|
|
|
121 |
sum = 0 |
|
|
122 |
weight = 8 |
|
|
123 |
for digit in number: |
|
|
124 |
sum += int(digit)*weight |
|
|
125 |
weight -= 1 |
|
|
126 |
|
|
|
127 |
remainder = sum % 11 |
|
|
128 |
|
|
|
129 |
# remainder is equal: |
|
|
130 |
# 0 or 10: last digit is 1 |
|
|
131 |
# 1: last digit is 0 |
|
|
132 |
# in other case, last digin is 11 - remainder |
|
|
133 |
|
|
|
134 |
if (not remainder % 10 and check == 1) or \ |
|
|
135 |
(remainder == 1 and check == 0) or \ |
|
|
136 |
(check == (11 - remainder)): |
|
|
137 |
return u'%s' % value |
|
|
138 |
|
|
|
139 |
raise ValidationError(self.error_messages['invalid']) |
|
|
140 |
|