PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/django/contrib/localflavor/id/forms.py

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