Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #12379 -- Added Chinese (cn) localflavor package. Thanks, Xia K…

…ai, Daniel Duan, DaNmarner and Łukasz Rekucki.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@16070 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 8b588747ed302fcdef0b79171ed3cf129cd32a38 1 parent 6c17190
@jezdez jezdez authored
View
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>
View
0  django/contrib/localflavor/cn/__init__.py
No changes.
View
49 django/contrib/localflavor/cn/cn_provinces.py
@@ -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
212 django/contrib/localflavor/cn/forms.py
@@ -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
31 docs/ref/contrib/localflavor.txt
@@ -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
113 tests/regressiontests/forms/localflavor/cn.py
@@ -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
1  tests/regressiontests/forms/localflavortests.py
@@ -7,6 +7,7 @@
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
44 tests/regressiontests/forms/tests/__init__.py
@@ -12,15 +12,37 @@
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
)
Please sign in to comment.
Something went wrong with that request. Please try again.