Ticket #12379: cn_localflavor_r15942.diff

File cn_localflavor_r15942.diff, 18.4 KB (added by Łukasz Rekucki, 14 years ago)

Last patch with cosmetic changes.

  • AUTHORS

    diff --git a/AUTHORS b/AUTHORS
    index a19e49b..4462c34 100644
    a b answer newbie questions, and generally made Django that much better:  
    151151    dne@mayonnaise.net
    152152    dready <wil@mojipage.com>
    153153    Maximillian Dornseif <md@hudora.de>
     154    Daniel Duan <DaNmarner@gmail.com>
    154155    Jeremy Dunck <http://dunck.us/>
    155156    Andrew Durdin <adurdin@gmail.com>
    156157    dusk@woofle.net
    answer newbie questions, and generally made Django that much better:  
    255256    Michael Josephson <http://www.sdjournal.com/>
    256257    jpellerin@gmail.com
    257258    junzhang.jn@gmail.com
     259    Xia Kai <http://blog.xiaket.org/>
    258260    Antti Kaihola <http://djangopeople.net/akaihola/>
    259261    Bahadır Kandemir <bahadir@pardus.org.tr>
    260262    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"""
     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 f54341e..08773b8 100644
    a b Countries currently supported by :mod:`~django.contrib.localflavor` are:  
    4343    * Brazil_
    4444    * Canada_
    4545    * Chile_
     46    * China_
    4647    * Czech_
    4748    * Finland_
    4849    * France_
    Here's an example of how to use them::  
    9293.. _Brazil: `Brazil (br)`_
    9394.. _Canada: `Canada (ca)`_
    9495.. _Chile: `Chile (cl)`_
     96.. _China: `China (cn)`_
    9597.. _Czech: `Czech (cz)`_
    9698.. _Finland: `Finland (fi)`_
    9799.. _France: `France (fr)`_
    Chile (``cl``)  
    322324    A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
    323325    choices.
    324326
     327China (``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
    325356Czech (``cz``)
    326357==============
    327358
  • 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
     3from django.contrib.localflavor.cn.forms import (CNProvinceSelect,
     4        CNPostCodeField, CNIDCardField, CNPhoneNumberField, CNCellNumberField)
     5from utils import LocalFlavorTestCase
     6
     7class 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  
    77from localflavor.ch import CHLocalFlavorTests
    88from localflavor.cl import CLLocalFlavorTests
    99from localflavor.cz import CZLocalFlavorTests
     10from localflavor.cn import CNLocalFlavorTests
    1011from localflavor.de import DELocalFlavorTests
    1112from localflavor.es import ESLocalFlavorTests
    1213from 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 *  
    1414from regressiontests.forms.localflavortests import (
    1515    ARLocalFlavorTests, ATLocalFlavorTests, AULocalFlavorTests,
    1616    BELocalFlavorTests, BRLocalFlavorTests, CALocalFlavorTests,
    17     CHLocalFlavorTests, CLLocalFlavorTests, CZLocalFlavorTests,
    18     DELocalFlavorTests, ESLocalFlavorTests, FILocalFlavorTests,
    19     FRLocalFlavorTests, GenericLocalFlavorTests, IDLocalFlavorTests,
    20     IELocalFlavorTests, ILLocalFlavorTests, ISLocalFlavorTests,
    21     ITLocalFlavorTests, JPLocalFlavorTests, KWLocalFlavorTests,
    22     NLLocalFlavorTests, PLLocalFlavorTests, PTLocalFlavorTests,
    23     ROLocalFlavorTests, SELocalFlavorTests, SKLocalFlavorTests,
    24     TRLocalFlavorTests, UKLocalFlavorTests, USLocalFlavorTests,
    25     UYLocalFlavorTests, ZALocalFlavorTests
     17    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
    2626)
Back to Top