web/lib/django/contrib/localflavor/id/forms.py
changeset 29 cc9b7e14412b
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
       
     1 """
       
     2 ID-specific Form helpers
       
     3 """
       
     4 
       
     5 import re
       
     6 import time
       
     7 
       
     8 from django.core.validators import EMPTY_VALUES
       
     9 from django.forms import ValidationError
       
    10 from django.forms.fields import Field, Select
       
    11 from django.utils.translation import ugettext_lazy as _
       
    12 from django.utils.encoding import smart_unicode
       
    13 
       
    14 postcode_re = re.compile(r'^[1-9]\d{4}$')
       
    15 phone_re = re.compile(r'^(\+62|0)[2-9]\d{7,10}$')
       
    16 plate_re = re.compile(r'^(?P<prefix>[A-Z]{1,2}) ' + \
       
    17             r'(?P<number>\d{1,5})( (?P<suffix>([A-Z]{1,3}|[1-9][0-9]{,2})))?$')
       
    18 nik_re = re.compile(r'^\d{16}$')
       
    19 
       
    20 
       
    21 class IDPostCodeField(Field):
       
    22     """
       
    23     An Indonesian post code field.
       
    24 
       
    25     http://id.wikipedia.org/wiki/Kode_pos
       
    26     """
       
    27     default_error_messages = {
       
    28         'invalid': _('Enter a valid post code'),
       
    29     }
       
    30 
       
    31     def clean(self, value):
       
    32         super(IDPostCodeField, self).clean(value)
       
    33         if value in EMPTY_VALUES:
       
    34             return u''
       
    35 
       
    36         value = value.strip()
       
    37         if not postcode_re.search(value):
       
    38             raise ValidationError(self.error_messages['invalid'])
       
    39 
       
    40         if int(value) < 10110:
       
    41             raise ValidationError(self.error_messages['invalid'])
       
    42 
       
    43         # 1xxx0
       
    44         if value[0] == '1' and value[4] != '0':
       
    45             raise ValidationError(self.error_messages['invalid'])
       
    46 
       
    47         return u'%s' % (value, )
       
    48 
       
    49 
       
    50 class IDProvinceSelect(Select):
       
    51     """
       
    52     A Select widget that uses a list of provinces of Indonesia as its
       
    53     choices.
       
    54     """
       
    55 
       
    56     def __init__(self, attrs=None):
       
    57         from id_choices import PROVINCE_CHOICES
       
    58         super(IDProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
       
    59 
       
    60 
       
    61 class IDPhoneNumberField(Field):
       
    62     """
       
    63     An Indonesian telephone number field.
       
    64 
       
    65     http://id.wikipedia.org/wiki/Daftar_kode_telepon_di_Indonesia
       
    66     """
       
    67     default_error_messages = {
       
    68         'invalid': _('Enter a valid phone number'),
       
    69     }
       
    70 
       
    71     def clean(self, value):
       
    72         super(IDPhoneNumberField, self).clean(value)
       
    73         if value in EMPTY_VALUES:
       
    74             return u''
       
    75 
       
    76         phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value))
       
    77 
       
    78         if phone_re.search(phone_number):
       
    79             return smart_unicode(value)
       
    80 
       
    81         raise ValidationError(self.error_messages['invalid'])
       
    82 
       
    83 
       
    84 class IDLicensePlatePrefixSelect(Select):
       
    85     """
       
    86     A Select widget that uses a list of vehicle license plate prefix code
       
    87     of Indonesia as its choices.
       
    88 
       
    89     http://id.wikipedia.org/wiki/Tanda_Nomor_Kendaraan_Bermotor
       
    90     """
       
    91 
       
    92     def __init__(self, attrs=None):
       
    93         from id_choices import LICENSE_PLATE_PREFIX_CHOICES
       
    94         super(IDLicensePlatePrefixSelect, self).__init__(attrs,
       
    95             choices=LICENSE_PLATE_PREFIX_CHOICES)
       
    96 
       
    97 
       
    98 class IDLicensePlateField(Field):
       
    99     """
       
   100     An Indonesian vehicle license plate field.
       
   101 
       
   102     http://id.wikipedia.org/wiki/Tanda_Nomor_Kendaraan_Bermotor
       
   103 
       
   104     Plus: "B 12345 12"
       
   105     """
       
   106     default_error_messages = {
       
   107         'invalid': _('Enter a valid vehicle license plate number'),
       
   108     }
       
   109 
       
   110     def clean(self, value):
       
   111         super(IDLicensePlateField, self).clean(value)
       
   112         if value in EMPTY_VALUES:
       
   113             return u''
       
   114 
       
   115         plate_number = re.sub(r'\s+', ' ',
       
   116             smart_unicode(value.strip())).upper()
       
   117 
       
   118         matches = plate_re.search(plate_number)
       
   119         if matches is None:
       
   120             raise ValidationError(self.error_messages['invalid'])
       
   121 
       
   122         # Make sure prefix is in the list of known codes.
       
   123         from id_choices import LICENSE_PLATE_PREFIX_CHOICES
       
   124         prefix = matches.group('prefix')
       
   125         if prefix not in [choice[0] for choice in LICENSE_PLATE_PREFIX_CHOICES]:
       
   126             raise ValidationError(self.error_messages['invalid'])
       
   127 
       
   128         # Only Jakarta (prefix B) can have 3 letter suffix.
       
   129         suffix = matches.group('suffix')
       
   130         if suffix is not None and len(suffix) == 3 and prefix != 'B':
       
   131             raise ValidationError(self.error_messages['invalid'])
       
   132 
       
   133         # RI plates don't have suffix.
       
   134         if prefix == 'RI' and suffix is not None and suffix != '':
       
   135             raise ValidationError(self.error_messages['invalid'])
       
   136 
       
   137         # Number can't be zero.
       
   138         number = matches.group('number')
       
   139         if number == '0':
       
   140             raise ValidationError(self.error_messages['invalid'])
       
   141 
       
   142         # CD, CC and B 12345 12
       
   143         if len(number) == 5 or prefix in ('CD', 'CC'):
       
   144             # suffix must be numeric and non-empty
       
   145             if re.match(r'^\d+$', suffix) is None:
       
   146                 raise ValidationError(self.error_messages['invalid'])
       
   147 
       
   148             # Known codes range is 12-124
       
   149             if prefix in ('CD', 'CC') and not (12 <= int(number) <= 124):
       
   150                 raise ValidationError(self.error_messages['invalid'])
       
   151             if len(number) == 5 and not (12 <= int(suffix) <= 124):
       
   152                 raise ValidationError(self.error_messages['invalid'])
       
   153         else:
       
   154             # suffix must be non-numeric
       
   155             if suffix is not None and re.match(r'^[A-Z]{,3}$', suffix) is None:
       
   156                 raise ValidationError(self.error_messages['invalid'])
       
   157 
       
   158         return plate_number
       
   159 
       
   160 
       
   161 class IDNationalIdentityNumberField(Field):
       
   162     """
       
   163     An Indonesian national identity number (NIK/KTP#) field.
       
   164 
       
   165     http://id.wikipedia.org/wiki/Nomor_Induk_Kependudukan
       
   166 
       
   167     xx.xxxx.ddmmyy.xxxx - 16 digits (excl. dots)
       
   168     """
       
   169     default_error_messages = {
       
   170         'invalid': _('Enter a valid NIK/KTP number'),
       
   171     }
       
   172 
       
   173     def clean(self, value):
       
   174         super(IDNationalIdentityNumberField, self).clean(value)
       
   175         if value in EMPTY_VALUES:
       
   176             return u''
       
   177 
       
   178         value = re.sub(r'[\s.]', '', smart_unicode(value))
       
   179 
       
   180         if not nik_re.search(value):
       
   181             raise ValidationError(self.error_messages['invalid'])
       
   182 
       
   183         if int(value) == 0:
       
   184             raise ValidationError(self.error_messages['invalid'])
       
   185 
       
   186         def valid_nik_date(year, month, day):
       
   187             try:
       
   188                 t1 = (int(year), int(month), int(day), 0, 0, 0, 0, 0, -1)
       
   189                 d = time.mktime(t1)
       
   190                 t2 = time.localtime(d)
       
   191                 if t1[:3] != t2[:3]:
       
   192                     return False
       
   193                 else:
       
   194                     return True
       
   195             except (OverflowError, ValueError):
       
   196                 return False
       
   197 
       
   198         year = int(value[10:12])
       
   199         month = int(value[8:10])
       
   200         day = int(value[6:8])
       
   201         current_year = time.localtime().tm_year
       
   202         if year < int(str(current_year)[-2:]):
       
   203             if not valid_nik_date(2000 + int(year), month, day):
       
   204                 raise ValidationError(self.error_messages['invalid'])
       
   205         elif not valid_nik_date(1900 + int(year), month, day):
       
   206             raise ValidationError(self.error_messages['invalid'])
       
   207 
       
   208         if value[:6] == '000000' or value[12:] == '0000':
       
   209             raise ValidationError(self.error_messages['invalid'])
       
   210 
       
   211         return '%s.%s.%s.%s' % (value[:2], value[2:6], value[6:12], value[12:])