123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214 |
- # -*- coding: utf-8 -*-
- """
- Chinese-specific form helpers
- """
- from __future__ import absolute_import, unicode_literals
- import re
- from django.contrib.localflavor.cn.cn_provinces import CN_PROVINCE_CHOICES
- 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):
- 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': _('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': _('ID Card Number consists of 15 or 18 digits.'),
- 'checksum': _('Invalid ID Card Number: Wrong checksum'),
- 'birthday': _('Invalid ID Card Number: Wrong birthdate'),
- 'location': _('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 ""
- # 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 '%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': _('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': _('Enter a valid cell number.'),
- }
- def __init__(self, *args, **kwargs):
- super(CNCellNumberField, self).__init__(CELL_RE, *args, **kwargs)
|