Ticket #12379: cn_localflavor_r15942.diff
File cn_localflavor_r15942.diff, 18.4 KB (added by , 14 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index a19e49b..4462c34 100644
a b answer newbie questions, and generally made Django that much better: 151 151 dne@mayonnaise.net 152 152 dready <wil@mojipage.com> 153 153 Maximillian Dornseif <md@hudora.de> 154 Daniel Duan <DaNmarner@gmail.com> 154 155 Jeremy Dunck <http://dunck.us/> 155 156 Andrew Durdin <adurdin@gmail.com> 156 157 dusk@woofle.net … … answer newbie questions, and generally made Django that much better: 255 256 Michael Josephson <http://www.sdjournal.com/> 256 257 jpellerin@gmail.com 257 258 junzhang.jn@gmail.com 259 Xia Kai <http://blog.xiaket.org/> 258 260 Antti Kaihola <http://djangopeople.net/akaihola/> 259 261 Bahadır Kandemir <bahadir@pardus.org.tr> 260 262 Karderio <karderio@gmail.com> -
new file django/contrib/localflavor/cn/cn_provinces.py
diff --git a/django/contrib/localflavor/cn/__init__.py b/django/contrib/localflavor/cn/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django/contrib/localflavor/cn/cn_provinces.py b/django/contrib/localflavor/cn/cn_provinces.py new file mode 100644 index 0000000..fe0aa37
- + 1 # -*- coding: utf-8 -*- 2 3 """ 4 An alphabetical list of provinces for use as `choices` in a formfield. 5 6 Reference: 7 http://en.wikipedia.org/wiki/ISO_3166-2:CN 8 http://en.wikipedia.org/wiki/Province_%28China%29 9 http://en.wikipedia.org/wiki/Direct-controlled_municipality 10 http://en.wikipedia.org/wiki/Autonomous_regions_of_China 11 """ 12 13 14 CN_PROVINCE_CHOICES = ( 15 ("anhui", u"安徽"), 16 ("beijing", u"北京"), 17 ("chongqing", u"重庆"), 18 ("fujian", u"福建"), 19 ("gansu", u"甘肃"), 20 ("guangdong", u"广东"), 21 ("guangxi", u"广西壮族自治区"), 22 ("guizhou", u"贵州"), 23 ("hainan", u"海南"), 24 ("hebei", u"河北"), 25 ("heilongjiang", u"黑龙江"), 26 ("henan", u"河南"), 27 ("hongkong", u"香港"), 28 ("hubei", u"湖北"), 29 ("hunan", u"湖南"), 30 ("jiangsu", u"江苏"), 31 ("jiangxi", u"江西"), 32 ("jilin", u"吉林"), 33 ("liaoning", u"辽宁"), 34 ("macao", u"澳门"), 35 ("neimongol", u"内蒙古自治区"), 36 ("ningxia", u"宁夏回族自治区"), 37 ("qinghai", u"青海"), 38 ("shaanxi", u"陕西"), 39 ("shandong", u"山东"), 40 ("shanghai", u"上海"), 41 ("shanxi", u"山西"), 42 ("sichuan", u"四川"), 43 ("taiwan", u"台湾"), 44 ("tianjin", u"天津"), 45 ("xinjiang", u"新疆维吾尔自治区"), 46 ("xizang", u"西藏自治区"), 47 ("yunnan", u"云南"), 48 ("zhejiang", u"浙江"), 49 ) -
new file django/contrib/localflavor/cn/forms.py
diff --git a/django/contrib/localflavor/cn/forms.py b/django/contrib/localflavor/cn/forms.py new file mode 100644 index 0000000..9749349
- + 1 # -*- coding: utf-8 -*- 2 3 """ 4 Chinese-specific form helpers 5 """ 6 import re 7 8 from django.forms import ValidationError 9 from django.forms.fields import CharField, RegexField, Select 10 from django.utils.translation import ugettext_lazy as _ 11 12 13 __all__ = ( 14 'CNProvinceSelect', 15 'CNPostCodeField', 16 'CNIDCardField', 17 'CNPhoneNumberField', 18 'CNCellNumberField', 19 ) 20 21 22 ID_CARD_RE = r'^\d{15}(\d{2}[0-9xX])?$' 23 POST_CODE_RE = r'^\d{6}$' 24 PHONE_RE = r'^\d{3,4}-\d{7,8}(-\d+)?$' 25 CELL_RE = r'^1[358]\d{9}$' 26 27 # Valid location code used in id card checking algorithm 28 CN_LOCATION_CODES = ( 29 11 , # Beijing 30 12 , # Tianjin 31 13 , # Hebei 32 14 , # Shanxi 33 15 , # Nei Mongol 34 21 , # Liaoning 35 22 , # Jilin 36 23 , # Heilongjiang 37 31 , # Shanghai 38 32 , # Jiangsu 39 33 , # Zhejiang 40 34 , # Anhui 41 35 , # Fujian 42 36 , # Jiangxi 43 37 , # Shandong 44 41 , # Henan 45 42 , # Hubei 46 43 , # Hunan 47 44 , # Guangdong 48 45 , # Guangxi 49 46 , # Hainan 50 50 , # Chongqing 51 51 , # Sichuan 52 52 , # Guizhou 53 53 , # Yunnan 54 54 , # Xizang 55 61 , # Shaanxi 56 62 , # Gansu 57 63 , # Qinghai 58 64 , # Ningxia 59 65 , # Xinjiang 60 71 , # Taiwan 61 81 , # Hong Kong 62 91 , # Macao 63 ) 64 65 class CNProvinceSelect(Select): 66 """ 67 A select widget with list of Chinese provinces as choices. 68 """ 69 def __init__(self, attrs=None): 70 from cn_provinces import CN_PROVINCE_CHOICES 71 super(CNProvinceSelect, self).__init__( 72 attrs, choices=CN_PROVINCE_CHOICES, 73 ) 74 75 76 class CNPostCodeField(RegexField): 77 """ 78 A form field that validates as Chinese post code. 79 Valid code is XXXXXX where X is digit. 80 """ 81 default_error_messages = { 82 'invalid': _(u'Enter a post code in the format XXXXXX.'), 83 } 84 85 def __init__(self, *args, **kwargs): 86 super(CNPostCodeField, self).__init__(POST_CODE_RE, *args, **kwargs) 87 88 89 class CNIDCardField(CharField): 90 """ 91 A form field that validates as Chinese Identification Card Number. 92 93 This field would check the following restrictions: 94 * the length could only be 15 or 18. 95 * if the length is 18, the last digit could be x or X. 96 * has a valid checksum.(length 18 only) 97 * has a valid birthdate. 98 * has a valid location. 99 100 The checksum algorithm is described in GB11643-1999. 101 """ 102 default_error_messages = { 103 'invalid': _(u'ID Card Number consists of 15 or 18 digits.'), 104 'checksum': _(u'Invalid ID Card Number: Wrong checksum'), 105 'birthday': _(u'Invalid ID Card Number: Wrong birthdate'), 106 'location': _(u'Invalid ID Card Number: Wrong location code'), 107 } 108 109 def __init__(self, max_length=18, min_length=15, *args, **kwargs): 110 super(CNIDCardField, self).__init__(max_length, min_length, *args, 111 **kwargs) 112 113 def clean(self, value): 114 """ 115 Check whether the input is a valid ID Card Number. 116 """ 117 # Check the length of the ID card number. 118 super(CNIDCardField, self).clean(value) 119 if not value: 120 return u"" 121 # Check whether this ID card number has valid format 122 if not re.match(ID_CARD_RE, value): 123 raise ValidationError(self.error_messages['invalid']) 124 # Check the birthday of the ID card number. 125 if not self.has_valid_birthday(value): 126 raise ValidationError(self.error_messages['birthday']) 127 # Check the location of the ID card number. 128 if not self.has_valid_location(value): 129 raise ValidationError(self.error_messages['location']) 130 # Check the checksum of the ID card number. 131 value = value.upper() 132 if not self.has_valid_checksum(value): 133 raise ValidationError(self.error_messages['checksum']) 134 return u'%s' % value 135 136 def has_valid_birthday(self, value): 137 """ 138 This function would grab the birthdate from the ID card number and test 139 whether it is a valid date. 140 """ 141 from datetime import datetime 142 if len(value) == 15: 143 # 1st generation ID card 144 time_string = value[6:12] 145 format_string = "%y%m%d" 146 else: 147 # 2nd generation ID card 148 time_string = value[6:14] 149 format_string = "%Y%m%d" 150 try: 151 datetime.strptime(time_string, format_string) 152 return True 153 except ValueError: 154 # invalid date 155 return False 156 157 def has_valid_location(self, value): 158 """ 159 This method checks if the first two digits in the ID Card are valid. 160 """ 161 return int(value[:2]) in CN_LOCATION_CODES 162 163 def has_valid_checksum(self, value): 164 """ 165 This method checks if the last letter/digit in value is valid 166 according to the algorithm the ID Card follows. 167 """ 168 # If the length of the number is not 18, then the number is a 1st 169 # generation ID card number, and there is no checksum to be checked. 170 if len(value) != 18: 171 return True 172 checksum_index = sum( 173 map( 174 lambda a,b:a*(ord(b)-ord('0')), 175 (7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2), 176 value[:17], 177 ), 178 ) % 11 179 return '10X98765432'[checksum_index] == value[-1] 180 181 182 class CNPhoneNumberField(RegexField): 183 """ 184 A form field that validates as Chinese phone number 185 A valid phone number could be like: 186 010-55555555 187 Considering there might be extension phone numbers, so this could also be: 188 010-55555555-35 189 """ 190 default_error_messages = { 191 'invalid': _(u'Enter a valid phone number.'), 192 } 193 194 def __init__(self, *args, **kwargs): 195 super(CNPhoneNumberField, self).__init__(PHONE_RE, *args, **kwargs) 196 197 198 class CNCellNumberField(RegexField): 199 """ 200 A form field that validates as Chinese cell number 201 A valid cell number could be like: 202 13012345678 203 We used a rough rule here, the first digit should be 1, the second could be 204 3, 5 and 8, the rest could be what so ever. 205 The length of the cell number should be 11. 206 """ 207 default_error_messages = { 208 'invalid': _(u'Enter a valid cell number.'), 209 } 210 211 def __init__(self, *args, **kwargs): 212 super(CNCellNumberField, self).__init__(CELL_RE, *args, **kwargs) -
docs/ref/contrib/localflavor.txt
diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt index f54341e..08773b8 100644
a b Countries currently supported by :mod:`~django.contrib.localflavor` are: 43 43 * Brazil_ 44 44 * Canada_ 45 45 * Chile_ 46 * China_ 46 47 * Czech_ 47 48 * Finland_ 48 49 * France_ … … Here's an example of how to use them:: 92 93 .. _Brazil: `Brazil (br)`_ 93 94 .. _Canada: `Canada (ca)`_ 94 95 .. _Chile: `Chile (cl)`_ 96 .. _China: `China (cn)`_ 95 97 .. _Czech: `Czech (cz)`_ 96 98 .. _Finland: `Finland (fi)`_ 97 99 .. _France: `France (fr)`_ … … Chile (``cl``) 322 324 A ``Select`` widget that uses a list of Chilean regions (Regiones) as its 323 325 choices. 324 326 327 China (``cn``) 328 ============== 329 330 .. class:: cn.forms.CNProvinceSelect 331 332 A ``Select`` widget that uses a list of Chinese regions as its choices. 333 334 .. class:: cn.forms.CNPostCodeField 335 336 A form field that validates input as a Chinese post code. 337 Valid formats are XXXXXX where X is digit. 338 339 .. class:: cn.forms.CNIDCardField 340 341 A form field that validates input as a Chinese Identification Card Number. 342 Both 1st and 2nd generation ID Card Number are validated. 343 344 .. class:: cn.forms.CNPhoneNumberField 345 346 A form field that validates input as a Chinese phone number. 347 Valid formats are 0XX-XXXXXXXX, composed of 3 or 4 digits of region code 348 and 7 or 8 digits of phone number. 349 350 .. class:: cn.forms.CNCellNumberField 351 352 A form field that validates input as a Chinese mobile phone number. 353 Valid formats are like 1XXXXXXXXXX, where X is digit. 354 The second digit could only be 3, 5 and 8. 355 325 356 Czech (``cz``) 326 357 ============== 327 358 -
new file tests/regressiontests/forms/localflavor/cn.py
diff --git a/tests/regressiontests/forms/localflavor/cn.py b/tests/regressiontests/forms/localflavor/cn.py new file mode 100644 index 0000000..6579d95
- + 1 # Tests for contrib/localflavor/ CN Form Fields 2 3 from django.contrib.localflavor.cn.forms import (CNProvinceSelect, 4 CNPostCodeField, CNIDCardField, CNPhoneNumberField, CNCellNumberField) 5 from utils import LocalFlavorTestCase 6 7 class CNLocalFlavorTests(LocalFlavorTestCase): 8 def test_CNProvinceSelect(self): 9 f = CNProvinceSelect() 10 correct_output = u'''<select name="provinces"> 11 <option value="anhui">\u5b89\u5fbd</option> 12 <option value="beijing">\u5317\u4eac</option> 13 <option value="chongqing">\u91cd\u5e86</option> 14 <option value="fujian">\u798f\u5efa</option> 15 <option value="gansu">\u7518\u8083</option> 16 <option value="guangdong">\u5e7f\u4e1c</option> 17 <option value="guangxi">\u5e7f\u897f\u58ee\u65cf\u81ea\u6cbb\u533a</option> 18 <option value="guizhou">\u8d35\u5dde</option> 19 <option value="hainan">\u6d77\u5357</option> 20 <option value="hebei">\u6cb3\u5317</option> 21 <option value="heilongjiang">\u9ed1\u9f99\u6c5f</option> 22 <option value="henan">\u6cb3\u5357</option> 23 <option value="hongkong">\u9999\u6e2f</option> 24 <option value="hubei" selected="selected">\u6e56\u5317</option> 25 <option value="hunan">\u6e56\u5357</option> 26 <option value="jiangsu">\u6c5f\u82cf</option> 27 <option value="jiangxi">\u6c5f\u897f</option> 28 <option value="jilin">\u5409\u6797</option> 29 <option value="liaoning">\u8fbd\u5b81</option> 30 <option value="macao">\u6fb3\u95e8</option> 31 <option value="neimongol">\u5185\u8499\u53e4\u81ea\u6cbb\u533a</option> 32 <option value="ningxia">\u5b81\u590f\u56de\u65cf\u81ea\u6cbb\u533a</option> 33 <option value="qinghai">\u9752\u6d77</option> 34 <option value="shaanxi">\u9655\u897f</option> 35 <option value="shandong">\u5c71\u4e1c</option> 36 <option value="shanghai">\u4e0a\u6d77</option> 37 <option value="shanxi">\u5c71\u897f</option> 38 <option value="sichuan">\u56db\u5ddd</option> 39 <option value="taiwan">\u53f0\u6e7e</option> 40 <option value="tianjin">\u5929\u6d25</option> 41 <option value="xinjiang">\u65b0\u7586\u7ef4\u543e\u5c14\u81ea\u6cbb\u533a</option> 42 <option value="xizang">\u897f\u85cf\u81ea\u6cbb\u533a</option> 43 <option value="yunnan">\u4e91\u5357</option> 44 <option value="zhejiang">\u6d59\u6c5f</option> 45 </select>''' 46 self.assertEqual(f.render('provinces', 'hubei'), correct_output) 47 48 def test_CNPostCodeField(self): 49 error_format = [u'Enter a post code in the format XXXXXX.'] 50 valid = { 51 '091209': u'091209' 52 } 53 invalid = { 54 '09120': error_format, 55 '09120916': error_format 56 } 57 self.assertFieldOutput(CNPostCodeField, valid, invalid) 58 59 def test_CNIDCardField(self): 60 valid = { 61 # A valid 1st generation ID Card Number. 62 '110101491001001': u'110101491001001', 63 # A valid 2nd generation ID Card number. 64 '11010119491001001X': u'11010119491001001X', 65 # Another valid 2nd gen ID Number with a case change 66 '11010119491001001x': u'11010119491001001X' 67 } 68 69 wrong_format = [u'ID Card Number consists of 15 or 18 digits.'] 70 wrong_location = [u'Invalid ID Card Number: Wrong location code'] 71 wrong_bday = [u'Invalid ID Card Number: Wrong birthdate'] 72 wrong_checksum = [u'Invalid ID Card Number: Wrong checksum'] 73 74 invalid = { 75 'abcdefghijklmnop': wrong_format, 76 '1010101010101010': wrong_format, 77 '010101491001001' : wrong_location, # 1st gen, 01 is invalid 78 '110101491041001' : wrong_bday, # 1st gen. There wasn't day 41 79 '92010119491001001X': wrong_location, # 2nd gen, 92 is invalid 80 '91010119491301001X': wrong_bday, # 2nd gen, 19491301 is invalid date 81 '910101194910010014': wrong_checksum #2nd gen 82 } 83 self.assertFieldOutput(CNIDCardField, valid, invalid) 84 85 def test_CNPhoneNumberField(self): 86 error_format = [u'Enter a valid phone number.'] 87 valid = { 88 '010-12345678': u'010-12345678', 89 '010-1234567': u'010-1234567', 90 '0101-12345678': u'0101-12345678', 91 '0101-1234567': u'0101-1234567', 92 '010-12345678-020':u'010-12345678-020' 93 } 94 invalid = { 95 '01x-12345678': error_format, 96 '12345678': error_format, 97 '01123-12345678': error_format, 98 '010-123456789': error_format, 99 '010-12345678-': error_format 100 } 101 self.assertFieldOutput(CNPhoneNumberField, valid, invalid) 102 103 def test_CNCellNumberField(self): 104 error_format = [u'Enter a valid cell number.'] 105 valid = { 106 '13012345678': u'13012345678', 107 } 108 invalid = { 109 '130123456789': error_format, 110 '14012345678': error_format 111 } 112 self.assertFieldOutput(CNCellNumberField, valid, invalid) 113 -
tests/regressiontests/forms/localflavortests.py
diff --git a/tests/regressiontests/forms/localflavortests.py b/tests/regressiontests/forms/localflavortests.py index 5ee1c32..94abed1 100644
a b from localflavor.ca import CALocalFlavorTests 7 7 from localflavor.ch import CHLocalFlavorTests 8 8 from localflavor.cl import CLLocalFlavorTests 9 9 from localflavor.cz import CZLocalFlavorTests 10 from localflavor.cn import CNLocalFlavorTests 10 11 from localflavor.de import DELocalFlavorTests 11 12 from localflavor.es import ESLocalFlavorTests 12 13 from localflavor.fi import FILocalFlavorTests -
tests/regressiontests/forms/tests/__init__.py
diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py index 2d96b2f..38671fa 100644
a b from widgets import * 14 14 from regressiontests.forms.localflavortests import ( 15 15 ARLocalFlavorTests, ATLocalFlavorTests, AULocalFlavorTests, 16 16 BELocalFlavorTests, BRLocalFlavorTests, CALocalFlavorTests, 17 CHLocalFlavorTests, CLLocalFlavorTests, C ZLocalFlavorTests,18 DELocalFlavorTests, ESLocalFlavorTests, FILocalFlavorTests,19 F RLocalFlavorTests, GenericLocalFlavorTests, IDLocalFlavorTests,20 I ELocalFlavorTests, ILLocalFlavorTests, ISLocalFlavorTests,21 I TLocalFlavorTests, JPLocalFlavorTests, KWLocalFlavorTests,22 NLLocalFlavorTests, PLLocalFlavorTests, PTLocalFlavorTests,23 ROLocalFlavorTests, SELocalFlavorTests, SKLocalFlavorTests,24 TRLocalFlavorTests, UKLocalFlavorTests, USLocalFlavorTests,25 U YLocalFlavorTests, ZALocalFlavorTests17 CHLocalFlavorTests, CLLocalFlavorTests, CNLocalFlavorTests, 18 CZLocalFlavorTests, DELocalFlavorTests, ESLocalFlavorTests, 19 FILocalFlavorTests, FRLocalFlavorTests, GenericLocalFlavorTests, 20 IDLocalFlavorTests, IELocalFlavorTests, ILLocalFlavorTests, 21 ISLocalFlavorTests, ITLocalFlavorTests, JPLocalFlavorTests, 22 KWLocalFlavorTests, NLLocalFlavorTests, PLLocalFlavorTests, 23 PTLocalFlavorTests, ROLocalFlavorTests, SELocalFlavorTests, 24 SKLocalFlavorTests, TRLocalFlavorTests, UKLocalFlavorTests, 25 USLocalFlavorTests, UYLocalFlavorTests, ZALocalFlavorTests 26 26 )