| | 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() |