Fixed #12379 -- Added Chinese (cn) localflavor package. Thanks, Xia Kai, Daniel Duan, DaNmarner and Łukasz Rekucki.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16070 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Jannis Leidel 2011-04-22 12:02:38 +00:00
parent 6c17190bf8
commit 8b588747ed
8 changed files with 441 additions and 11 deletions

View File

@ -151,6 +151,7 @@ answer newbie questions, and generally made Django that much better:
dne@mayonnaise.net
dready <wil@mojipage.com>
Maximillian Dornseif <md@hudora.de>
Daniel Duan <DaNmarner@gmail.com>
Jeremy Dunck <http://dunck.us/>
Andrew Durdin <adurdin@gmail.com>
dusk@woofle.net
@ -256,6 +257,7 @@ answer newbie questions, and generally made Django that much better:
Michael Josephson <http://www.sdjournal.com/>
jpellerin@gmail.com
junzhang.jn@gmail.com
Xia Kai <http://blog.xiaket.org/>
Antti Kaihola <http://djangopeople.net/akaihola/>
Bahadır Kandemir <bahadir@pardus.org.tr>
Karderio <karderio@gmail.com>

View File

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
"""
An alphabetical list of provinces for use as `choices` in a formfield.
Reference:
http://en.wikipedia.org/wiki/ISO_3166-2:CN
http://en.wikipedia.org/wiki/Province_%28China%29
http://en.wikipedia.org/wiki/Direct-controlled_municipality
http://en.wikipedia.org/wiki/Autonomous_regions_of_China
"""
CN_PROVINCE_CHOICES = (
("anhui", u"安徽"),
("beijing", u"北京"),
("chongqing", u"重庆"),
("fujian", u"福建"),
("gansu", u"甘肃"),
("guangdong", u"广东"),
("guangxi", u"广西壮族自治区"),
("guizhou", u"贵州"),
("hainan", u"海南"),
("hebei", u"河北"),
("heilongjiang", u"黑龙江"),
("henan", u"河南"),
("hongkong", u"香港"),
("hubei", u"湖北"),
("hunan", u"湖南"),
("jiangsu", u"江苏"),
("jiangxi", u"江西"),
("jilin", u"吉林"),
("liaoning", u"辽宁"),
("macao", u"澳门"),
("neimongol", u"内蒙古自治区"),
("ningxia", u"宁夏回族自治区"),
("qinghai", u"青海"),
("shaanxi", u"陕西"),
("shandong", u"山东"),
("shanghai", u"上海"),
("shanxi", u"山西"),
("sichuan", u"四川"),
("taiwan", u"台湾"),
("tianjin", u"天津"),
("xinjiang", u"新疆维吾尔自治区"),
("xizang", u"西藏自治区"),
("yunnan", u"云南"),
("zhejiang", u"浙江"),
)

View File

@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
"""
Chinese-specific form helpers
"""
import re
from django.forms import ValidationError
from django.forms.fields import CharField, RegexField, Select
from django.utils.translation import ugettext_lazy as _
__all__ = (
'CNProvinceSelect',
'CNPostCodeField',
'CNIDCardField',
'CNPhoneNumberField',
'CNCellNumberField',
)
ID_CARD_RE = r'^\d{15}(\d{2}[0-9xX])?$'
POST_CODE_RE = r'^\d{6}$'
PHONE_RE = r'^\d{3,4}-\d{7,8}(-\d+)?$'
CELL_RE = r'^1[358]\d{9}$'
# Valid location code used in id card checking algorithm
CN_LOCATION_CODES = (
11, # Beijing
12, # Tianjin
13, # Hebei
14, # Shanxi
15, # Nei Mongol
21, # Liaoning
22, # Jilin
23, # Heilongjiang
31, # Shanghai
32, # Jiangsu
33, # Zhejiang
34, # Anhui
35, # Fujian
36, # Jiangxi
37, # Shandong
41, # Henan
42, # Hubei
43, # Hunan
44, # Guangdong
45, # Guangxi
46, # Hainan
50, # Chongqing
51, # Sichuan
52, # Guizhou
53, # Yunnan
54, # Xizang
61, # Shaanxi
62, # Gansu
63, # Qinghai
64, # Ningxia
65, # Xinjiang
71, # Taiwan
81, # Hong Kong
91, # Macao
)
class CNProvinceSelect(Select):
"""
A select widget with list of Chinese provinces as choices.
"""
def __init__(self, attrs=None):
from cn_provinces import CN_PROVINCE_CHOICES
super(CNProvinceSelect, self).__init__(
attrs, choices=CN_PROVINCE_CHOICES,
)
class CNPostCodeField(RegexField):
"""
A form field that validates as Chinese post code.
Valid code is XXXXXX where X is digit.
"""
default_error_messages = {
'invalid': _(u'Enter a post code in the format XXXXXX.'),
}
def __init__(self, *args, **kwargs):
super(CNPostCodeField, self).__init__(POST_CODE_RE, *args, **kwargs)
class CNIDCardField(CharField):
"""
A form field that validates as Chinese Identification Card Number.
This field would check the following restrictions:
* the length could only be 15 or 18.
* if the length is 18, the last digit could be x or X.
* has a valid checksum.(length 18 only)
* has a valid birthdate.
* has a valid location.
The checksum algorithm is described in GB11643-1999.
"""
default_error_messages = {
'invalid': _(u'ID Card Number consists of 15 or 18 digits.'),
'checksum': _(u'Invalid ID Card Number: Wrong checksum'),
'birthday': _(u'Invalid ID Card Number: Wrong birthdate'),
'location': _(u'Invalid ID Card Number: Wrong location code'),
}
def __init__(self, max_length=18, min_length=15, *args, **kwargs):
super(CNIDCardField, self).__init__(max_length, min_length, *args,
**kwargs)
def clean(self, value):
"""
Check whether the input is a valid ID Card Number.
"""
# Check the length of the ID card number.
super(CNIDCardField, self).clean(value)
if not value:
return u""
# Check whether this ID card number has valid format
if not re.match(ID_CARD_RE, value):
raise ValidationError(self.error_messages['invalid'])
# Check the birthday of the ID card number.
if not self.has_valid_birthday(value):
raise ValidationError(self.error_messages['birthday'])
# Check the location of the ID card number.
if not self.has_valid_location(value):
raise ValidationError(self.error_messages['location'])
# Check the checksum of the ID card number.
value = value.upper()
if not self.has_valid_checksum(value):
raise ValidationError(self.error_messages['checksum'])
return u'%s' % value
def has_valid_birthday(self, value):
"""
This function would grab the birthdate from the ID card number and test
whether it is a valid date.
"""
from datetime import datetime
if len(value) == 15:
# 1st generation ID card
time_string = value[6:12]
format_string = "%y%m%d"
else:
# 2nd generation ID card
time_string = value[6:14]
format_string = "%Y%m%d"
try:
datetime.strptime(time_string, format_string)
return True
except ValueError:
# invalid date
return False
def has_valid_location(self, value):
"""
This method checks if the first two digits in the ID Card are valid.
"""
return int(value[:2]) in CN_LOCATION_CODES
def has_valid_checksum(self, value):
"""
This method checks if the last letter/digit in value is valid
according to the algorithm the ID Card follows.
"""
# If the length of the number is not 18, then the number is a 1st
# generation ID card number, and there is no checksum to be checked.
if len(value) != 18:
return True
checksum_index = sum(
map(
lambda a,b:a*(ord(b)-ord('0')),
(7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2),
value[:17],
),
) % 11
return '10X98765432'[checksum_index] == value[-1]
class CNPhoneNumberField(RegexField):
"""
A form field that validates as Chinese phone number
A valid phone number could be like:
010-55555555
Considering there might be extension phone numbers, so this could also be:
010-55555555-35
"""
default_error_messages = {
'invalid': _(u'Enter a valid phone number.'),
}
def __init__(self, *args, **kwargs):
super(CNPhoneNumberField, self).__init__(PHONE_RE, *args, **kwargs)
class CNCellNumberField(RegexField):
"""
A form field that validates as Chinese cell number
A valid cell number could be like:
13012345678
We used a rough rule here, the first digit should be 1, the second could be
3, 5 and 8, the rest could be what so ever.
The length of the cell number should be 11.
"""
default_error_messages = {
'invalid': _(u'Enter a valid cell number.'),
}
def __init__(self, *args, **kwargs):
super(CNCellNumberField, self).__init__(CELL_RE, *args, **kwargs)

View File

@ -43,6 +43,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
* Brazil_
* Canada_
* Chile_
* China_
* Czech_
* Finland_
* France_
@ -92,6 +93,7 @@ Here's an example of how to use them::
.. _Brazil: `Brazil (br)`_
.. _Canada: `Canada (ca)`_
.. _Chile: `Chile (cl)`_
.. _China: `China (cn)`_
.. _Czech: `Czech (cz)`_
.. _Finland: `Finland (fi)`_
.. _France: `France (fr)`_
@ -337,6 +339,35 @@ Chile (``cl``)
A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
choices.
China (``cn``)
==============
.. class:: cn.forms.CNProvinceSelect
A ``Select`` widget that uses a list of Chinese regions as its choices.
.. class:: cn.forms.CNPostCodeField
A form field that validates input as a Chinese post code.
Valid formats are XXXXXX where X is digit.
.. class:: cn.forms.CNIDCardField
A form field that validates input as a Chinese Identification Card Number.
Both 1st and 2nd generation ID Card Number are validated.
.. class:: cn.forms.CNPhoneNumberField
A form field that validates input as a Chinese phone number.
Valid formats are 0XX-XXXXXXXX, composed of 3 or 4 digits of region code
and 7 or 8 digits of phone number.
.. class:: cn.forms.CNCellNumberField
A form field that validates input as a Chinese mobile phone number.
Valid formats are like 1XXXXXXXXXX, where X is digit.
The second digit could only be 3, 5 and 8.
Czech (``cz``)
==============

View File

@ -0,0 +1,113 @@
# Tests for contrib/localflavor/ CN Form Fields
from django.contrib.localflavor.cn.forms import (CNProvinceSelect,
CNPostCodeField, CNIDCardField, CNPhoneNumberField, CNCellNumberField)
from utils import LocalFlavorTestCase
class CNLocalFlavorTests(LocalFlavorTestCase):
def test_CNProvinceSelect(self):
f = CNProvinceSelect()
correct_output = u'''<select name="provinces">
<option value="anhui">\u5b89\u5fbd</option>
<option value="beijing">\u5317\u4eac</option>
<option value="chongqing">\u91cd\u5e86</option>
<option value="fujian">\u798f\u5efa</option>
<option value="gansu">\u7518\u8083</option>
<option value="guangdong">\u5e7f\u4e1c</option>
<option value="guangxi">\u5e7f\u897f\u58ee\u65cf\u81ea\u6cbb\u533a</option>
<option value="guizhou">\u8d35\u5dde</option>
<option value="hainan">\u6d77\u5357</option>
<option value="hebei">\u6cb3\u5317</option>
<option value="heilongjiang">\u9ed1\u9f99\u6c5f</option>
<option value="henan">\u6cb3\u5357</option>
<option value="hongkong">\u9999\u6e2f</option>
<option value="hubei" selected="selected">\u6e56\u5317</option>
<option value="hunan">\u6e56\u5357</option>
<option value="jiangsu">\u6c5f\u82cf</option>
<option value="jiangxi">\u6c5f\u897f</option>
<option value="jilin">\u5409\u6797</option>
<option value="liaoning">\u8fbd\u5b81</option>
<option value="macao">\u6fb3\u95e8</option>
<option value="neimongol">\u5185\u8499\u53e4\u81ea\u6cbb\u533a</option>
<option value="ningxia">\u5b81\u590f\u56de\u65cf\u81ea\u6cbb\u533a</option>
<option value="qinghai">\u9752\u6d77</option>
<option value="shaanxi">\u9655\u897f</option>
<option value="shandong">\u5c71\u4e1c</option>
<option value="shanghai">\u4e0a\u6d77</option>
<option value="shanxi">\u5c71\u897f</option>
<option value="sichuan">\u56db\u5ddd</option>
<option value="taiwan">\u53f0\u6e7e</option>
<option value="tianjin">\u5929\u6d25</option>
<option value="xinjiang">\u65b0\u7586\u7ef4\u543e\u5c14\u81ea\u6cbb\u533a</option>
<option value="xizang">\u897f\u85cf\u81ea\u6cbb\u533a</option>
<option value="yunnan">\u4e91\u5357</option>
<option value="zhejiang">\u6d59\u6c5f</option>
</select>'''
self.assertEqual(f.render('provinces', 'hubei'), correct_output)
def test_CNPostCodeField(self):
error_format = [u'Enter a post code in the format XXXXXX.']
valid = {
'091209': u'091209'
}
invalid = {
'09120': error_format,
'09120916': error_format
}
self.assertFieldOutput(CNPostCodeField, valid, invalid)
def test_CNIDCardField(self):
valid = {
# A valid 1st generation ID Card Number.
'110101491001001': u'110101491001001',
# A valid 2nd generation ID Card number.
'11010119491001001X': u'11010119491001001X',
# Another valid 2nd gen ID Number with a case change
'11010119491001001x': u'11010119491001001X'
}
wrong_format = [u'ID Card Number consists of 15 or 18 digits.']
wrong_location = [u'Invalid ID Card Number: Wrong location code']
wrong_bday = [u'Invalid ID Card Number: Wrong birthdate']
wrong_checksum = [u'Invalid ID Card Number: Wrong checksum']
invalid = {
'abcdefghijklmnop': wrong_format,
'1010101010101010': wrong_format,
'010101491001001' : wrong_location, # 1st gen, 01 is invalid
'110101491041001' : wrong_bday, # 1st gen. There wasn't day 41
'92010119491001001X': wrong_location, # 2nd gen, 92 is invalid
'91010119491301001X': wrong_bday, # 2nd gen, 19491301 is invalid date
'910101194910010014': wrong_checksum #2nd gen
}
self.assertFieldOutput(CNIDCardField, valid, invalid)
def test_CNPhoneNumberField(self):
error_format = [u'Enter a valid phone number.']
valid = {
'010-12345678': u'010-12345678',
'010-1234567': u'010-1234567',
'0101-12345678': u'0101-12345678',
'0101-1234567': u'0101-1234567',
'010-12345678-020':u'010-12345678-020'
}
invalid = {
'01x-12345678': error_format,
'12345678': error_format,
'01123-12345678': error_format,
'010-123456789': error_format,
'010-12345678-': error_format
}
self.assertFieldOutput(CNPhoneNumberField, valid, invalid)
def test_CNCellNumberField(self):
error_format = [u'Enter a valid cell number.']
valid = {
'13012345678': u'13012345678',
}
invalid = {
'130123456789': error_format,
'14012345678': error_format
}
self.assertFieldOutput(CNCellNumberField, valid, invalid)

View File

@ -7,6 +7,7 @@ from localflavor.ca import CALocalFlavorTests
from localflavor.ch import CHLocalFlavorTests
from localflavor.cl import CLLocalFlavorTests
from localflavor.cz import CZLocalFlavorTests
from localflavor.cn import CNLocalFlavorTests
from localflavor.de import DELocalFlavorTests
from localflavor.es import ESLocalFlavorTests
from localflavor.fi import FILocalFlavorTests

View File

@ -12,15 +12,37 @@ from validators import TestFieldWithValidators
from widgets import *
from regressiontests.forms.localflavortests import (
ARLocalFlavorTests, ATLocalFlavorTests, AULocalFlavorTests,
BELocalFlavorTests, BRLocalFlavorTests, CALocalFlavorTests,
CHLocalFlavorTests, CLLocalFlavorTests, CZLocalFlavorTests,
DELocalFlavorTests, ESLocalFlavorTests, FILocalFlavorTests,
FRLocalFlavorTests, GenericLocalFlavorTests, IDLocalFlavorTests,
IELocalFlavorTests, ILLocalFlavorTests, ISLocalFlavorTests,
ITLocalFlavorTests, JPLocalFlavorTests, KWLocalFlavorTests,
NLLocalFlavorTests, PLLocalFlavorTests, PTLocalFlavorTests,
ROLocalFlavorTests, SELocalFlavorTests, SKLocalFlavorTests,
TRLocalFlavorTests, UKLocalFlavorTests, USLocalFlavorTests,
UYLocalFlavorTests, ZALocalFlavorTests
ARLocalFlavorTests,
ATLocalFlavorTests,
AULocalFlavorTests,
BELocalFlavorTests,
BRLocalFlavorTests,
CALocalFlavorTests,
CHLocalFlavorTests,
CLLocalFlavorTests,
CNLocalFlavorTests,
CZLocalFlavorTests,
DELocalFlavorTests,
ESLocalFlavorTests,
FILocalFlavorTests,
FRLocalFlavorTests,
GenericLocalFlavorTests,
IDLocalFlavorTests,
IELocalFlavorTests,
ILLocalFlavorTests,
ISLocalFlavorTests,
ITLocalFlavorTests,
JPLocalFlavorTests,
KWLocalFlavorTests,
NLLocalFlavorTests,
PLLocalFlavorTests,
PTLocalFlavorTests,
ROLocalFlavorTests,
SELocalFlavorTests,
SKLocalFlavorTests,
TRLocalFlavorTests,
UKLocalFlavorTests,
USLocalFlavorTests,
UYLocalFlavorTests,
ZALocalFlavorTests
)