Author: jezdez
Date: 2011-04-22 05:02:38 -0700 (Fri, 22 Apr 2011)
New Revision: 16070

Added:
   django/trunk/django/contrib/localflavor/cn/
   django/trunk/django/contrib/localflavor/cn/__init__.py
   django/trunk/django/contrib/localflavor/cn/cn_provinces.py
   django/trunk/django/contrib/localflavor/cn/forms.py
   django/trunk/tests/regressiontests/forms/localflavor/cn.py
Modified:
   django/trunk/AUTHORS
   django/trunk/docs/ref/contrib/localflavor.txt
   django/trunk/tests/regressiontests/forms/localflavortests.py
   django/trunk/tests/regressiontests/forms/tests/__init__.py
Log:
Fixed #12379 -- Added Chinese (cn) localflavor package. Thanks, Xia Kai, Daniel 
Duan, DaNmarner and ?\197?\129ukasz Rekucki.

Modified: django/trunk/AUTHORS
===================================================================
--- django/trunk/AUTHORS        2011-04-22 12:02:25 UTC (rev 16069)
+++ django/trunk/AUTHORS        2011-04-22 12:02:38 UTC (rev 16070)
@@ -151,6 +151,7 @@
     d...@mayonnaise.net
     dready <w...@mojipage.com>
     Maximillian Dornseif <m...@hudora.de>
+    Daniel Duan <danmar...@gmail.com>
     Jeremy Dunck <http://dunck.us/>
     Andrew Durdin <adur...@gmail.com>
     d...@woofle.net
@@ -256,6 +257,7 @@
     Michael Josephson <http://www.sdjournal.com/>
     jpelle...@gmail.com
     junzhang...@gmail.com
+    Xia Kai <http://blog.xiaket.org/>
     Antti Kaihola <http://djangopeople.net/akaihola/>
     Bahadır Kandemir <baha...@pardus.org.tr>
     Karderio <karde...@gmail.com>

Added: django/trunk/django/contrib/localflavor/cn/__init__.py
===================================================================
Added: django/trunk/django/contrib/localflavor/cn/cn_provinces.py
===================================================================
--- django/trunk/django/contrib/localflavor/cn/cn_provinces.py                  
        (rev 0)
+++ django/trunk/django/contrib/localflavor/cn/cn_provinces.py  2011-04-22 
12:02:38 UTC (rev 16070)
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+
+"""
+An alphabetical list of provinces for use as `choices` in a formfield.
+
+Reference:
+http://en.wikipedia.org/wiki/ISO_3166-2:CN
+http://en.wikipedia.org/wiki/Province_%28China%29
+http://en.wikipedia.org/wiki/Direct-controlled_municipality
+http://en.wikipedia.org/wiki/Autonomous_regions_of_China
+"""
+
+
+CN_PROVINCE_CHOICES = (
+    ("anhui", u"安徽"),
+    ("beijing", u"北京"),
+    ("chongqing", u"重庆"),
+    ("fujian", u"福建"),
+    ("gansu", u"甘肃"),
+    ("guangdong", u"广东"),
+    ("guangxi", u"广西壮族自治区"),
+    ("guizhou", u"贵州"),
+    ("hainan", u"海南"),
+    ("hebei", u"河北"),
+    ("heilongjiang", u"黑龙江"),
+    ("henan", u"河南"),
+    ("hongkong", u"香港"),
+    ("hubei", u"湖北"),
+    ("hunan", u"湖南"),
+    ("jiangsu", u"江苏"),
+    ("jiangxi", u"江西"),
+    ("jilin", u"吉林"),
+    ("liaoning", u"辽宁"),
+    ("macao", u"澳门"),
+    ("neimongol", u"内蒙古自治区"),
+    ("ningxia", u"宁夏回族自治区"),
+    ("qinghai", u"青海"),
+    ("shaanxi", u"陕西"),
+    ("shandong", u"山东"),
+    ("shanghai", u"上海"),
+    ("shanxi", u"山西"),
+    ("sichuan", u"四川"),
+    ("taiwan", u"台湾"),
+    ("tianjin", u"天津"),
+    ("xinjiang", u"新疆维吾尔自治区"),
+    ("xizang", u"西藏自治区"),
+    ("yunnan", u"云南"),
+    ("zhejiang", u"浙江"),
+)

Added: django/trunk/django/contrib/localflavor/cn/forms.py
===================================================================
--- django/trunk/django/contrib/localflavor/cn/forms.py                         
(rev 0)
+++ django/trunk/django/contrib/localflavor/cn/forms.py 2011-04-22 12:02:38 UTC 
(rev 16070)
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+
+"""
+Chinese-specific form helpers
+"""
+import re
+
+from django.forms import ValidationError
+from django.forms.fields import CharField, RegexField, Select
+from django.utils.translation import ugettext_lazy as _
+
+
+__all__ = (
+    'CNProvinceSelect',
+    'CNPostCodeField',
+    'CNIDCardField',
+    'CNPhoneNumberField',
+    'CNCellNumberField',
+)
+
+
+ID_CARD_RE = r'^\d{15}(\d{2}[0-9xX])?$'
+POST_CODE_RE = r'^\d{6}$'
+PHONE_RE = r'^\d{3,4}-\d{7,8}(-\d+)?$'
+CELL_RE = r'^1[358]\d{9}$'
+
+# Valid location code used in id card checking algorithm
+CN_LOCATION_CODES = (
+     11,  # Beijing
+     12,  # Tianjin
+     13,  # Hebei
+     14,  # Shanxi
+     15,  # Nei Mongol
+     21,  # Liaoning
+     22,  # Jilin
+     23,  # Heilongjiang
+     31,  # Shanghai
+     32,  # Jiangsu
+     33,  # Zhejiang
+     34,  # Anhui
+     35,  # Fujian
+     36,  # Jiangxi
+     37,  # Shandong
+     41,  # Henan
+     42,  # Hubei
+     43,  # Hunan
+     44,  # Guangdong
+     45,  # Guangxi
+     46,  # Hainan
+     50,  # Chongqing
+     51,  # Sichuan
+     52,  # Guizhou
+     53,  # Yunnan
+     54,  # Xizang
+     61,  # Shaanxi
+     62,  # Gansu
+     63,  # Qinghai
+     64,  # Ningxia
+     65,  # Xinjiang
+     71,  # Taiwan
+     81,  # Hong Kong
+     91,  # Macao
+)
+
+class CNProvinceSelect(Select):
+    """
+    A select widget with list of Chinese provinces as choices.
+    """
+    def __init__(self, attrs=None):
+        from cn_provinces import CN_PROVINCE_CHOICES
+        super(CNProvinceSelect, self).__init__(
+            attrs, choices=CN_PROVINCE_CHOICES,
+        )
+
+
+class CNPostCodeField(RegexField):
+    """
+    A form field that validates as Chinese post code.
+    Valid code is XXXXXX where X is digit.
+    """
+    default_error_messages = {
+        'invalid': _(u'Enter a post code in the format XXXXXX.'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(CNPostCodeField, self).__init__(POST_CODE_RE, *args, **kwargs)
+
+
+class CNIDCardField(CharField):
+    """
+    A form field that validates as Chinese Identification Card Number.
+
+    This field would check the following restrictions:
+        * the length could only be 15 or 18.
+        * if the length is 18, the last digit could be x or X.
+        * has a valid checksum.(length 18 only)
+        * has a valid birthdate.
+        * has a valid location.
+
+    The checksum algorithm is described in GB11643-1999.
+    """
+    default_error_messages = {
+        'invalid': _(u'ID Card Number consists of 15 or 18 digits.'),
+        'checksum': _(u'Invalid ID Card Number: Wrong checksum'),
+        'birthday': _(u'Invalid ID Card Number: Wrong birthdate'),
+        'location': _(u'Invalid ID Card Number: Wrong location code'),
+    }
+
+    def __init__(self, max_length=18, min_length=15, *args, **kwargs):
+        super(CNIDCardField, self).__init__(max_length, min_length, *args,
+                                         **kwargs)
+
+    def clean(self, value):
+        """
+        Check whether the input is a valid ID Card Number.
+        """
+        # Check the length of the ID card number.
+        super(CNIDCardField, self).clean(value)
+        if not value:
+            return u""
+        # Check whether this ID card number has valid format
+        if not re.match(ID_CARD_RE, value):
+            raise ValidationError(self.error_messages['invalid'])
+        # Check the birthday of the ID card number.
+        if not self.has_valid_birthday(value):
+            raise ValidationError(self.error_messages['birthday'])
+        # Check the location of the ID card number.
+        if not self.has_valid_location(value):
+            raise ValidationError(self.error_messages['location'])
+        # Check the checksum of the ID card number.
+        value = value.upper()
+        if not self.has_valid_checksum(value):
+            raise ValidationError(self.error_messages['checksum'])
+        return u'%s' % value
+
+    def has_valid_birthday(self, value):
+        """
+        This function would grab the birthdate from the ID card number and test
+        whether it is a valid date.
+        """
+        from datetime import datetime
+        if len(value) == 15:
+            # 1st generation ID card
+            time_string = value[6:12]
+            format_string = "%y%m%d"
+        else:
+            # 2nd generation ID card
+            time_string = value[6:14]
+            format_string = "%Y%m%d"
+        try:
+            datetime.strptime(time_string, format_string)
+            return True
+        except ValueError:
+            # invalid date
+            return False
+
+    def has_valid_location(self, value):
+        """
+        This method checks if the first two digits in the ID Card are valid.
+        """
+        return int(value[:2]) in CN_LOCATION_CODES
+
+    def has_valid_checksum(self, value):
+        """
+        This method checks if the last letter/digit in value is valid
+        according to the algorithm the ID Card follows.
+        """
+        # If the length of the number is not 18, then the number is a 1st
+        # generation ID card number, and there is no checksum to be checked.
+        if len(value) != 18:
+            return True
+        checksum_index = sum(
+            map(
+                lambda a,b:a*(ord(b)-ord('0')),
+                (7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2),
+                value[:17],
+            ),
+        ) % 11
+        return '10X98765432'[checksum_index] == value[-1]
+
+
+class CNPhoneNumberField(RegexField):
+    """
+    A form field that validates as Chinese phone number
+    A valid phone number could be like:
+        010-55555555
+    Considering there might be extension phone numbers, so this could also be:
+        010-55555555-35
+    """
+    default_error_messages = {
+        'invalid': _(u'Enter a valid phone number.'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(CNPhoneNumberField, self).__init__(PHONE_RE, *args, **kwargs)
+
+
+class CNCellNumberField(RegexField):
+    """
+    A form field that validates as Chinese cell number
+    A valid cell number could be like:
+        13012345678
+    We used a rough rule here, the first digit should be 1, the second could be
+    3, 5 and 8, the rest could be what so ever.
+    The length of the cell number should be 11.
+    """
+    default_error_messages = {
+        'invalid': _(u'Enter a valid cell number.'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(CNCellNumberField, self).__init__(CELL_RE, *args, **kwargs)

Modified: django/trunk/docs/ref/contrib/localflavor.txt
===================================================================
--- django/trunk/docs/ref/contrib/localflavor.txt       2011-04-22 12:02:25 UTC 
(rev 16069)
+++ django/trunk/docs/ref/contrib/localflavor.txt       2011-04-22 12:02:38 UTC 
(rev 16070)
@@ -43,6 +43,7 @@
     * Brazil_
     * Canada_
     * Chile_
+    * China_
     * Czech_
     * Finland_
     * France_
@@ -92,6 +93,7 @@
 .. _Brazil: `Brazil (br)`_
 .. _Canada: `Canada (ca)`_
 .. _Chile: `Chile (cl)`_
+.. _China: `China (cn)`_
 .. _Czech: `Czech (cz)`_
 .. _Finland: `Finland (fi)`_
 .. _France: `France (fr)`_
@@ -337,6 +339,35 @@
     A ``Select`` widget that uses a list of Chilean regions (Regiones) as its
     choices.
 
+China (``cn``)
+==============
+
+.. class:: cn.forms.CNProvinceSelect
+
+    A ``Select`` widget that uses a list of Chinese regions as its choices.
+
+.. class:: cn.forms.CNPostCodeField
+
+    A form field that validates input as a Chinese post code.
+    Valid formats are XXXXXX where X is digit.
+
+.. class:: cn.forms.CNIDCardField
+
+    A form field that validates input as a Chinese Identification Card Number.
+    Both 1st and 2nd generation ID Card Number are validated.
+
+.. class:: cn.forms.CNPhoneNumberField
+
+    A form field that validates input as a Chinese phone number.
+    Valid formats are 0XX-XXXXXXXX, composed of 3 or 4 digits of region code
+    and 7 or 8 digits of phone number.
+
+.. class:: cn.forms.CNCellNumberField
+
+    A form field that validates input as a Chinese mobile phone number.
+    Valid formats are like 1XXXXXXXXXX, where X is digit.
+    The second digit could only be 3, 5 and 8.
+
 Czech (``cz``)
 ==============
 

Added: django/trunk/tests/regressiontests/forms/localflavor/cn.py
===================================================================
--- django/trunk/tests/regressiontests/forms/localflavor/cn.py                  
        (rev 0)
+++ django/trunk/tests/regressiontests/forms/localflavor/cn.py  2011-04-22 
12:02:38 UTC (rev 16070)
@@ -0,0 +1,113 @@
+# Tests for contrib/localflavor/ CN Form Fields
+
+from django.contrib.localflavor.cn.forms import (CNProvinceSelect,
+        CNPostCodeField, CNIDCardField, CNPhoneNumberField, CNCellNumberField)
+from utils import LocalFlavorTestCase
+
+class CNLocalFlavorTests(LocalFlavorTestCase):
+    def test_CNProvinceSelect(self):
+        f = CNProvinceSelect()
+        correct_output = u'''<select name="provinces">
+<option value="anhui">\u5b89\u5fbd</option>
+<option value="beijing">\u5317\u4eac</option>
+<option value="chongqing">\u91cd\u5e86</option>
+<option value="fujian">\u798f\u5efa</option>
+<option value="gansu">\u7518\u8083</option>
+<option value="guangdong">\u5e7f\u4e1c</option>
+<option value="guangxi">\u5e7f\u897f\u58ee\u65cf\u81ea\u6cbb\u533a</option>
+<option value="guizhou">\u8d35\u5dde</option>
+<option value="hainan">\u6d77\u5357</option>
+<option value="hebei">\u6cb3\u5317</option>
+<option value="heilongjiang">\u9ed1\u9f99\u6c5f</option>
+<option value="henan">\u6cb3\u5357</option>
+<option value="hongkong">\u9999\u6e2f</option>
+<option value="hubei" selected="selected">\u6e56\u5317</option>
+<option value="hunan">\u6e56\u5357</option>
+<option value="jiangsu">\u6c5f\u82cf</option>
+<option value="jiangxi">\u6c5f\u897f</option>
+<option value="jilin">\u5409\u6797</option>
+<option value="liaoning">\u8fbd\u5b81</option>
+<option value="macao">\u6fb3\u95e8</option>
+<option value="neimongol">\u5185\u8499\u53e4\u81ea\u6cbb\u533a</option>
+<option value="ningxia">\u5b81\u590f\u56de\u65cf\u81ea\u6cbb\u533a</option>
+<option value="qinghai">\u9752\u6d77</option>
+<option value="shaanxi">\u9655\u897f</option>
+<option value="shandong">\u5c71\u4e1c</option>
+<option value="shanghai">\u4e0a\u6d77</option>
+<option value="shanxi">\u5c71\u897f</option>
+<option value="sichuan">\u56db\u5ddd</option>
+<option value="taiwan">\u53f0\u6e7e</option>
+<option value="tianjin">\u5929\u6d25</option>
+<option 
value="xinjiang">\u65b0\u7586\u7ef4\u543e\u5c14\u81ea\u6cbb\u533a</option>
+<option value="xizang">\u897f\u85cf\u81ea\u6cbb\u533a</option>
+<option value="yunnan">\u4e91\u5357</option>
+<option value="zhejiang">\u6d59\u6c5f</option>
+</select>'''
+        self.assertEqual(f.render('provinces', 'hubei'), correct_output)
+
+    def test_CNPostCodeField(self):
+        error_format = [u'Enter a post code in the format XXXXXX.']
+        valid = {
+                '091209': u'091209'
+        }
+        invalid = {
+                '09120': error_format,
+                '09120916': error_format
+        }
+        self.assertFieldOutput(CNPostCodeField, valid, invalid)
+
+    def test_CNIDCardField(self):
+        valid = {
+                # A valid 1st generation ID Card Number.
+                '110101491001001': u'110101491001001',
+                # A valid 2nd generation ID Card number.
+                '11010119491001001X': u'11010119491001001X',
+                # Another valid 2nd gen ID Number with a case change
+                '11010119491001001x': u'11010119491001001X'
+        }
+
+        wrong_format = [u'ID Card Number consists of 15 or 18 digits.']
+        wrong_location = [u'Invalid ID Card Number: Wrong location code']
+        wrong_bday = [u'Invalid ID Card Number: Wrong birthdate']
+        wrong_checksum = [u'Invalid ID Card Number: Wrong checksum']
+
+        invalid = {
+                'abcdefghijklmnop': wrong_format,
+                '1010101010101010': wrong_format,
+                '010101491001001' : wrong_location, # 1st gen, 01 is invalid
+                '110101491041001' : wrong_bday, # 1st gen. There wasn't day 41
+                '92010119491001001X': wrong_location, # 2nd gen, 92 is invalid
+                '91010119491301001X': wrong_bday, # 2nd gen, 19491301 is 
invalid date
+                '910101194910010014': wrong_checksum #2nd gen
+        }
+        self.assertFieldOutput(CNIDCardField, valid, invalid)
+
+    def test_CNPhoneNumberField(self):
+        error_format = [u'Enter a valid phone number.']
+        valid = {
+                '010-12345678': u'010-12345678',
+                '010-1234567': u'010-1234567',
+                '0101-12345678': u'0101-12345678',
+                '0101-1234567': u'0101-1234567',
+                '010-12345678-020':u'010-12345678-020'
+        }
+        invalid = {
+                '01x-12345678': error_format,
+                '12345678': error_format,
+                '01123-12345678': error_format,
+                '010-123456789': error_format,
+                '010-12345678-': error_format
+        }
+        self.assertFieldOutput(CNPhoneNumberField, valid, invalid)
+
+    def test_CNCellNumberField(self):
+        error_format = [u'Enter a valid cell number.']
+        valid = {
+                '13012345678': u'13012345678',
+        }
+        invalid = {
+                '130123456789': error_format,
+                '14012345678': error_format
+        }
+        self.assertFieldOutput(CNCellNumberField, valid, invalid)
+

Modified: django/trunk/tests/regressiontests/forms/localflavortests.py
===================================================================
--- django/trunk/tests/regressiontests/forms/localflavortests.py        
2011-04-22 12:02:25 UTC (rev 16069)
+++ django/trunk/tests/regressiontests/forms/localflavortests.py        
2011-04-22 12:02:38 UTC (rev 16070)
@@ -7,6 +7,7 @@
 from localflavor.ch import CHLocalFlavorTests
 from localflavor.cl import CLLocalFlavorTests
 from localflavor.cz import CZLocalFlavorTests
+from localflavor.cn import CNLocalFlavorTests
 from localflavor.de import DELocalFlavorTests
 from localflavor.es import ESLocalFlavorTests
 from localflavor.fi import FILocalFlavorTests

Modified: django/trunk/tests/regressiontests/forms/tests/__init__.py
===================================================================
--- django/trunk/tests/regressiontests/forms/tests/__init__.py  2011-04-22 
12:02:25 UTC (rev 16069)
+++ django/trunk/tests/regressiontests/forms/tests/__init__.py  2011-04-22 
12:02:38 UTC (rev 16070)
@@ -12,15 +12,37 @@
 from widgets import *
 
 from regressiontests.forms.localflavortests import (
-    ARLocalFlavorTests, ATLocalFlavorTests, AULocalFlavorTests,
-    BELocalFlavorTests, BRLocalFlavorTests, CALocalFlavorTests,
-    CHLocalFlavorTests, CLLocalFlavorTests, CZLocalFlavorTests,
-    DELocalFlavorTests, ESLocalFlavorTests, FILocalFlavorTests,
-    FRLocalFlavorTests, GenericLocalFlavorTests, IDLocalFlavorTests,
-    IELocalFlavorTests, ILLocalFlavorTests, ISLocalFlavorTests,
-    ITLocalFlavorTests, JPLocalFlavorTests, KWLocalFlavorTests,
-    NLLocalFlavorTests, PLLocalFlavorTests, PTLocalFlavorTests,
-    ROLocalFlavorTests, SELocalFlavorTests, SKLocalFlavorTests,
-    TRLocalFlavorTests, UKLocalFlavorTests, USLocalFlavorTests,
-    UYLocalFlavorTests, ZALocalFlavorTests
+    ARLocalFlavorTests,
+    ATLocalFlavorTests,
+    AULocalFlavorTests,
+    BELocalFlavorTests,
+    BRLocalFlavorTests,
+    CALocalFlavorTests,
+    CHLocalFlavorTests,
+    CLLocalFlavorTests,
+    CNLocalFlavorTests,
+    CZLocalFlavorTests,
+    DELocalFlavorTests,
+    ESLocalFlavorTests,
+    FILocalFlavorTests,
+    FRLocalFlavorTests,
+    GenericLocalFlavorTests,
+    IDLocalFlavorTests,
+    IELocalFlavorTests,
+    ILLocalFlavorTests,
+    ISLocalFlavorTests,
+    ITLocalFlavorTests,
+    JPLocalFlavorTests,
+    KWLocalFlavorTests,
+    NLLocalFlavorTests,
+    PLLocalFlavorTests,
+    PTLocalFlavorTests,
+    ROLocalFlavorTests,
+    SELocalFlavorTests,
+    SKLocalFlavorTests,
+    TRLocalFlavorTests,
+    UKLocalFlavorTests,
+    USLocalFlavorTests,
+    UYLocalFlavorTests,
+    ZALocalFlavorTests
 )

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to django-updates@googlegroups.com.
To unsubscribe from this group, send email to 
django-updates+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en.

Reply via email to