Ticket #12379: cn_localflavor_r13230.diff
File cn_localflavor_r13230.diff, 18.9 KB (added by , 14 years ago) |
---|
-
AUTHORS
diff --git a/AUTHORS b/AUTHORS index 4921f7c..b991c23 100644
a b answer newbie questions, and generally made Django that much better: 209 209 Ronny Haryanto <http://ronny.haryan.to/> 210 210 Hawkeye 211 211 Joe Heck <http://www.rhonabwy.com/wp/> 212 Xia Kai <http://blog.xiaket.org/> 212 213 Joel Heenan <joelh-django@planetjoel.com> 213 214 Mikko Hellsing <mikko@sorl.net> 214 215 Sebastian Hillig <sebastian.hillig@gmail.com> … … answer newbie questions, and generally made Django that much better: 510 511 Cheng Zhang 511 512 Glenn Maynard <glenn@zewt.org> 512 513 bthomas 514 Daniel Duan <DaNmarner@gmail.com> 513 515 514 516 A big THANK YOU goes to: 515 517 -
new file django/contrib/localflavor/cn/cn_provinces.py
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 1c58e2d..7d8a31c 100644
a b Countries currently supported by :mod:`~django.contrib.localflavor` are: 44 44 * Brazil_ 45 45 * Canada_ 46 46 * Chile_ 47 * China_ 47 48 * Czech_ 48 49 * Finland_ 49 50 * France_ … … Here's an example of how to use them:: 90 91 .. _Brazil: `Brazil (br)`_ 91 92 .. _Canada: `Canada (ca)`_ 92 93 .. _Chile: `Chile (cl)`_ 94 .. _China: `China (cn)`_ 93 95 .. _Czech: `Czech (cz)`_ 94 96 .. _Finland: `Finland (fi)`_ 95 97 .. _France: `France (fr)`_ … … Chile (``cl``) 245 247 A ``Select`` widget that uses a list of Chilean regions (Regiones) as its 246 248 choices. 247 249 250 China (``cn``) 251 ============== 252 253 .. class:: cn.forms.CNProvinceSelect 254 255 A ``Select`` widget that uses a list of Chinese regions as its choices. 256 257 .. class:: cn.forms.CNPostCodeField 258 259 A form field that validates input as a Chinese post code. 260 Valid formats are XXXXXX where X is digit. 261 262 .. class:: cn.forms.CNIDCardField 263 264 A form field that validates input as a Chinese Identification Card Number. 265 Both 1st and 2nd generation ID Card Number are validated. 266 267 .. class:: cn.forms.CNPhoneNumberField 268 269 A form field that validates input as a Chinese phone number. 270 Valid formats are 0XX-XXXXXXXX, composed of 3 or 4 digits of region code 271 and 7 or 8 digits of phone number. 272 273 .. class:: cn.forms.CNCellNumberField 274 275 A form field that validates input as a Chinese mobile phone number. 276 Valid formats are like 1XXXXXXXXXX, where X is digit. 277 The second digit could only be 3, 5 and 8. 278 248 279 Czech (``cz``) 249 280 ============== 250 281 -
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..c1cc27f
- + 1 # Tests for contrib/localflavor/ CN Form Fields 2 3 tests = r""" 4 5 ############################################################ 6 ##################### CNProvinceSelect ##################### 7 ############################################################ 8 >>> from django.contrib.localflavor.cn.forms import CNProvinceSelect 9 >>> s = CNProvinceSelect() 10 >>> s.render('provinces', 'hubei') 11 u'<select name="provinces"> 12 <option value="anhui">\u5b89\u5fbd</option> 13 <option value="beijing">\u5317\u4eac</option> 14 <option value="chongqing">\u91cd\u5e86</option> 15 <option value="fujian">\u798f\u5efa</option> 16 <option value="gansu">\u7518\u8083</option> 17 <option value="guangdong">\u5e7f\u4e1c</option> 18 <option value="guangxi">\u5e7f\u897f\u58ee\u65cf\u81ea\u6cbb\u533a</option> 19 <option value="guizhou">\u8d35\u5dde</option> 20 <option value="hainan">\u6d77\u5357</option> 21 <option value="hebei">\u6cb3\u5317</option> 22 <option value="heilongjiang">\u9ed1\u9f99\u6c5f</option> 23 <option value="henan">\u6cb3\u5357</option> 24 <option value="honghong">\u9999\u6e2f</option> 25 <option value="hubei" selected="selected">\u6e56\u5317</option> 26 <option value="hunan">\u6e56\u5357</option> 27 <option value="jiangsu">\u6c5f\u82cf</option> 28 <option value="jiangxi">\u6c5f\u897f</option> 29 <option value="jilin">\u5409\u6797</option> 30 <option value="liaoning">\u8fbd\u5b81</option> 31 <option value="macao">\u6fb3\u95e8</option> 32 <option value="neimongol">\u5185\u8499\u53e4\u81ea\u6cbb\u533a</option> 33 <option value="ningxia">\u5b81\u590f\u56de\u65cf\u81ea\u6cbb\u533a</option> 34 <option value="qinghai">\u9752\u6d77</option> 35 <option value="shaanxi">\u9655\u897f</option> 36 <option value="shandong">\u5c71\u4e1c</option> 37 <option value="shanghai">\u4e0a\u6d77</option> 38 <option value="shanxi">\u5c71\u897f</option> 39 <option value="sichuan">\u56db\u5ddd</option> 40 <option value="taiwan">\u53f0\u6e7e</option> 41 <option value="tianjin">\u5929\u6d25</option> 42 <option value="xinjiang">\u65b0\u7586\u7ef4\u543e\u5c14\u81ea\u6cbb\u533a</option> 43 <option value="xizang">\u897f\u85cf\u81ea\u6cbb\u533a</option> 44 <option value="yunnan">\u4e91\u5357</option> 45 <option value="zhejiang">\u6d59\u6c5f</option> 46 </select>' 47 48 ############################################################ 49 ##################### CNPostCodeField ###################### 50 ############################################################ 51 >>> from django.contrib.localflavor.cn.forms import CNPostCodeField 52 >>> f = CNPostCodeField(required=False) 53 >>> f.clean('') 54 u'' 55 >>> f.clean('091209') 56 u'091209' 57 >>> f.clean('09120') 58 Traceback (most recent call last): 59 ... 60 ValidationError: [u'Enter a post code in the format XXXXXX.'] 61 >>> f.clean('09120916') 62 Traceback (most recent call last): 63 ... 64 ValidationError: [u'Enter a post code in the format XXXXXX.'] 65 66 ############################################################ 67 ##################### CNIDCardField ######################## 68 ############################################################ 69 >>> from django.contrib.localflavor.cn.forms import CNIDCardField 70 >>> f = CNIDCardField(required=False) 71 >>> f.clean('') 72 u'' 73 74 >>> # A string of 16 characters. 75 >>> f.clean("abcdefghijklmnop") 76 Traceback (most recent call last): 77 ... 78 ValidationError: [u'ID Card Number consists of 15 or 18 digits.'] 79 80 >>> # A string of 16 digits. 81 >>> f.clean("1010101010101010") 82 Traceback (most recent call last): 83 ... 84 ValidationError: [u'ID Card Number consists of 15 or 18 digits.'] 85 86 >>> # A valid 1st generation ID Card number. 87 >>> f.clean('110101491001001') 88 u'110101491001001' 89 90 >>> # A 1st generation ID Card number with invalid location 91 >>> f.clean('010101491001001') # 01, an invalid location. 92 Traceback (most recent call last): 93 ... 94 ValidationError: [u'Invalid ID Card Number: Wrong location code'] 95 96 >>> # A 1st generation ID Card number with invalid birthdate. 97 >>> f.clean('110101491041001') # 491041, an invalid day, 41 98 Traceback (most recent call last): 99 ... 100 ValidationError: [u'Invalid ID Card Number: Wrong birthdate'] 101 102 >>> # A valid 2nd generation ID Card number. 103 >>> f.clean('11010119491001001X') 104 u'11010119491001001X' 105 106 >>> # Another valid 2nd generation ID Card number, notice that the case of the last 107 >>> # character is changed. 108 >>> f.clean('11010119491001001x') 109 u'11010119491001001X' 110 >>> # An invalid 2nd generation ID Card number. 111 >>> f.clean('92010119491001001X') # 92 is an invalid location code. 112 Traceback (most recent call last): 113 ... 114 ValidationError: [u'Invalid ID Card Number: Wrong location code'] 115 116 >>> # Another invalid 2nd generation ID Card number. 117 >>> f.clean('91010119491301001X') # 19491301 is an invalid date. 118 Traceback (most recent call last): 119 ... 120 ValidationError: [u'Invalid ID Card Number: Wrong birthdate'] 121 122 >>> # Yet another invalid 2nd generation ID Card number. 123 >>> f.clean('910101194910010014') 124 Traceback (most recent call last): 125 ... 126 ValidationError: [u'Invalid ID Card Number: Wrong checksum'] 127 128 ############################################################ 129 ##################### CNPhoneNumberField ################### 130 ############################################################ 131 >>> from django.contrib.localflavor.cn.forms import CNPhoneNumberField 132 >>> f = CNPhoneNumberField(required=False) 133 >>> f.clean('') 134 u'' 135 >>> f.clean('010-12345678') 136 u'010-12345678' 137 >>> f.clean('010-1234567') 138 u'010-1234567' 139 >>> f.clean('0101-12345678') 140 u'0101-12345678' 141 >>> f.clean('0101-1234567') 142 u'0101-1234567' 143 >>> f.clean('01x-12345678') 144 Traceback (most recent call last): 145 ... 146 ValidationError: [u'Enter a valid phone number.'] 147 >>> f.clean('12345678') 148 Traceback (most recent call last): 149 ... 150 ValidationError: [u'Enter a valid phone number.'] 151 >>> f.clean('01123-12345678') 152 Traceback (most recent call last): 153 ... 154 ValidationError: [u'Enter a valid phone number.'] 155 >>> f.clean('010-123456789') 156 Traceback (most recent call last): 157 ... 158 ValidationError: [u'Enter a valid phone number.'] 159 >>> f.clean('010-12345678-020') 160 u'010-12345678-020' 161 >>> f.clean('010-12345678-') 162 Traceback (most recent call last): 163 ... 164 ValidationError: [u'Enter a valid phone number.'] 165 166 ############################################################ 167 ##################### CNCellNumberField #################### 168 ############################################################ 169 >>> from django.contrib.localflavor.cn.forms import CNCellNumberField 170 >>> f = CNCellNumberField(required=False) 171 >>> f.clean('') 172 u'' 173 >>> f.clean('13012345678') 174 u'13012345678' 175 >>> f.clean('130123456789') 176 Traceback (most recent call last): 177 ... 178 ValidationError: [u'Enter a valid cell number.'] 179 >>> f.clean('14012345678') 180 Traceback (most recent call last): 181 ... 182 ValidationError: [u'Enter a valid cell number.'] 183 """ -
tests/regressiontests/forms/tests.py
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 8757e79..2cef443 100644
a b from localflavor.br import tests as localflavor_br_tests 9 9 from localflavor.ca import tests as localflavor_ca_tests 10 10 from localflavor.ch import tests as localflavor_ch_tests 11 11 from localflavor.cl import tests as localflavor_cl_tests 12 from localflavor.cn import tests as localflavor_cn_tests 12 13 from localflavor.cz import tests as localflavor_cz_tests 13 14 from localflavor.de import tests as localflavor_de_tests 14 15 from localflavor.es import tests as localflavor_es_tests … … __test__ = { 52 53 'localflavor_ca_tests': localflavor_ca_tests, 53 54 'localflavor_ch_tests': localflavor_ch_tests, 54 55 'localflavor_cl_tests': localflavor_cl_tests, 56 'localflavor_cn_tests': localflavor_cn_tests, 55 57 'localflavor_cz_tests': localflavor_cz_tests, 56 58 'localflavor_de_tests': localflavor_de_tests, 57 59 'localflavor_es_tests': localflavor_es_tests,