| 1 | |
| 2 | """ |
| 3 | SG-specific Form helpers |
| 4 | """ |
| 5 | |
| 6 | from __future__ import absolute_import |
| 7 | |
| 8 | import re |
| 9 | import time |
| 10 | |
| 11 | from django.core.validators import EMPTY_VALUES |
| 12 | from django.forms import ValidationError |
| 13 | from django.core.validators import RegexValidator |
| 14 | from django.forms.fields import Field, Select |
| 15 | from django.utils.translation import ugettext_lazy as _ |
| 16 | from django.utils.encoding import smart_unicode |
| 17 | |
| 18 | # from http://www.ura.gov.sg/realEstateWeb/resources/misc/list_of_postal_districts.htm |
| 19 | postcode_re = re.compile(r'^(?!(83|84|85|86|87|88|89))([0-8][0-9])\d{4}$') |
| 20 | # RE for both NRIC and FIN. Check sum and validity of the numerical part |
| 21 | # are not calculated, due to branching rules and b/c the checksum isn't |
| 22 | # in the public domain. |
| 23 | # Format : @0000000# |
| 24 | # @ is centry marker, S,T for NRIC, F,G for FIN |
| 25 | # 0000000 is the serial no, with aditional constraints for birth year. |
| 26 | # # is Checksum. |
| 27 | nric_re = re.compile(r'([sftg])\d{7}[a-z]$',re.IGNORECASE) |
| 28 | mobile_phone_re = re.compile(r'^[(]?(?P<cc>\+65)[)]?(?P<num>[89]\d{7})') |
| 29 | phone_re = re.compile(r'^[(]?(?P<cc>\+65)[)]?(?P<num>[3689]\d{7})') |
| 30 | |
| 31 | class NRICValidator(RegexValidator): |
| 32 | code = 'invalid' |
| 33 | message = _('Enter a valid NRIC/FIN number') |
| 34 | regex = nric_re |
| 35 | |
| 36 | def __call__(self,value): |
| 37 | super(NRICValidator,self).__call__(value) |
| 38 | |
| 39 | validate_nric = NRICValidator() |
| 40 | |
| 41 | class SGPhoneValidator(RegexValidator): |
| 42 | code = 'invalid' |
| 43 | message = _('Enter a valid phone number'), |
| 44 | regex = phone_re |
| 45 | |
| 46 | def __call__(self,value): |
| 47 | super(SGPhoneValidator,self).__call__(value) |
| 48 | |
| 49 | validate_SG_phone = SGPhoneValidator() |
| 50 | validate_SG_mobile_phone = SGPhoneValidator(regex=mobile_phone_re) |
| 51 | |
| 52 | class SGPostCodeValidator(RegexValidator): |
| 53 | code = 'invalid' |
| 54 | message = _('Enter a valid post code'), |
| 55 | regex = postcode_re |
| 56 | |
| 57 | def __call__(self,value): |
| 58 | super(SGPostCodeValidator,self).__call__(value) |
| 59 | |
| 60 | validate_SG_postcode = SGPostCodeValidator() |
| 61 | |
| 62 | class SGPostCodeField(Field): |
| 63 | """ |
| 64 | A Singaporian post code field. |
| 65 | |
| 66 | http://www.ura.gov.sg/realEstateWeb/resources/misc/list_of_postal_districts.htm |
| 67 | """ |
| 68 | default_error_messages = { |
| 69 | 'invalid': _('Enter a valid post code'), |
| 70 | 'required': _('Post code is required'), |
| 71 | } |
| 72 | default_validators = [validate_SG_postcode] |
| 73 | |
| 74 | def validate(self, value): |
| 75 | if value in EMPTY_VALUES and self.required: |
| 76 | raise ValidationError(self.error_messages['required']) |
| 77 | |
| 78 | def to_python(self,value): |
| 79 | if not value: |
| 80 | return u'' |
| 81 | return value.strip() |
| 82 | |
| 83 | def clean(self,value): |
| 84 | value = self.to_python(value) |
| 85 | self.validate(value) |
| 86 | self.run_validators(value) |
| 87 | return value |
| 88 | |
| 89 | class SGPhoneNumberField(Field): |
| 90 | """ |
| 91 | Singaporian phone number field. |
| 92 | |
| 93 | http://en.wikipedia.org/wiki/Telephone_numbers_in_Singapore |
| 94 | """ |
| 95 | default_error_messages = { |
| 96 | 'invalid': _('Enter a valid phone number'), |
| 97 | } |
| 98 | |
| 99 | def __init__(self, mobile_phone_only=False, *args, **kwargs): |
| 100 | if mobile_phone_only: |
| 101 | validators = [validate_SG_mobile_phone,] |
| 102 | else: |
| 103 | validators = [validate_SG_phone,] |
| 104 | super(SGPhoneNumberField, self).__init__(validators=validators, *args, |
| 105 | **kwargs) |
| 106 | |
| 107 | def to_python(self,value): |
| 108 | if not value: |
| 109 | return u'' |
| 110 | return value.strip() |
| 111 | |
| 112 | def clean(self,value): |
| 113 | value = self.to_python(value) |
| 114 | self.validate(value) |
| 115 | self.run_validators(value) |
| 116 | v_search = phone_re.search(value) |
| 117 | if value in EMPTY_VALUES and not self.required: |
| 118 | return value |
| 119 | elif not v_search: |
| 120 | raise ValidationError(self.default_error_messages['invalid']) |
| 121 | |
| 122 | return u"{0}{1}".format(v_search.group('cc'), |
| 123 | v_search.group('num')) |
| 124 | |
| 125 | class SGNationalRegistrationIdentityCard(Field): |
| 126 | """ |
| 127 | Singaporian NRIC and FIN card |
| 128 | |
| 129 | http://http://en.wikipedia.org/wiki/National_Registration_Identity_Card |
| 130 | """ |
| 131 | default_error_messages = { |
| 132 | 'invalid': _('Enter a valid NRIC/FIN number'), |
| 133 | 'required': _('NRIC/FIN is required'), |
| 134 | } |
| 135 | default_validators = [RegexValidator(nric_re,code='invalid')] |
| 136 | |
| 137 | def to_python(self,value): |
| 138 | if not value: |
| 139 | return u'' |
| 140 | return value.strip() |
| 141 | |
| 142 | def clean(self,value): |
| 143 | """Validates NRIC/FIN number, cleans, etc. |
| 144 | raises ValidationError on any error""" |
| 145 | value = super(SGNationalRegistrationIdentityCard, self).clean(value) |
| 146 | return value.upper() |