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:
parent
6c17190bf8
commit
8b588747ed
2
AUTHORS
2
AUTHORS
|
@ -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>
|
||||
|
|
|
@ -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"浙江"),
|
||||
)
|
|
@ -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)
|
|
@ -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``)
|
||||
==============
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue