2009-02-22 15:51:57 +08:00
|
|
|
"""
|
|
|
|
Czech-specific form helpers
|
|
|
|
"""
|
|
|
|
|
2010-01-05 11:56:19 +08:00
|
|
|
from django.core.validators import EMPTY_VALUES
|
2009-02-22 15:51:57 +08:00
|
|
|
from django.forms import ValidationError
|
2010-01-05 11:56:19 +08:00
|
|
|
from django.forms.fields import Select, RegexField, Field
|
2009-02-22 15:51:57 +08:00
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
import re
|
|
|
|
|
|
|
|
birth_number = re.compile(r'^(?P<birth>\d{6})/?(?P<id>\d{3,4})$')
|
|
|
|
ic_number = re.compile(r'^(?P<number>\d{7})(?P<check>\d)$')
|
|
|
|
|
|
|
|
class CZRegionSelect(Select):
|
|
|
|
"""
|
|
|
|
A select widget widget with list of Czech regions as choices.
|
|
|
|
"""
|
|
|
|
def __init__(self, attrs=None):
|
|
|
|
from cz_regions import REGION_CHOICES
|
|
|
|
super(CZRegionSelect, self).__init__(attrs, choices=REGION_CHOICES)
|
|
|
|
|
|
|
|
class CZPostalCodeField(RegexField):
|
|
|
|
"""
|
|
|
|
A form field that validates its input as Czech postal code.
|
|
|
|
Valid form is XXXXX or XXX XX, where X represents integer.
|
|
|
|
"""
|
|
|
|
default_error_messages = {
|
|
|
|
'invalid': _(u'Enter a postal code in the format XXXXX or XXX XX.'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
super(CZPostalCodeField, self).__init__(r'^\d{5}$|^\d{3} \d{2}$',
|
|
|
|
max_length=None, min_length=None, *args, **kwargs)
|
|
|
|
|
|
|
|
def clean(self, value):
|
|
|
|
"""
|
|
|
|
Validates the input and returns a string that contains only numbers.
|
|
|
|
Returns an empty string for empty values.
|
|
|
|
"""
|
|
|
|
v = super(CZPostalCodeField, self).clean(value)
|
|
|
|
return v.replace(' ', '')
|
|
|
|
|
|
|
|
class CZBirthNumberField(Field):
|
|
|
|
"""
|
|
|
|
Czech birth number field.
|
|
|
|
"""
|
|
|
|
default_error_messages = {
|
|
|
|
'invalid_format': _(u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'),
|
|
|
|
'invalid_gender': _(u'Invalid optional parameter Gender, valid values are \'f\' and \'m\''),
|
|
|
|
'invalid': _(u'Enter a valid birth number.'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def clean(self, value, gender=None):
|
2010-11-21 22:06:32 +08:00
|
|
|
super(CZBirthNumberField, self).clean(value)
|
2009-02-22 15:51:57 +08:00
|
|
|
|
|
|
|
if value in EMPTY_VALUES:
|
|
|
|
return u''
|
|
|
|
|
|
|
|
match = re.match(birth_number, value)
|
|
|
|
if not match:
|
|
|
|
raise ValidationError(self.error_messages['invalid_format'])
|
|
|
|
|
|
|
|
birth, id = match.groupdict()['birth'], match.groupdict()['id']
|
|
|
|
|
2010-12-25 02:19:48 +08:00
|
|
|
# Three digits for verification number were used until 1. january 1954
|
2009-02-22 15:51:57 +08:00
|
|
|
if len(id) == 3:
|
|
|
|
return u'%s' % value
|
|
|
|
|
|
|
|
# Birth number is in format YYMMDD. Females have month value raised by 50.
|
|
|
|
# In case that all possible number are already used (for given date),
|
2010-12-25 02:19:48 +08:00
|
|
|
# the month field is raised by 20.
|
2009-02-22 15:51:57 +08:00
|
|
|
if gender is not None:
|
2010-12-25 02:19:48 +08:00
|
|
|
import warnings
|
|
|
|
warnings.warn(
|
|
|
|
"Support for validating the gender of a CZ Birth number has been deprecated.",
|
2010-12-27 15:41:26 +08:00
|
|
|
PendingDeprecationWarning)
|
2009-02-22 15:51:57 +08:00
|
|
|
if gender == 'f':
|
|
|
|
female_const = 50
|
|
|
|
elif gender == 'm':
|
|
|
|
female_const = 0
|
|
|
|
else:
|
|
|
|
raise ValidationError(self.error_messages['invalid_gender'])
|
|
|
|
|
|
|
|
month = int(birth[2:4]) - female_const
|
|
|
|
if (not 1 <= month <= 12):
|
|
|
|
if (not 1 <= (month - 20) <= 12):
|
|
|
|
raise ValidationError(self.error_messages['invalid'])
|
|
|
|
|
|
|
|
day = int(birth[4:6])
|
|
|
|
if not (1 <= day <= 31):
|
|
|
|
raise ValidationError(self.error_messages['invalid'])
|
|
|
|
|
|
|
|
# Fourth digit has been added since 1. January 1954.
|
|
|
|
# It is modulo of dividing birth number and verification number by 11.
|
|
|
|
# If the modulo were 10, the last number was 0 (and therefore, the whole
|
|
|
|
# birth number wasn't divisable by 11. These number are no longer used (since 1985)
|
|
|
|
# and the condition 'modulo == 10' can be removed in 2085.
|
|
|
|
|
|
|
|
modulo = int(birth + id[:3]) % 11
|
|
|
|
|
|
|
|
if (modulo == int(id[-1])) or (modulo == 10 and id[-1] == '0'):
|
|
|
|
return u'%s' % value
|
|
|
|
else:
|
|
|
|
raise ValidationError(self.error_messages['invalid'])
|
|
|
|
|
|
|
|
class CZICNumberField(Field):
|
|
|
|
"""
|
|
|
|
Czech IC number field.
|
|
|
|
"""
|
|
|
|
default_error_messages = {
|
|
|
|
'invalid': _(u'Enter a valid IC number.'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def clean(self, value):
|
2010-11-21 22:06:32 +08:00
|
|
|
super(CZICNumberField, self).clean(value)
|
2009-02-22 15:51:57 +08:00
|
|
|
|
|
|
|
if value in EMPTY_VALUES:
|
|
|
|
return u''
|
|
|
|
|
|
|
|
match = re.match(ic_number, value)
|
|
|
|
if not match:
|
|
|
|
raise ValidationError(self.error_messages['invalid'])
|
|
|
|
|
|
|
|
number, check = match.groupdict()['number'], int(match.groupdict()['check'])
|
|
|
|
|
|
|
|
sum = 0
|
|
|
|
weight = 8
|
|
|
|
for digit in number:
|
|
|
|
sum += int(digit)*weight
|
|
|
|
weight -= 1
|
|
|
|
|
|
|
|
remainder = sum % 11
|
|
|
|
|
|
|
|
# remainder is equal:
|
|
|
|
# 0 or 10: last digit is 1
|
|
|
|
# 1: last digit is 0
|
|
|
|
# in other case, last digin is 11 - remainder
|
|
|
|
|
|
|
|
if (not remainder % 10 and check == 1) or \
|
|
|
|
(remainder == 1 and check == 0) or \
|
|
|
|
(check == (11 - remainder)):
|
|
|
|
return u'%s' % value
|
|
|
|
|
|
|
|
raise ValidationError(self.error_messages['invalid'])
|
|
|
|
|