~ubuntu-branches/ubuntu/saucy/python-django/saucy-updates

« back to all changes in this revision

Viewing changes to django/contrib/localflavor/id/forms.py

  • Committer: Bazaar Package Importer
  • Author(s): Chris Lamb
  • Date: 2010-05-21 07:52:55 UTC
  • mfrom: (1.3.6 upstream)
  • mto: This revision was merged to the branch mainline in revision 28.
  • Revision ID: james.westby@ubuntu.com-20100521075255-ii78v1dyfmyu3uzx
Tags: upstream-1.2
ImportĀ upstreamĀ versionĀ 1.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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:])