Ticket #12379: cn_localflavor_r13230.diff

File cn_localflavor_r13230.diff, 18.9 KB (added by Daniel Duan <DaNmarner@…>, 14 years ago)

slightly modified.

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index 4921f7c..b991c23 100644
    a b answer newbie questions, and generally made Django that much better:  
    209209    Ronny Haryanto <http://ronny.haryan.to/>
    210210    Hawkeye
    211211    Joe Heck <http://www.rhonabwy.com/wp/>
     212    Xia Kai <http://blog.xiaket.org/>
    212213    Joel Heenan <joelh-django@planetjoel.com>
    213214    Mikko Hellsing <mikko@sorl.net>
    214215    Sebastian Hillig <sebastian.hillig@gmail.com>
    answer newbie questions, and generally made Django that much better:  
    510511    Cheng Zhang
    511512    Glenn Maynard <glenn@zewt.org>
    512513    bthomas
     514    Daniel Duan <DaNmarner@gmail.com>
    513515
    514516A big THANK YOU goes to:
    515517
  • 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"""
     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    ("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"""
     4Chinese-specific form helpers
     5"""
     6import re
     7
     8from django.forms import ValidationError
     9from django.forms.fields import CharField, RegexField, Select
     10from django.utils.translation import ugettext_lazy as _
     11
     12
     13__all__ = (
     14    'CNProvinceSelect',
     15    'CNPostCodeField',
     16    'CNIDCardField',
     17    'CNPhoneNumberField',
     18    'CNCellNumberField',
     19)
     20
     21
     22ID_CARD_RE = r'^\d{15}(\d{2}[0-9xX])?$'
     23POST_CODE_RE = r'^\d{6}$'
     24PHONE_RE = r'^\d{3,4}-\d{7,8}(-\d+)?$'
     25CELL_RE = r'^1[358]\d{9}$'
     26
     27# Valid location code used in id card checking algorithm
     28CN_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
     65class 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
     76class 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
     89class 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
     182class 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
     198class 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:  
    4444    * Brazil_
    4545    * Canada_
    4646    * Chile_
     47    * China_
    4748    * Czech_
    4849    * Finland_
    4950    * France_
    Here's an example of how to use them::  
    9091.. _Brazil: `Brazil (br)`_
    9192.. _Canada: `Canada (ca)`_
    9293.. _Chile: `Chile (cl)`_
     94.. _China: `China (cn)`_
    9395.. _Czech: `Czech (cz)`_
    9496.. _Finland: `Finland (fi)`_
    9597.. _France: `France (fr)`_
    Chile (``cl``)  
    245247    A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
    246248    choices.
    247249
     250China (``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
    248279Czech (``cz``)
    249280==============
    250281
  • 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
     3tests = r"""
     4
     5############################################################
     6##################### CNProvinceSelect #####################
     7############################################################
     8>>> from django.contrib.localflavor.cn.forms import CNProvinceSelect
     9>>> s = CNProvinceSelect()
     10>>> s.render('provinces', 'hubei')
     11u'<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('')
     54u''
     55>>> f.clean('091209')
     56u'091209'
     57>>> f.clean('09120')
     58Traceback (most recent call last):
     59    ...
     60ValidationError: [u'Enter a post code in the format XXXXXX.']
     61>>> f.clean('09120916')
     62Traceback (most recent call last):
     63    ...
     64ValidationError: [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('')
     72u''
     73
     74>>> # A string of 16 characters.
     75>>> f.clean("abcdefghijklmnop")
     76Traceback (most recent call last):
     77    ...
     78ValidationError: [u'ID Card Number consists of 15 or 18 digits.']
     79
     80>>> # A string of 16 digits.
     81>>> f.clean("1010101010101010")
     82Traceback (most recent call last):
     83    ...
     84ValidationError: [u'ID Card Number consists of 15 or 18 digits.']
     85
     86>>> # A valid 1st generation ID Card number.
     87>>> f.clean('110101491001001')
     88u'110101491001001'
     89
     90>>> # A 1st generation ID Card number with invalid location
     91>>> f.clean('010101491001001')  # 01, an invalid location.
     92Traceback (most recent call last):
     93...
     94ValidationError: [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
     98Traceback (most recent call last):
     99...
     100ValidationError: [u'Invalid ID Card Number: Wrong birthdate']
     101
     102>>> # A valid 2nd generation ID Card number.
     103>>> f.clean('11010119491001001X')
     104u'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')
     109u'11010119491001001X'
     110>>> # An invalid 2nd generation ID Card number.
     111>>> f.clean('92010119491001001X')   # 92 is an invalid location code.
     112Traceback (most recent call last):
     113...
     114ValidationError: [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.
     118Traceback (most recent call last):
     119...
     120ValidationError: [u'Invalid ID Card Number: Wrong birthdate']
     121
     122>>> # Yet another invalid 2nd generation ID Card number.
     123>>> f.clean('910101194910010014')
     124Traceback (most recent call last):
     125...
     126ValidationError: [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('')
     134u''
     135>>> f.clean('010-12345678')
     136u'010-12345678'
     137>>> f.clean('010-1234567')
     138u'010-1234567'
     139>>> f.clean('0101-12345678')
     140u'0101-12345678'
     141>>> f.clean('0101-1234567')
     142u'0101-1234567'
     143>>> f.clean('01x-12345678')
     144Traceback (most recent call last):
     145...
     146ValidationError: [u'Enter a valid phone number.']
     147>>> f.clean('12345678')
     148Traceback (most recent call last):
     149...
     150ValidationError: [u'Enter a valid phone number.']
     151>>> f.clean('01123-12345678')
     152Traceback (most recent call last):
     153...
     154ValidationError: [u'Enter a valid phone number.']
     155>>> f.clean('010-123456789')
     156Traceback (most recent call last):
     157...
     158ValidationError: [u'Enter a valid phone number.']
     159>>> f.clean('010-12345678-020')
     160u'010-12345678-020'
     161>>> f.clean('010-12345678-')
     162Traceback (most recent call last):
     163...
     164ValidationError: [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('')
     172u''
     173>>> f.clean('13012345678')
     174u'13012345678'
     175>>> f.clean('130123456789')
     176Traceback (most recent call last):
     177...
     178ValidationError: [u'Enter a valid cell number.']
     179>>> f.clean('14012345678')
     180Traceback (most recent call last):
     181...
     182ValidationError: [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  
    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
    __test__ = {  
    5253    'localflavor_ca_tests': localflavor_ca_tests,
    5354    'localflavor_ch_tests': localflavor_ch_tests,
    5455    'localflavor_cl_tests': localflavor_cl_tests,
     56    'localflavor_cn_tests': localflavor_cn_tests,
    5557    'localflavor_cz_tests': localflavor_cz_tests,
    5658    'localflavor_de_tests': localflavor_de_tests,
    5759    'localflavor_es_tests': localflavor_es_tests,
Back to Top