diff --git a/django/contrib/localflavor/pl/forms.py b/django/contrib/localflavor/pl/forms.py index d1e9773a80..ef4a38b7de 100644 --- a/django/contrib/localflavor/pl/forms.py +++ b/django/contrib/localflavor/pl/forms.py @@ -44,7 +44,7 @@ class PLPESELField(RegexField): super(PLPESELField, self).__init__(r'^\d{11}$', max_length=None, min_length=None, *args, **kwargs) - def clean(self,value): + def clean(self, value): super(PLPESELField, self).clean(value) if value in EMPTY_VALUES: return u'' @@ -62,6 +62,60 @@ class PLPESELField(RegexField): result += int(number[i]) * multiple_table[i] return result % 10 == 0 +class PLNationalIDCardNumberField(RegexField): + """ + A form field that validates as Polish National ID Card Number. + + Checks the following rules: + * the length consist of 3 letter and 6 digits + * has a valid checksum + + The algorithm is documented at http://en.wikipedia.org/wiki/Polish_identity_card. + """ + default_error_messages = { + 'invalid': _(u'National ID Card Number consists of 3 letters and 6 digits.'), + 'checksum': _(u'Wrong checksum for the National ID Card Number.'), + } + + def __init__(self, *args, **kwargs): + super(PLNationalIDCardNumberField, self).__init__(r'^[A-Za-z]{3}\d{6}$', + max_length=None, min_length=None, *args, **kwargs) + + def clean(self,value): + super(PLNationalIDCardNumberField, self).clean(value) + if value in EMPTY_VALUES: + return u'' + + value = value.upper() + + if not self.has_valid_checksum(value): + raise ValidationError(self.error_messages['checksum']) + return u'%s' % value + + def has_valid_checksum(self, number): + """ + Calculates a checksum with the provided algorithm. + """ + letter_dict = {'A': 10, 'B': 11, 'C': 12, 'D': 13, + 'E': 14, 'F': 15, 'G': 16, 'H': 17, + 'I': 18, 'J': 19, 'K': 20, 'L': 21, + 'M': 22, 'N': 23, 'O': 24, 'P': 25, + 'Q': 26, 'R': 27, 'S': 28, 'T': 29, + 'U': 30, 'V': 31, 'W': 32, 'X': 33, + 'Y': 34, 'Z': 35} + + # convert letters to integer values + int_table = [(not c.isdigit()) and letter_dict[c] or int(c) + for c in number] + + multiple_table = (7, 3, 1, -1, 7, 3, 1, 7, 3) + result = 0 + for i in range(len(int_table)): + result += int_table[i] * multiple_table[i] + + return result % 10 == 0 + + class PLNIPField(RegexField): """ A form field that validates as Polish Tax Number (NIP). @@ -158,3 +212,4 @@ class PLPostalCodeField(RegexField): def __init__(self, *args, **kwargs): super(PLPostalCodeField, self).__init__(r'^\d{2}-\d{3}$', max_length=None, min_length=None, *args, **kwargs) + diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index 59ce7bf3c5..4935382093 100644 --- a/docs/ref/contrib/localflavor.txt +++ b/docs/ref/contrib/localflavor.txt @@ -759,6 +759,17 @@ Poland (``pl``) .. _PESEL: http://en.wikipedia.org/wiki/PESEL +.. versionadded:: 1.4 + +.. class:: pl.forms.PLNationalIDCardNumberField + + A form field that validates input as a Polish National ID Card number. The + valid format is AAAXXXXXX, where A is letter (A-Z), X is digit and left-most + digit is checksum digit. More information about checksum calculation algorithm + see `Polish identity card`_. + +.. _`Polish identity card`: http://en.wikipedia.org/wiki/Polish_identity_card + .. class:: pl.forms.PLREGONField A form field that validates input as a Polish National Official Business diff --git a/tests/regressiontests/forms/localflavor/pl.py b/tests/regressiontests/forms/localflavor/pl.py index 51721f83f8..7225abe87d 100644 --- a/tests/regressiontests/forms/localflavor/pl.py +++ b/tests/regressiontests/forms/localflavor/pl.py @@ -1,5 +1,5 @@ from django.contrib.localflavor.pl.forms import (PLProvinceSelect, - PLCountySelect, PLPostalCodeField, PLNIPField, PLPESELField, PLREGONField) + PLCountySelect, PLPostalCodeField, PLNIPField, PLPESELField, PLNationalIDCardNumberField, PLREGONField) from utils import LocalFlavorTestCase @@ -26,7 +26,7 @@ class PLLocalFlavorTests(LocalFlavorTestCase): ''' self.assertEqual(f.render('voivodeships', 'pomerania'), out) - + def test_PLCountrySelect(self): f = PLCountySelect() out = u'''''' self.assertEqual(f.render('administrativeunit', 'katowice'), out) - + def test_PLPostalCodeField(self): error_format = [u'Enter a postal code in the format XX-XXX.'] valid = { @@ -418,7 +418,7 @@ class PLLocalFlavorTests(LocalFlavorTestCase): '43--434': error_format, } self.assertFieldOutput(PLPostalCodeField, valid, invalid) - + def test_PLNIPField(self): error_format = [u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'] error_checksum = [u'Wrong checksum for the Tax Number (NIP).'] @@ -431,7 +431,7 @@ class PLLocalFlavorTests(LocalFlavorTestCase): '646-241-41-23': error_checksum, } self.assertFieldOutput(PLNIPField, valid, invalid) - + def test_PLPESELField(self): error_checksum = [u'Wrong checksum for the National Identification Number.'] error_format = [u'National Identification Number consists of 11 digits.'] @@ -444,7 +444,22 @@ class PLLocalFlavorTests(LocalFlavorTestCase): '800716106AA': error_format, } self.assertFieldOutput(PLPESELField, valid, invalid) - + + def test_PLNationalIDCardNumberField(self): + error_checksum = [u'Wrong checksum for the National ID Card Number.'] + error_format = [u'National ID Card Number consists of 3 letters and 6 digits.'] + valid = { + 'ABC123458': 'ABC123458', + 'abc123458': 'ABC123458', + } + invalid = { + 'ABC123457': error_checksum, + 'abc123457': error_checksum, + 'a12Aaaaaa': error_format, + 'AA1234443': error_format, + } + self.assertFieldOutput(PLNationalIDCardNumberField, valid, invalid) + def test_PLREGONField(self): error_checksum = [u'Wrong checksum for the National Business Register Number (REGON).'] error_format = [u'National Business Register Number (REGON) consists of 9 or 14 digits.'] @@ -459,4 +474,3 @@ class PLLocalFlavorTests(LocalFlavorTestCase): '590096': error_format, } self.assertFieldOutput(PLREGONField, valid, invalid) -