Fixed #15856 -- Added Macedonian localflavor. Many thanks to vasiliyeah.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16385 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2011-06-12 13:31:40 +00:00
parent 9f3d76a921
commit 050e11956f
13 changed files with 622 additions and 3 deletions

View File

@ -0,0 +1,100 @@
import datetime
from django.core.validators import EMPTY_VALUES
from django.forms import ValidationError
from django.forms.fields import RegexField, Select
from django.utils.translation import ugettext_lazy as _
from mk_choices import MK_MUNICIPALITIES
class MKIdentityCardNumberField(RegexField):
"""
A Macedonian ID card number. Accepts both old and new format.
"""
default_error_messages = {
'invalid': _(u'Identity card numbers must contain'
' either 4 to 7 digits or an uppercase letter and 7 digits.'),
}
def __init__(self, *args, **kwargs):
kwargs['min_length'] = None
kwargs['max_length'] = 8
regex = ur'(^[A-Z]{1}\d{7}$)|(^\d{4,7}$)'
super(MKIdentityCardNumberField, self).__init__(regex, *args, **kwargs)
class MKMunicipalitySelect(Select):
"""
A form ``Select`` widget that uses a list of Macedonian municipalities as
choices. The label is the name of the municipality and the value
is a 2 character code for the municipality.
"""
def __init__(self, attrs=None):
super(MKMunicipalitySelect, self).__init__(attrs, choices = MK_MUNICIPALITIES)
class UMCNField(RegexField):
"""
A form field that validates input as a unique master citizen
number.
The format of the unique master citizen number has been kept the same from
Yugoslavia. It is still in use in other countries as well, it is not applicable
solely in Macedonia. For more information see:
https://secure.wikimedia.org/wikipedia/en/wiki/Unique_Master_Citizen_Number
A value will pass validation if it complies to the following rules:
* Consists of exactly 13 digits
* The first 7 digits represent a valid past date in the format DDMMYYY
* The last digit of the UMCN passes a checksum test
"""
default_error_messages = {
'invalid': _(u'This field should contain exactly 13 digits.'),
'date': _(u'The first 7 digits of the UMCN must represent a valid past date.'),
'checksum': _(u'The UMCN is not valid.'),
}
def __init__(self, *args, **kwargs):
kwargs['min_length'] = None
kwargs['max_length'] = 13
super(UMCNField, self).__init__(r'^\d{13}$', *args, **kwargs)
def clean(self, value):
value = super(UMCNField, self).clean(value)
if value in EMPTY_VALUES:
return u''
if not self._validate_date_part(value):
raise ValidationError(self.error_messages['date'])
if self._validate_checksum(value):
return value
else:
raise ValidationError(self.error_messages['checksum'])
def _validate_checksum(self, value):
a,b,c,d,e,f,g,h,i,j,k,l,K = [int(digit) for digit in value]
m = 11 - (( 7*(a+g) + 6*(b+h) + 5*(c+i) + 4*(d+j) + 3*(e+k) + 2*(f+l)) % 11)
if (m >= 1 and m <= 9) and K == m:
return True
elif m == 11 and K == 0:
return True
else:
return False
def _validate_date_part(self, value):
daypart, monthpart, yearpart = int(value[:2]), int(value[2:4]), int(value[4:7])
if yearpart >= 800:
yearpart += 1000
else:
yearpart += 2000
try:
date = datetime.datetime(year = yearpart, month = monthpart, day = daypart).date()
except ValueError:
return False
if date >= datetime.datetime.now().date():
return False
return True

View File

@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-
"""
Macedonian municipalities per the reorganization from 2004.
"""
from django.utils.translation import ugettext_lazy as _
MK_MUNICIPALITIES = (
('AD', _(u'Aerodrom')),
('AR', _(u'Aračinovo')),
('BR', _(u'Berovo')),
('TL', _(u'Bitola')),
('BG', _(u'Bogdanci')),
('VJ', _(u'Bogovinje')),
('BS', _(u'Bosilovo')),
('BN', _(u'Brvenica')),
('BU', _(u'Butel')),
('VA', _(u'Valandovo')),
('VL', _(u'Vasilevo')),
('VV', _(u'Vevčani')),
('VE', _(u'Veles')),
('NI', _(u'Vinica')),
('VC', _(u'Vraneštica')),
('VH', _(u'Vrapčište')),
('GB', _(u'Gazi Baba')),
('GV', _(u'Gevgelija')),
('GT', _(u'Gostivar')),
('GR', _(u'Gradsko')),
('DB', _(u'Debar')),
('DA', _(u'Debarca')),
('DL', _(u'Delčevo')),
('DK', _(u'Demir Kapija')),
('DM', _(u'Demir Hisar')),
('DE', _(u'Dolneni')),
('DR', _(u'Drugovo')),
('GP', _(u'Gjorče Petrov')),
('ZE', _(u'Želino')),
('ZA', _(u'Zajas')),
('ZK', _(u'Zelenikovo')),
('ZR', _(u'Zrnovci')),
('IL', _(u'Ilinden')),
('JG', _(u'Jegunovce')),
('AV', _(u'Kavadarci')),
('KB', _(u'Karbinci')),
('KX', _(u'Karpoš')),
('VD', _(u'Kisela Voda')),
('KH', _(u'Kičevo')),
('KN', _(u'Konče')),
('OC', _(u'Koćani')),
('KY', _(u'Kratovo')),
('KZ', _(u'Kriva Palanka')),
('KG', _(u'Krivogaštani')),
('KS', _(u'Kruševo')),
('UM', _(u'Kumanovo')),
('LI', _(u'Lipkovo')),
('LO', _(u'Lozovo')),
('MR', _(u'Mavrovo i Rostuša')),
('MK', _(u'Makedonska Kamenica')),
('MD', _(u'Makedonski Brod')),
('MG', _(u'Mogila')),
('NG', _(u'Negotino')),
('NV', _(u'Novaci')),
('NS', _(u'Novo Selo')),
('OS', _(u'Oslomej')),
('OD', _(u'Ohrid')),
('PE', _(u'Petrovec')),
('PH', _(u'Pehčevo')),
('PN', _(u'Plasnica')),
('PP', _(u'Prilep')),
('PT', _(u'Probištip')),
('RV', _(u'Radoviš')),
('RN', _(u'Rankovce')),
('RE', _(u'Resen')),
('RO', _(u'Rosoman')),
('AJ', _(u'Saraj')),
('SL', _(u'Sveti Nikole')),
('SS', _(u'Sopište')),
('SD', _(u'Star Dojran')),
('NA', _(u'Staro Nagoričane')),
('UG', _(u'Struga')),
('RU', _(u'Strumica')),
('SU', _(u'Studeničani')),
('TR', _(u'Tearce')),
('ET', _(u'Tetovo')),
('CE', _(u'Centar')),
('CZ', _(u'Centar-Župa')),
('CI', _(u'Čair')),
('CA', _(u'Čaška')),
('CH', _(u'Češinovo-Obleševo')),
('CS', _(u'Čučer-Sandevo')),
('ST', _(u'Štip')),
('SO', _(u'Šuto Orizari')),
)

View File

@ -0,0 +1,44 @@
from django.db.models.fields import CharField
from django.utils.translation import ugettext_lazy as _
from django.contrib.localflavor.mk.mk_choices import MK_MUNICIPALITIES
from django.contrib.localflavor.mk.forms import (UMCNField as UMCNFormField,
MKIdentityCardNumberField as MKIdentityCardNumberFormField)
class MKIdentityCardNumberField(CharField):
description = _("Macedonian identity card number")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 8
super(MKIdentityCardNumberField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {'form_class' : MKIdentityCardNumberFormField}
defaults.update(kwargs)
return super(MKIdentityCardNumberField, self).formfield(**defaults)
class MKMunicipalityField(CharField):
description = _("A Macedonian municipality (2 character code)")
def __init__(self, *args, **kwargs):
kwargs['choices'] = MK_MUNICIPALITIES
kwargs['max_length'] = 2
super(MKMunicipalityField, self).__init__(*args, **kwargs)
class UMCNField(CharField):
description = _("Unique master citizen number (13 digits)")
def __init__(self, *args, **kwargs):
kwargs['max_length'] = 13
super(UMCNField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
defaults = {'form_class' : UMCNFormField}
defaults.update(kwargs)
return super(UMCNField, self).formfield(**defaults)

View File

@ -57,6 +57,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
* Italy_
* Japan_
* Kuwait_
* Macedonia_
* Mexico_
* `The Netherlands`_
* Norway_
@ -110,6 +111,7 @@ Here's an example of how to use them::
.. _Italy: `Italy (it)`_
.. _Japan: `Japan (jp)`_
.. _Kuwait: `Kuwait (kw)`_
.. _Macedonia: `Macedonia (mk)`_
.. _Mexico: `Mexico (mx)`_
.. _Norway: `Norway (no)`_
.. _Peru: `Peru (pe)`_
@ -652,8 +654,6 @@ Israel (``il``)
.. _Israeli identification number: http://he.wikipedia.org/wiki/%D7%9E%D7%A1%D7%A4%D7%A8_%D7%96%D7%94%D7%95%D7%AA_(%D7%99%D7%A9%D7%A8%D7%90%D7%9C)
.. _Luhn algorithm: http://en.wikipedia.org/wiki/Luhn_algorithm
Italy (``it``)
==============
@ -705,6 +705,57 @@ Kuwait (``kw``)
* The birthdate of the person is a valid date.
* The calculated checksum equals to the last digit of the Civil ID.
Macedonia (``mk``)
===================
.. versionadded:: 1.4
.. class:: mk.forms.MKIdentityCardNumberField
A form field that validates input as a Macedonian identity card number.
Both old and new identity card numbers are supported.
.. class:: mk.forms.MKMunicipalitySelect
A form ``Select`` widget that uses a list of Macedonian municipalities as
choices.
.. class:: mk.forms.UMCNField
A form field that validates input as a unique master citizen
number.
The format of the unique master citizen number is not unique
to Macedonia. For more information see:
https://secure.wikimedia.org/wikipedia/en/wiki/Unique_Master_Citizen_Number
A value will pass validation if it complies to the following rules:
* Consists of exactly 13 digits
* The first 7 digits represent a valid past date in the format DDMMYYY
* The last digit of the UMCN passes a checksum test
.. class:: mk.models.MKIdentityCardNumberField
A model field that forms represent as a
``forms.MKIdentityCardNumberField`` field.
.. class:: mk.models.MKMunicipalityField
A model field that forms represent as a
``forms.MKMunicipalitySelect`` and stores the 2 character code of the
municipality in the database.
.. class:: mk.models.UMCNField
A model field that forms represent as a ``forms.UMCNField`` field.
Mexico (``mx``)
===============

View File

@ -0,0 +1,131 @@
from django.contrib.localflavor.mk.forms import (
MKIdentityCardNumberField, MKMunicipalitySelect, UMCNField)
from utils import LocalFlavorTestCase
class MKLocalFlavorTests(LocalFlavorTestCase):
def test_MKIdentityCardNumberField(self):
error_invalid = (u'Identity card numbers must contain'
' either 4 to 7 digits or an uppercase letter and 7 digits.')
valid = {
'L0018077':'L0018077',
'A0078315' : 'A0078315',
}
invalid = {
'123': error_invalid,
'abcdf': error_invalid,
'234390a': error_invalid,
}
self.assertFieldOutput(MKIdentityCardNumberField, valid, invalid)
def test_MKMunicipalitySelect(self):
f = MKMunicipalitySelect()
out=u'''<select name="municipality">
<option value="AD">Aerodrom</option>
<option value="AR">Ara\u010dinovo</option>
<option value="BR">Berovo</option>
<option value="TL">Bitola</option>
<option value="BG">Bogdanci</option>
<option value="VJ">Bogovinje</option>
<option value="BS">Bosilovo</option>
<option value="BN">Brvenica</option>
<option value="BU">Butel</option>
<option value="VA">Valandovo</option>
<option value="VL">Vasilevo</option>
<option value="VV">Vev\u010dani</option>
<option value="VE">Veles</option>
<option value="NI">Vinica</option>
<option value="VC">Vrane\u0161tica</option>
<option value="VH">Vrap\u010di\u0161te</option>
<option value="GB">Gazi Baba</option>
<option value="GV">Gevgelija</option>
<option value="GT">Gostivar</option>
<option value="GR">Gradsko</option>
<option value="DB">Debar</option>
<option value="DA">Debarca</option>
<option value="DL" selected="selected">Del\u010devo</option>
<option value="DK">Demir Kapija</option>
<option value="DM">Demir Hisar</option>
<option value="DE">Dolneni</option>
<option value="DR">Drugovo</option>
<option value="GP">Gjor\u010de Petrov</option>
<option value="ZE">\u017delino</option>
<option value="ZA">Zajas</option>
<option value="ZK">Zelenikovo</option>
<option value="ZR">Zrnovci</option>
<option value="IL">Ilinden</option>
<option value="JG">Jegunovce</option>
<option value="AV">Kavadarci</option>
<option value="KB">Karbinci</option>
<option value="KX">Karpo\u0161</option>
<option value="VD">Kisela Voda</option>
<option value="KH">Ki\u010devo</option>
<option value="KN">Kon\u010de</option>
<option value="OC">Ko\u0107ani</option>
<option value="KY">Kratovo</option>
<option value="KZ">Kriva Palanka</option>
<option value="KG">Krivoga\u0161tani</option>
<option value="KS">Kru\u0161evo</option>
<option value="UM">Kumanovo</option>
<option value="LI">Lipkovo</option>
<option value="LO">Lozovo</option>
<option value="MR">Mavrovo i Rostu\u0161a</option>
<option value="MK">Makedonska Kamenica</option>
<option value="MD">Makedonski Brod</option>
<option value="MG">Mogila</option>
<option value="NG">Negotino</option>
<option value="NV">Novaci</option>
<option value="NS">Novo Selo</option>
<option value="OS">Oslomej</option>
<option value="OD">Ohrid</option>
<option value="PE">Petrovec</option>
<option value="PH">Peh\u010devo</option>
<option value="PN">Plasnica</option>
<option value="PP">Prilep</option>
<option value="PT">Probi\u0161tip</option>
<option value="RV">Radovi\u0161</option>
<option value="RN">Rankovce</option>
<option value="RE">Resen</option>
<option value="RO">Rosoman</option>
<option value="AJ">Saraj</option>
<option value="SL">Sveti Nikole</option>
<option value="SS">Sopi\u0161te</option>
<option value="SD">Star Dojran</option>
<option value="NA">Staro Nagori\u010dane</option>
<option value="UG">Struga</option>
<option value="RU">Strumica</option>
<option value="SU">Studeni\u010dani</option>
<option value="TR">Tearce</option>
<option value="ET">Tetovo</option>
<option value="CE">Centar</option>
<option value="CZ">Centar-\u017dupa</option>
<option value="CI">\u010cair</option>
<option value="CA">\u010ca\u0161ka</option>
<option value="CH">\u010ce\u0161inovo-Oble\u0161evo</option>
<option value="CS">\u010cu\u010der-Sandevo</option>
<option value="ST">\u0160tip</option>
<option value="SO">\u0160uto Orizari</option>
</select>'''
self.assertEqual(f.render('municipality', 'DL' ), out)
def test_UMCNField(self):
error_invalid = [u'This field should contain exactly 13 digits.']
error_checksum = [u'The UMCN is not valid.']
error_date = [u'The first 7 digits of the UMCN '
'must represent a valid past date.']
valid = {
'2402983450006': '2402983450006',
'2803984430038': '2803984430038',
'1909982045004': '1909982045004',
}
invalid = {
'240298345': error_invalid,
'abcdefghj': error_invalid,
'2402082450006': error_date,
'3002982450006': error_date,
'2402983450007': error_checksum,
'2402982450006': error_checksum,
}
self.assertFieldOutput(UMCNField, valid, invalid)

View File

@ -23,6 +23,7 @@ from localflavor.is_ import ISLocalFlavorTests
from localflavor.it import ITLocalFlavorTests
from localflavor.jp import JPLocalFlavorTests
from localflavor.kw import KWLocalFlavorTests
from localflavor.mk import MKLocalFlavorTests
from localflavor.nl import NLLocalFlavorTests
from localflavor.pl import PLLocalFlavorTests
from localflavor.pt import PTLocalFlavorTests

View File

@ -36,6 +36,7 @@ from regressiontests.forms.localflavortests import (
ITLocalFlavorTests,
JPLocalFlavorTests,
KWLocalFlavorTests,
MKLocalFlavorTests,
NLLocalFlavorTests,
PLLocalFlavorTests,
PTLocalFlavorTests,

View File

@ -0,0 +1,7 @@
from django.forms import ModelForm
from models import MKPerson
class MKPersonForm(ModelForm):
class Meta:
model = MKPerson

View File

@ -0,0 +1,14 @@
from django.db import models
from django.contrib.localflavor.mk.models import (
MKIdentityCardNumberField, MKMunicipalityField, UMCNField)
class MKPerson(models.Model):
first_name = models.CharField(max_length = 20)
last_name = models.CharField(max_length = 20)
umcn = UMCNField()
id_number = MKIdentityCardNumberField()
municipality = MKMunicipalityField(blank = True)
municipality_req = MKMunicipalityField(blank = False)
class Meta:
app_label = 'localflavor'

View File

@ -0,0 +1,176 @@
from django.test import TestCase
from forms import MKPersonForm
class MKLocalflavorTests(TestCase):
def setUp(self):
self.form = MKPersonForm({
'first_name':'Someone',
'last_name':'Something',
'umcn': '2402983450006',
'municipality':'OD',
'municipality_req':'VE',
'id_number':'A1234567',
})
def test_get_display_methods(self):
"""
Test that the get_*_display() methods are added to the model instances.
"""
person = self.form.save()
self.assertEqual(person.get_municipality_display(), 'Ohrid')
self.assertEqual(person.get_municipality_req_display(), 'Veles')
def test_municipality_required(self):
"""
Test that required MKMunicipalityFields throw appropriate errors.
"""
form = MKPersonForm({
'first_name':'Someone',
'last_name':'Something',
'umcn': '2402983450006',
'municipality':'OD',
'id_number':'A1234567',
})
self.assertFalse(form.is_valid())
self.assertEqual(
form.errors['municipality_req'], [u'This field is required.'])
def test_umcn_invalid(self):
"""
Test that UMCNFields throw appropriate errors for invalid UMCNs.
"""
form = MKPersonForm({
'first_name':'Someone',
'last_name':'Something',
'umcn': '2402983450007',
'municipality':'OD',
'municipality_req':'VE',
'id_number':'A1234567',
})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['umcn'], [u'The UMCN is not valid.'])
form = MKPersonForm({
'first_name':'Someone',
'last_name':'Something',
'umcn': '3002983450007',
'municipality':'OD',
'municipality_req':'VE',
'id_number':'A1234567',
})
self.assertEqual(form.errors['umcn'],
[u'The first 7 digits of the UMCN must represent a valid past date.'])
def test_idnumber_invalid(self):
"""
Test that MKIdentityCardNumberFields throw
appropriate errors for invalid values
"""
form = MKPersonForm({
'first_name':'Someone',
'last_name':'Something',
'umcn': '2402983450007',
'municipality':'OD',
'municipality_req':'VE',
'id_number':'A123456a',
})
self.assertFalse(form.is_valid())
self.assertEqual(form.errors['id_number'],
[u'Identity card numbers must contain either 4 to 7 '
'digits or an uppercase letter and 7 digits.'])
def test_field_blank_option(self):
"""
Test that the empty option is there.
"""
municipality_select_html = """\
<select name="municipality" id="id_municipality">
<option value="">---------</option>
<option value="AD">Aerodrom</option>
<option value="AR">Ara\xc4\x8dinovo</option>
<option value="BR">Berovo</option>
<option value="TL">Bitola</option>
<option value="BG">Bogdanci</option>
<option value="VJ">Bogovinje</option>
<option value="BS">Bosilovo</option>
<option value="BN">Brvenica</option>
<option value="BU">Butel</option>
<option value="VA">Valandovo</option>
<option value="VL">Vasilevo</option>
<option value="VV">Vev\xc4\x8dani</option>
<option value="VE">Veles</option>
<option value="NI">Vinica</option>
<option value="VC">Vrane\xc5\xa1tica</option>
<option value="VH">Vrap\xc4\x8di\xc5\xa1te</option>
<option value="GB">Gazi Baba</option>
<option value="GV">Gevgelija</option>
<option value="GT">Gostivar</option>
<option value="GR">Gradsko</option>
<option value="DB">Debar</option>
<option value="DA">Debarca</option>
<option value="DL">Del\xc4\x8devo</option>
<option value="DK">Demir Kapija</option>
<option value="DM">Demir Hisar</option>
<option value="DE">Dolneni</option>
<option value="DR">Drugovo</option>
<option value="GP">Gjor\xc4\x8de Petrov</option>
<option value="ZE">\xc5\xbdelino</option>
<option value="ZA">Zajas</option>
<option value="ZK">Zelenikovo</option>
<option value="ZR">Zrnovci</option>
<option value="IL">Ilinden</option>
<option value="JG">Jegunovce</option>
<option value="AV">Kavadarci</option>
<option value="KB">Karbinci</option>
<option value="KX">Karpo\xc5\xa1</option>
<option value="VD">Kisela Voda</option>
<option value="KH">Ki\xc4\x8devo</option>
<option value="KN">Kon\xc4\x8de</option>
<option value="OC">Ko\xc4\x87ani</option>
<option value="KY">Kratovo</option>
<option value="KZ">Kriva Palanka</option>
<option value="KG">Krivoga\xc5\xa1tani</option>
<option value="KS">Kru\xc5\xa1evo</option>
<option value="UM">Kumanovo</option>
<option value="LI">Lipkovo</option>
<option value="LO">Lozovo</option>
<option value="MR">Mavrovo i Rostu\xc5\xa1a</option>
<option value="MK">Makedonska Kamenica</option>
<option value="MD">Makedonski Brod</option>
<option value="MG">Mogila</option>
<option value="NG">Negotino</option>
<option value="NV">Novaci</option>
<option value="NS">Novo Selo</option>
<option value="OS">Oslomej</option>
<option value="OD" selected="selected">Ohrid</option>
<option value="PE">Petrovec</option>
<option value="PH">Peh\xc4\x8devo</option>
<option value="PN">Plasnica</option>
<option value="PP">Prilep</option>
<option value="PT">Probi\xc5\xa1tip</option>
<option value="RV">Radovi\xc5\xa1</option>
<option value="RN">Rankovce</option>
<option value="RE">Resen</option>
<option value="RO">Rosoman</option>
<option value="AJ">Saraj</option>
<option value="SL">Sveti Nikole</option>
<option value="SS">Sopi\xc5\xa1te</option>
<option value="SD">Star Dojran</option>
<option value="NA">Staro Nagori\xc4\x8dane</option>
<option value="UG">Struga</option>
<option value="RU">Strumica</option>
<option value="SU">Studeni\xc4\x8dani</option>
<option value="TR">Tearce</option>
<option value="ET">Tetovo</option>
<option value="CE">Centar</option>
<option value="CZ">Centar-\xc5\xbdupa</option>
<option value="CI">\xc4\x8cair</option>
<option value="CA">\xc4\x8ca\xc5\xa1ka</option>
<option value="CH">\xc4\x8ce\xc5\xa1inovo-Oble\xc5\xa1evo</option>
<option value="CS">\xc4\x8cu\xc4\x8der-Sandevo</option>
<option value="ST">\xc5\xa0tip</option>
<option value="SO">\xc5\xa0uto Orizari</option>
</select>"""
self.assertEqual(str(self.form['municipality']), municipality_select_html)

View File

@ -2,5 +2,7 @@ from django.test import TestCase
from django.utils import unittest
# just import your tests here
from us.tests import *
from au.tests import *
from mk.tests import *
from us.tests import *