Ticket #12379: cn-localflavor-against-r11809.diff

File cn-localflavor-against-r11809.diff, 19.1 KB (added by Xia Kai(夏恺), 14 years ago)

Chinese localflavor patch against current trunk, and it would work for version 1.1.1

  • django/contrib/localflavor/cn/cn_provinces.py

     
     1#coding=utf-8
     2
     3"""
     4An alphabetical list of provinces for use as `choices` in a formfield.
     5
     6Reference:
     7http://en.wikipedia.org/wiki/ISO_3166-2:CN
     8http://en.wikipedia.org/wiki/Province_%28China%29
     9http://en.wikipedia.org/wiki/Direct-controlled_municipality
     10http://en.wikipedia.org/wiki/Autonomous_regions_of_China
     11"""
     12
     13
     14CN_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    ("Hong Kong", u"香港"),
     28    ("Hubei", u"湖北"),
     29    ("Hunan", u"湖南"),
     30    ("Jiangsu", u"江苏"),
     31    ("Jiangxi", u"江西"),
     32    ("Jilin", u"吉林"),
     33    ("Liaoning", u"辽宁"),
     34    ("Macao", u"澳门"),
     35    ("Nei Mongol", 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)
  • django/contrib/localflavor/cn/cn_location_code.py

     
     1#coding=utf-8
     2"""
     3This file contains a tuple of valid Chinese location codes as was
     4described in GB/T2260-1995.
     5
     6Please note that this tuple is not complete and detailed. We do not want to
     7check the last four digits of the location code, which might be changed
     8(relatively) frequently.
     9"""
     10
     11CN_LOCATION_CODES = (
     12     11 ,       # Beijing
     13     12 ,       # Tianjin
     14     13 ,       # Hebei
     15     14 ,       # Shanxi
     16     15 ,       # Nei Mongol
     17     21 ,       # Liaoning
     18     22 ,       # Jilin
     19     23 ,       # Heilongjiang
     20     31 ,       # Shanghai
     21     32 ,       # Jiangsu
     22     33 ,       # Zhejiang
     23     34 ,       # Anhui
     24     35 ,       # Fujian
     25     36 ,       # Jiangxi
     26     37 ,       # Shandong
     27     41 ,       # Henan
     28     42 ,       # Hubei
     29     43 ,       # Hunan
     30     44 ,       # Guangdong
     31     45 ,       # Guangxi
     32     46 ,       # Hainan
     33     50 ,       # Chongqing
     34     51 ,       # Sichuan
     35     52 ,       # Guizhou
     36     53 ,       # Yunnan
     37     54 ,       # Xizang
     38     61 ,       # Shaanxi
     39     62 ,       # Gansu
     40     63 ,       # Qinghai
     41     64 ,       # Ningxia
     42     65 ,       # Xinjiang
     43     71 ,       # Taiwan
     44     81 ,       # Hong Kong
     45     91 ,       # Macao
     46)
  • django/contrib/localflavor/cn/forms.py

     
     1#coding=utf-8
     2"""
     3Chinese-specific form helpers
     4"""
     5import re
     6
     7from django.forms import ValidationError
     8from django.forms.fields import CharField, RegexField, Select
     9from django.utils.translation import ugettext_lazy as _
     10
     11
     12__all__ = (
     13    'CNProvinceSelect',
     14    'CNPostCodeField',
     15    'CNIDCardField',
     16    'CNPhoneNumberField',
     17    'CNCellNumberField',
     18)
     19
     20
     21ID_CARD_RE = r'^\d{15}(\d{2}[0-9xX])?$'
     22POST_CODE_RE = r'^\d{6}$'
     23PHONE_RE = r'^\d{3,4}-\d{7,8}(-\d+)?$'
     24CELL_RE = r'^1[358]\d{9}$'
     25
     26
     27class CNProvinceSelect(Select):
     28    """
     29    A select widget with list of Chinese provinces as choices.
     30    """
     31    def __init__(self, attrs=None):
     32        from cn_provinces import CN_PROVINCE_CHOICES
     33        super(CNProvinceSelect, self).__init__(
     34            attrs, choices=CN_PROVINCE_CHOICES,
     35        )
     36
     37
     38class CNPostCodeField(RegexField):
     39    """
     40    A form field that validates as Chinese post code.
     41    Valid code is XXXXXX where X is digit.
     42    """
     43    default_error_messages = {
     44        'invalid': _(u'Enter a post code in the format XXXXXX.'),
     45    }
     46
     47    def __init__(self, *args, **kwargs):
     48        super(CNPostCodeField, self).__init__(POST_CODE_RE, *args, **kwargs)
     49
     50
     51class CNIDCardField(CharField):
     52    """
     53    A form field that validates as Chinese Identification Card Number.
     54
     55    This field would check the following restrictions:
     56        * the length could only be 15 or 18.
     57        * if the length is 18, the last digit could be x or X.
     58        * has a valid checksum.(length 18 only)
     59        * has a valid birthdate.
     60        * has a valid location.
     61
     62    The checksum algorithm is described in GB11643-1999.
     63    """
     64    default_error_messages = {
     65        'invalid': _(u'ID Card Number consists of 15 or 18 digits.'),
     66        'checksum': _(u'Invalid ID Card Number: Wrong checksum'),
     67        'birthday': _(u'Invalid ID Card Number: Wrong birthdate'),
     68        'location': _(u'Invalid ID Card Number: Wrong location code'),
     69    }
     70
     71    def __init__(self, max_length=18, min_length=15, *args, **kwargs):
     72        super(CNIDCardField, self).__init__(max_length, min_length, *args,
     73                                         **kwargs)
     74
     75    def clean(self, value):
     76        """
     77        Check whether the input is a valid ID Card Number.
     78        """
     79        # Check the length of the ID card number.
     80        super(CNIDCardField, self).clean(value)
     81        if not value:
     82            return u""
     83        # Check whether this ID card number has valid format
     84        if not re.match(ID_CARD_RE, value):
     85            raise ValidationError(self.error_messages['invalid'])
     86        # Check the birthday of the ID card number.
     87        if not self.has_valid_birthday(value):
     88            raise ValidationError(self.error_messages['birthday'])
     89        # Check the location of the ID card number.
     90        if not self.has_valid_location(value):
     91            raise ValidationError(self.error_messages['location'])
     92        # Check the checksum of the ID card number.
     93        value = value.upper()
     94        if not self.has_valid_checksum(value):
     95            raise ValidationError(self.error_messages['checksum'])
     96        return u'%s' % value
     97
     98    def has_valid_birthday(self, value):
     99        """
     100        This function would grab the birthdate from the ID card number and test
     101        whether it is a valid date.
     102        """
     103        from datetime import datetime
     104        if len(value) == 15:
     105            # 1st generation ID card
     106            time_string = value[6:12]
     107            format_string = "%y%m%d"
     108        else:
     109            # 2nd generation ID card
     110            time_string = value[6:14]
     111            format_string = "%Y%m%d"
     112        try:
     113            datetime.strptime(time_string, format_string)
     114            return True
     115        except ValueError:
     116            # invalid date
     117            return False
     118
     119    def has_valid_location(self, value):
     120        """
     121        This function would check the first two digits in the ID card number to
     122        see whether it is valid.
     123        """
     124        from cn_location_code import CN_LOCATION_CODES
     125        location_code = int(value[:2])
     126        return location_code in CN_LOCATION_CODES
     127
     128    def has_valid_checksum(self, value):
     129        """
     130        This function would check whether the last digit of the ID card number
     131        is a valid checksum.
     132        """
     133        # If the length of the number is not 18, then the number is a 1st
     134        # generation ID card number, and there is no checksum to be checked.
     135        if len(value) != 18:
     136            return True
     137        checksum_index = sum(
     138            map(
     139                lambda a,b:a*(ord(b)-ord('0')),
     140                (7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2),
     141                value[:17],
     142            ),
     143        ) % 11
     144        return '10X98765432'[checksum_index] == value[-1]
     145
     146
     147class CNPhoneNumberField(RegexField):
     148    """
     149    A form field that validates as Chinese phone number
     150    A valid phone number could be like:
     151        010-55555555
     152    Considering there might be extension phone numbers, so this could also be:
     153        010-55555555-35
     154    """
     155    default_error_messages = {
     156        'invalid': _(u'Enter a valid phone number.'),
     157    }
     158
     159    def __init__(self, *args, **kwargs):
     160        super(CNPhoneNumberField, self).__init__(PHONE_RE, *args, **kwargs)
     161
     162
     163class CNCellNumberField(RegexField):
     164    """
     165    A form field that validates as Chinese cell number
     166    A valid cell number could be like:
     167        13012345678
     168    We used a rough rule here, the first digit should be 1, the second could be
     169    3, 5 and 8, the rest could be what so ever.
     170    The length of the cell number should be 11.
     171    """
     172    default_error_messages = {
     173        'invalid': _(u'Enter a valid cell number.'),
     174    }
     175
     176    def __init__(self, *args, **kwargs):
     177        super(CNCellNumberField, self).__init__(CELL_RE, *args, **kwargs)
  • tests/regressiontests/forms/localflavor/cn.py

     
     1tests = r"""
     2
     3############################################################
     4##################### CNProvinceSelect #####################
     5############################################################
     6>>> from django.contrib.localflavor.cn.forms import CNProvinceSelect
     7>>> s = CNProvinceSelect()
     8>>> s.render('provinces', 'Hubei')
     9u'<select name="provinces">
     10    <option value="Anhui">\u5b89\u5fbd</option>
     11    <option value="Beijing">\u5317\u4eac</option>
     12    <option value="Chongqing">\u91cd\u5e86</option>
     13    <option value="Fujian">\u798f\u5efa</option>
     14    <option value="Gansu">\u7518\u8083</option>
     15    <option value="Guangdong">\u5e7f\u4e1c</option>
     16    <option value="Guangxi">\u5e7f\u897f\u58ee\u65cf\u81ea\u6cbb\u533a</option>
     17    <option value="Guizhou">\u8d35\u5dde</option>
     18    <option value="Hainan">\u6d77\u5357</option>
     19    <option value="Hebei">\u6cb3\u5317</option>
     20    <option value="Heilongjiang">\u9ed1\u9f99\u6c5f</option>
     21    <option value="Henan">\u6cb3\u5357</option>
     22    <option value="Hong Kong">\u9999\u6e2f</option>
     23    <option value="Hubei" selected="selected">\u6e56\u5317</option>
     24    <option value="Hunan">\u6e56\u5357</option>
     25    <option value="Jiangsu">\u6c5f\u82cf</option>
     26    <option value="Jiangxi">\u6c5f\u897f</option>
     27    <option value="Jilin">\u5409\u6797</option>
     28    <option value="Liaoning">\u8fbd\u5b81</option>
     29    <option value="Macao">\u6fb3\u95e8</option>
     30    <option value="Nei Mongol">\u5185\u8499\u53e4\u81ea\u6cbb\u533a</option>
     31    <option value="Ningxia">\u5b81\u590f\u56de\u65cf\u81ea\u6cbb\u533a</option>
     32    <option value="Qinghai">\u9752\u6d77</option>
     33    <option value="Shaanxi">\u9655\u897f</option>
     34    <option value="Shandong">\u5c71\u4e1c</option>
     35    <option value="Shanghai">\u4e0a\u6d77</option>
     36    <option value="Shanxi">\u5c71\u897f</option>
     37    <option value="Sichuan">\u56db\u5ddd</option>
     38    <option value="Taiwan">\u53f0\u6e7e</option>
     39    <option value="Tianjin">\u5929\u6d25</option>
     40    <option value="Xinjiang">\u65b0\u7586\u7ef4\u543e\u5c14\u81ea\u6cbb\u533a</option>
     41    <option value="Xizang">\u897f\u85cf\u81ea\u6cbb\u533a</option>
     42    <option value="Yunnan">\u4e91\u5357</option>
     43    <option value="Zhejiang">\u6d59\u6c5f</option>
     44</select>'
     45
     46############################################################
     47##################### CNPostCodeField ######################
     48############################################################
     49>>> from django.contrib.localflavor.cn.forms import CNPostCodeField
     50>>> f = CNPostCodeField(required=False)
     51>>> f.clean('')
     52u''
     53>>> f.clean('091209')
     54u'091209'
     55>>> f.clean('09120')
     56Traceback (most recent call last):
     57    ...
     58ValidationError: [u'Enter a post code in the format XXXXXX.']
     59>>> f.clean('09120916')
     60Traceback (most recent call last):
     61    ...
     62ValidationError: [u'Enter a post code in the format XXXXXX.']
     63
     64############################################################
     65##################### CNIDCardField ########################
     66############################################################
     67>>> from django.contrib.localflavor.cn.forms import CNIDCardField
     68>>> f = CNIDCardField(required=False)
     69>>> f.clean('')
     70u''
     71
     72>>> # A string of 16 characters.
     73>>> f.clean("abcdefghijklmnop")
     74Traceback (most recent call last):
     75    ...
     76ValidationError: [u'ID Card Number consists of 15 or 18 digits.']
     77
     78>>> # A string of 16 digits.
     79>>> f.clean("1010101010101010")
     80Traceback (most recent call last):
     81    ...
     82ValidationError: [u'ID Card Number consists of 15 or 18 digits.']
     83
     84>>> # A valid 1st generation ID Card number.
     85>>> f.clean('110101491001001')
     86u'110101491001001'
     87
     88>>> # A 1st generation ID Card number with invalid location
     89>>> f.clean('010101491001001')  # 01, an invalid location.
     90Traceback (most recent call last):
     91...
     92ValidationError: [u'Invalid ID Card Number: Wrong location code']
     93
     94>>> # A 1st generation ID Card number with invalid birthdate.
     95>>> f.clean('110101491041001')  # 491041, an invalid day, 41
     96Traceback (most recent call last):
     97...
     98ValidationError: [u'Invalid ID Card Number: Wrong birthdate']
     99
     100>>> # A valid 2nd generation ID Card number.
     101>>> f.clean('11010119491001001X')
     102u'11010119491001001X'
     103
     104>>> # Another valid 2nd generation ID Card number, notice that the case of the last
     105>>> # character is changed.
     106>>> f.clean('11010119491001001x')
     107u'11010119491001001X'
     108>>> # An invalid 2nd generation ID Card number.
     109>>> f.clean('92010119491001001X')   # 92 is an invalid location code.
     110Traceback (most recent call last):
     111...
     112ValidationError: [u'Invalid ID Card Number: Wrong location code']
     113
     114>>> # Another invalid 2nd generation ID Card number.
     115>>> f.clean('91010119491301001X')   # 19491301 is an invalid date.
     116Traceback (most recent call last):
     117...
     118ValidationError: [u'Invalid ID Card Number: Wrong birthdate']
     119
     120>>> # Yet another invalid 2nd generation ID Card number.
     121>>> f.clean('910101194910010014')
     122Traceback (most recent call last):
     123...
     124ValidationError: [u'Invalid ID Card Number: Wrong checksum']
     125
     126############################################################
     127##################### CNPhoneNumberField ###################
     128############################################################
     129>>> from django.contrib.localflavor.cn.forms import CNPhoneNumberField
     130>>> f = CNPhoneNumberField(required=False)
     131>>> f.clean('')
     132u''
     133>>> f.clean('010-12345678')
     134u'010-12345678'
     135>>> f.clean('010-1234567')
     136u'010-1234567'
     137>>> f.clean('0101-12345678')
     138u'0101-12345678'
     139>>> f.clean('0101-1234567')
     140u'0101-1234567'
     141>>> f.clean('01x-12345678')
     142Traceback (most recent call last):
     143...
     144ValidationError: [u'Enter a valid phone number.']
     145>>> f.clean('01123-12345678')
     146Traceback (most recent call last):
     147...
     148ValidationError: [u'Enter a valid phone number.']
     149>>> f.clean('010-123456789')
     150Traceback (most recent call last):
     151...
     152ValidationError: [u'Enter a valid phone number.']
     153>>> f.clean('010-12345678-020')
     154u'010-12345678-020'
     155>>> f.clean('010-12345678-')
     156Traceback (most recent call last):
     157...
     158ValidationError: [u'Enter a valid phone number.']
     159
     160############################################################
     161##################### CNCellNumberField ####################
     162############################################################
     163>>> from django.contrib.localflavor.cn.forms import CNCellNumberField
     164>>> f = CNCellNumberField(required=False)
     165>>> f.clean('')
     166u''
     167>>> f.clean('13012345678')
     168u'13012345678'
     169>>> f.clean('130123456789')
     170Traceback (most recent call last):
     171...
     172ValidationError: [u'Enter a valid cell number.']
     173>>> f.clean('14012345678')
     174Traceback (most recent call last):
     175...
     176ValidationError: [u'Enter a valid cell number.']
     177"""
  • tests/regressiontests/forms/tests.py

     
    99from localflavor.ca import tests as localflavor_ca_tests
    1010from localflavor.ch import tests as localflavor_ch_tests
    1111from localflavor.cl import tests as localflavor_cl_tests
     12from localflavor.cn import tests as localflavor_cn_tests
    1213from localflavor.cz import tests as localflavor_cz_tests
    1314from localflavor.de import tests as localflavor_de_tests
    1415from localflavor.es import tests as localflavor_es_tests
     
    4445    'localflavor_ca_tests': localflavor_ca_tests,
    4546    'localflavor_ch_tests': localflavor_ch_tests,
    4647    'localflavor_cl_tests': localflavor_cl_tests,
     48    'localflavor_cn_tests': localflavor_cn_tests,
    4749    'localflavor_cz_tests': localflavor_cz_tests,
    4850    'localflavor_de_tests': localflavor_de_tests,
    4951    'localflavor_es_tests': localflavor_es_tests,
  • AUTHORS

     
    197197    Brant Harris
    198198    Hawkeye
    199199    Joe Heck <http://www.rhonabwy.com/wp/>
     200    Xia Kai <http://blog.xiaket.org/>
    200201    Joel Heenan <joelh-django@planetjoel.com>
    201202    Mikko Hellsing <mikko@sorl.net>
    202203    Sebastian Hillig <sebastian.hillig@gmail.com>
  • docs/ref/contrib/localflavor.txt

     
    4444    * Brazil_
    4545    * Canada_
    4646    * Chile_
     47    * China_
    4748    * Czech_
    4849    * Finland_
    4950    * France_
     
    8485.. _Brazil: `Brazil (br)`_
    8586.. _Canada: `Canada (ca)`_
    8687.. _Chile: `Chile (cl)`_
     88.. _China: `China (cn)`_
    8789.. _Czech: `Czech (cz)`_
    8890.. _Finland: `Finland (fi)`_
    8991.. _France: `France (fr)`_
     
    233235    A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
    234236    choices.
    235237
     238China (``cn``)
     239==============
     240
     241.. class:: cn.forms.CNProvinceSelect
     242
     243    A ``Select`` widget that uses a list of Chinese regions as its choices.
     244
     245.. class:: cn.forms.CNPostCodeField
     246
     247    A form field that validates input as a Chinese post code.
     248    Valid formats are XXXXXX where X is digit.
     249
     250.. class:: cn.forms.CNIDCardField
     251
     252    A form field that validates input as a Chinese Identification Card Number.
     253    Both 1st and 2nd generation ID Card Number are validated.
     254
     255.. class:: cn.forms.CNPhoneNumberField
     256
     257    A form field that validates input as a Chinese phone number.
     258    Valid formats are 0XX-XXXXXXXX, composed of 3 or 4 digits of region code
     259    and 7 or 8 digits of phone number.
     260
     261.. class:: cn.forms.CNCellNumberField
     262
     263    A form field that validates input as a Chinese mobile phone number.
     264    Valid formats are like 1XXXXXXXXXX, where X is digit.
     265    The second digit could only be 3, 5 and 8.
     266
    236267Czech (``cz``)
    237268==============
    238269
Back to Top