diff --git a/django/contrib/localflavor/cz/__init__.py b/django/contrib/localflavor/cz/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/django/contrib/localflavor/cz/cz_regions.py b/django/contrib/localflavor/cz/cz_regions.py new file mode 100644 index 0000000000..179e1861e2 --- /dev/null +++ b/django/contrib/localflavor/cz/cz_regions.py @@ -0,0 +1,22 @@ +""" +Czech regions, translations get from http://www.crwflags.com/fotw/Flags/cz-re.html +""" + +from django.utils.translation import ugettext_lazy as _ + +REGION_CHOICES = ( + ('PR', _('Prague')), + ('CE', _('Cenral Bohemian Region')), + ('SO', _('South Bohemian Region')), + ('PI', _('Pilsen Region')), + ('CA', _('Carlsbad Region')), + ('US', _('Usti Region')), + ('LB', _('Liberec Region')), + ('HK', _('Hradec Region')), + ('PA', _('Pardubice Region')), + ('VY', _('Vysocina Region')), + ('SM', _('South Moravian Region')), + ('OL', _('Olomouc Region')), + ('ZL', _('Zlin Region')), + ('MS', _('Moravian-Silesian Region')), +) diff --git a/django/contrib/localflavor/cz/forms.py b/django/contrib/localflavor/cz/forms.py new file mode 100644 index 0000000000..6c7a5bf5dd --- /dev/null +++ b/django/contrib/localflavor/cz/forms.py @@ -0,0 +1,140 @@ +""" +Czech-specific form helpers +""" + +from django.forms import ValidationError +from django.forms.fields import Select, RegexField, Field, EMPTY_VALUES +from django.utils.translation import ugettext_lazy as _ +import re + +birth_number = re.compile(r'^(?P\d{6})/?(?P\d{3,4})$') +ic_number = re.compile(r'^(?P\d{7})(?P\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): + super(CZBirthNumberField, self).__init__(value) + + 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'] + + # Three digits for verificatin number were used until 1. january 1954 + 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), + # the month field is raised by 20. + if gender is not None: + 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): + super(CZICNumberField, self).__init__(value) + + 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']) + diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 65522b0572..8e3044bae3 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -44,6 +44,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are: * Brazil_ * Canada_ * Chile_ + * Czech_ * Finland_ * France_ * Germany_ @@ -83,6 +84,7 @@ Here's an example of how to use them:: .. _Brazil: `Brazil (br)`_ .. _Canada: `Canada (ca)`_ .. _Chile: `Chile (cl)`_ +.. _Czech: `Czech (cz)`_ .. _Finland: `Finland (fi)`_ .. _France: `France (fr)`_ .. _Germany: `Germany (de)`_ @@ -231,6 +233,27 @@ Chile (``cl``) A ``Select`` widget that uses a list of Chilean regions (Regiones) as its choices. +Czech (``cz``) +============== + +.. class:: cz.forms.CZPostalCodeField + + A form field that validates input as a Czech postal code. Valid formats + are XXXXX or XXX XX, where X is a digit. + +.. class:: cz.forms.CZBirthNumberField + + A form field that validates input as a Czech Birth Number. + A valid number must be in format XXXXXX/XXXX (slash is optional). + +.. class:: cz.forms.CZICNumberField + + A form field that validates input as a Czech IC number field. + +.. class:: cz.forms.CZRegionSelect + + A ``Select`` widget that uses a list of Czech regions as its choices. + Finland (``fi``) ================ diff --git a/tests/regressiontests/forms/localflavor/cz.py b/tests/regressiontests/forms/localflavor/cz.py new file mode 100644 index 0000000000..5fb98a8374 --- /dev/null +++ b/tests/regressiontests/forms/localflavor/cz.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +# Tests for the contrib/localflavor/ CZ Form Fields + +tests = r""" +# CZPostalCodeField ######################################################### + +>>> from django.contrib.localflavor.cz.forms import CZPostalCodeField +>>> f = CZPostalCodeField() +>>> f.clean('84545x') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.'] +>>> f.clean('91909') +u'91909' +>>> f.clean('917 01') +u'91701' +>>> f.clean('12345') +u'12345' +>>> f.clean('123456') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.'] +>>> f.clean('1234') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.'] +>>> f.clean('123 4') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postal code in the format XXXXX or XXX XX.'] + +# CZRegionSelect ############################################################ + +>>> from django.contrib.localflavor.cz.forms import CZRegionSelect +>>> w = CZRegionSelect() +>>> w.render('regions', 'TT') +u'' + +# CZBirthNumberField ######################################################## + +>>> from django.contrib.localflavor.cz.forms import CZBirthNumberField +>>> f = CZBirthNumberField() +>>> f.clean('880523/1237') +u'880523/1237' +>>> f.clean('8805231237') +u'8805231237' +>>> f.clean('880523/000') +u'880523/000' +>>> f.clean('880523000') +u'880523000' +>>> f.clean('882101/0011') +u'882101/0011' +>>> f.clean('880523/1237', 'm') +u'880523/1237' +>>> f.clean('885523/1231', 'f') +u'885523/1231' +>>> f.clean('123456/12') +Traceback (most recent call last): +... +ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'] +>>> f.clean('123456/12345') +Traceback (most recent call last): +... +ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'] +>>> f.clean('12345612') +Traceback (most recent call last): +... +ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'] +>>> f.clean('12345612345') +Traceback (most recent call last): +... +ValidationError: [u'Enter a birth number in the format XXXXXX/XXXX or XXXXXXXXXX.'] +>>> f.clean('881523/0000', 'm') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] +>>> f.clean('885223/0000', 'm') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] +>>> f.clean('881223/0000', 'f') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] +>>> f.clean('886523/0000', 'f') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] +>>> f.clean('880523/1239') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] +>>> f.clean('8805231239') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] +>>> f.clean('990101/0011') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid birth number.'] + +# CZICNumberField ######################################################## + +>>> from django.contrib.localflavor.cz.forms import CZICNumberField +>>> f = CZICNumberField() +>>> f.clean('12345679') +u'12345679' +>>> f.clean('12345601') +u'12345601' +>>> f.clean('12345661') +u'12345661' +>>> f.clean('12345610') +u'12345610' +>>> f.clean('1234567') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IC number.'] +>>> f.clean('12345660') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IC number.'] +>>> f.clean('12345600') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid IC number.'] +""" diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 6a8b017f44..6d418fa5a3 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -10,6 +10,7 @@ from localflavor.br import tests as localflavor_br_tests from localflavor.ca import tests as localflavor_ca_tests from localflavor.ch import tests as localflavor_ch_tests from localflavor.cl import tests as localflavor_cl_tests +from localflavor.cz import tests as localflavor_cz_tests from localflavor.de import tests as localflavor_de_tests from localflavor.es import tests as localflavor_es_tests from localflavor.fi import tests as localflavor_fi_tests @@ -43,6 +44,7 @@ __test__ = { 'localflavor_ca_tests': localflavor_ca_tests, 'localflavor_ch_tests': localflavor_ch_tests, 'localflavor_cl_tests': localflavor_cl_tests, + 'localflavor_cz_tests': localflavor_cz_tests, 'localflavor_de_tests': localflavor_de_tests, 'localflavor_es_tests': localflavor_es_tests, 'localflavor_fi_tests': localflavor_fi_tests,