From b2a437765193131d649aad87de1dea60ffc34a72 Mon Sep 17 00:00:00 2001
From: Russell Keith-Magee <russell@keith-magee.com>
Date: Sun, 22 Feb 2009 07:51:57 +0000
Subject: [PATCH] Fixed #9066 -- Added Czech localflavor. Thanks to Elvard for
 the contribution.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9876 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 django/contrib/localflavor/cz/__init__.py     |   0
 django/contrib/localflavor/cz/cz_regions.py   |  22 +++
 django/contrib/localflavor/cz/forms.py        | 140 ++++++++++++++++++
 docs/ref/contrib/localflavor.txt              |  23 +++
 tests/regressiontests/forms/localflavor/cz.py | 126 ++++++++++++++++
 tests/regressiontests/forms/tests.py          |   2 +
 6 files changed, 313 insertions(+)
 create mode 100644 django/contrib/localflavor/cz/__init__.py
 create mode 100644 django/contrib/localflavor/cz/cz_regions.py
 create mode 100644 django/contrib/localflavor/cz/forms.py
 create mode 100644 tests/regressiontests/forms/localflavor/cz.py

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<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):
+        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'<select name="regions">\n<option value="PR">Prague</option>\n<option value="CE">Cenral Bohemian Region</option>\n<option value="SO">South Bohemian Region</option>\n<option value="PI">Pilsen Region</option>\n<option value="CA">Carlsbad Region</option>\n<option value="US">Usti Region</option>\n<option value="LB">Liberec Region</option>\n<option value="HK">Hradec Region</option>\n<option value="PA">Pardubice Region</option>\n<option value="VY">Vysocina Region</option>\n<option value="SM">South Moravian Region</option>\n<option value="OL">Olomouc Region</option>\n<option value="ZL">Zlin Region</option>\n<option value="MS">Moravian-Silesian Region</option>\n</select>'
+
+# 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,