Ticket #11626: my_localflavor.diff

File my_localflavor.diff, 21.3 KB (added by Zaim Bakar, 15 years ago)

Malaysia localflavor patch

  • django/contrib/localflavor/my/my_states.py

     
     1"""
     2A list of Malaysian states for use as `choices` in a form or model field.
     3"""
     4
     5from django.utils.translation import gettext_lazy as _
     6
     7STATE_CHOICES = (
     8    ('JHR', _(u'Johor')),
     9    ('KDH', _(u'Kedah')),
     10    ('KTN', _(u'Kelantan')),
     11    ('MLK', _(u'Melaka')),
     12    ('NSN', _(u'Negeri Sembilan')),
     13    ('PHG', _(u'Pahang ')),
     14    ('PNG', _(u'Pulau Pinang')),
     15    ('PRK', _(u'Perak')),
     16    ('PLS', _(u'Perlis')),
     17    ('SGR', _(u'Selangor')),
     18    ('TRG', _(u'Terengganu')),
     19    ('SBH', _(u'Sabah')),
     20    ('SRW', _(u'Sarawak')),
     21    ('KUL', _(u'W.P Kuala Lumpur')),
     22    ('LBN', _(u'W.P Labuan')),
     23    ('PJY', _(u'W.P Putrajaya')),
     24    ('WP',  _(u'Wilayah Persekutuan')),
     25)
  • django/contrib/localflavor/my/models.py

     
     1"""
     2Malaysia-specific model fields.
     3"""
     4
     5from django.conf import settings
     6from django.db.models.fields import Field
     7
     8class IdentityCardNumberField(Field):
     9    """
     10    A ``CharField`` validates its input is a Malaysian Identity Card number.
     11    """
     12    def get_internal_type(self):
     13        return "IdentityCardNumberField"
     14
     15    def db_type(self):
     16        if settings.DATABASE_ENGINE == 'oracle':
     17            return 'VARCHAR2(14)'
     18        else:
     19            return 'varchar(14)'
     20
     21    def formfield(self, **kwargs):
     22        from django.contrib.localflavor.my.forms import MYIdentityCardNumberField
     23        defaults = {'widget': MYIdentityCardNumberField}
     24        defaults.update(kwargs)
     25        return super(MYIdentityCardNumberField, self).formfield(**defaults)
     26
     27class PostCodeField(Field):
     28    """
     29    A model field that forms represent as a ``forms.MYPostCodeField`` and
     30    validates its input is a Malaysian postcode.
     31    """
     32    def get_internal_type(self):
     33        return "PostCodeField"
     34
     35    def db_type(self):
     36        if settings.DATABASE_ENGINE == 'oracle':
     37            return 'CHAR(5)'
     38        else:
     39            return 'varchar(5)'
     40
     41    def formfield(self, **kwargs):
     42        from django.contrib.localflavor.my.forms import MYPostCodeField
     43        defaults = {'widget': MYPostCodeField}
     44        defaults.update(kwargs)
     45        return super(MYPostCodeField, self).formfield(**defaults)
     46
     47class PhoneNumberField(Field):
     48    """
     49    A model field that forms represent as a ``forms.MYPhoneNumberField`` and
     50    validates its input is a Malaysian phone number.
     51    """
     52    def get_internal_type(self):
     53        return "PhoneNumberField"
     54
     55    def db_type(self):
     56        if settings.DATABASE_ENGINE == 'oracle':
     57            return 'VARCHAR2(14)'
     58        else:
     59            return 'varchar(14)'
     60
     61    def formfield(self, **kwargs):
     62        from django.contrib.localflavor.my.forms import MYPhoneNumberField
     63        defaults = {'widget': MYPhoneNumberField}
     64        defaults.update(kwargs)
     65        return super(MYPhoneNumberField, self).formfield(**defaults)
     66
     67class StateField(Field):
     68    """
     69    A model field that forms represent as a ``forms.MYStateField`` and stores the
     70    three-letter Malaysian state abbreviation in the database.
     71    """
     72    def get_internal_type(self):
     73        return "StateField"
     74
     75    def db_type(self):
     76        if settings.DATABASE_ENGINE == 'oracle':
     77            return 'CHAR(3)'
     78        else:
     79            return 'varchar(3)'
     80
     81    def formfield(self, **kwargs):
     82        from django.contrib.localflavor.my.forms import MYStateSelect
     83        defaults = {'widget': MYStateSelect}
     84        defaults.update(kwargs)
     85        return super(MYStateSelect, self).formfield(**defaults)
  • django/contrib/localflavor/my/my_bpcodes.py

     
     1"""
     2A list of valid BP (Birth Place) codes in the Malaysian Identity Card number.
     3
     4Source: http://en.wikipedia.org/wiki/MyKad#BP_codes_-_Codes_representing_place_of_birth
     5"""
     6
     7BP_CODES = ('00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10',
     8'11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23',
     9'24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36',
     10'37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49',
     11'50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62',
     12'63', '64', '65', '66', '67', '68', '74', '75', '76', '77', '78', '79', '82',
     13'83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93')
  • django/contrib/localflavor/my/forms.py

     
     1"""
     2Malaysia-specific form helpers.
     3"""
     4
     5from django.forms import ValidationError
     6from django.forms.fields import Field, RegexField, Select, EMPTY_VALUES
     7from django.utils.encoding import smart_unicode
     8from django.utils.translation import gettext_lazy as _
     9import re
     10
     11ic_re = re.compile(r'^(?P<dob>\d{6})-?(?P<bp>\d{2})-?(?P<serial>\d{4})$')
     12phone_re = re.compile(r'^(\+?6?)(\d{2,3}-?)(\d{6,8})$')
     13
     14class MYIdentityCardNumberField(Field):
     15    """
     16    A form field that validates its input is a valid Malaysian Identity Card number.
     17    (YYMMDD-BP-XXXX)
     18
     19    The regular expression used is sourced from: http://en.wikipedia.org/wiki/MyKad
     20
     21    Takes one extra optional argument:
     22
     23    .. attribute:: MYIdentityCardNumberField.check_bp_code
     24
     25        If ``True``, field will validate the BP digits is a valid BP (birth place)
     26        code (see above Wikipedia link for more information). Default: ``True``
     27    """
     28    default_error_messages = {
     29        'invalid': _(u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.'),
     30    }
     31
     32    def __init__(self, *args, **kwargs):
     33        self.check_bp_code = kwargs.get('check_bp_code', True)
     34        if 'check_bp_code' in kwargs:
     35            del kwargs['check_bp_code']
     36        Field.__init__(self, *args, **kwargs)
     37
     38    def clean(self, value):
     39        super(MYIdentityCardNumberField, self).clean(value)
     40        if value in EMPTY_VALUES:
     41            return u''
     42        match = re.match(ic_re, value)
     43        if not match:
     44            raise ValidationError(self.error_messages['invalid'])
     45        dob, bp, serial = match.groupdict()['dob'], match.groupdict()['bp'], match.groupdict()['serial']
     46
     47        # DOB is a valid date
     48        from datetime import date
     49        try:
     50            yy, mm, dd = int(dob[0:2]), int(dob[2:4]), int(dob[4:])
     51            d = date(yy, mm, dd)
     52        except ValueError:
     53            raise ValidationError(self.error_messages['invalid'])
     54
     55        # BP is a valid birth place code
     56        if self.check_bp_code:
     57            from my_bpcodes import BP_CODES
     58            if not bp in BP_CODES:
     59                raise ValidationError(self.error_messages['invalid'])
     60
     61        return u'%s-%s-%s' % (dob, bp, serial)
     62
     63class MYPostCodeField(RegexField):
     64    """
     65    A form field that validates its input is a 5-digit Malaysian postcode.
     66    """
     67    default_error_messages = {
     68        'invalid': _(u'Enter a postcode in the format XXXXX.')
     69    }
     70
     71    def __init__(self, *args, **kwargs):
     72        super(MYPostCodeField, self).__init__(r'^\d{5}$',
     73            max_length=None, min_length=None, *args, **kwargs)
     74
     75class MYPhoneNumberField(Field):
     76    """
     77    A form field that validates its input is a Malaysian phone number.
     78
     79    Accepts phone numbers in the format XXX-XXXXXXXX, with or without an
     80    international country code prefix (+6)
     81    """
     82    default_error_messages = {
     83        'invalid': _(u'Enter a phone number in the format XXX-XXXXXXXX.')
     84    }
     85
     86    def clean(self, value):
     87        super(MYPhoneNumberField, self).clean(value)
     88        if value in EMPTY_VALUES:
     89            return u''
     90        value = re.sub('(\(|\)|\s+)', '', smart_unicode(value))
     91        m = phone_re.search(value)
     92        if m:
     93            return u'%s%s%s' % (m.group(1), m.group(2), m.group(3))
     94        raise ValidationError(self.error_messages['invalid'])
     95
     96class MYStateSelect(Select):
     97    """
     98    A Select widget that uses a list of Malaysian states as its choices.
     99    """
     100    def __init__(self, attrs=None):
     101        from my_states import STATE_CHOICES
     102        super(MYStateSelect, self).__init__(attrs, choices=STATE_CHOICES)
  • tests/regressiontests/forms/localflavor/my.py

     
     1# Tests for the contrib/localflavor/ MY form fields.
     2
     3tests = r"""
     4# MYIdentityCardNumberField ###################################################
     5
     6>>> from django.contrib.localflavor.my.forms import MYIdentityCardNumberField
     7>>> f = MYIdentityCardNumberField()
     8>>> f.clean('821226-10-5007')
     9u'821226-10-5007'
     10>>> f.clean('821226105007')
     11u'821226-10-5007'
     12>>> f.clean('821X2-1@-5007') # non-numeric
     13Traceback (most recent call last):
     14...
     15ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     16>>> f.clean('82122-10-5007') # missing digits
     17Traceback (most recent call last):
     18...
     19ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     20>>> f.clean('821232-10-5007') # invalid date
     21Traceback (most recent call last):
     22...
     23ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     24>>> f.clean('821226-99-5007') # invalid BP code
     25Traceback (most recent call last):
     26...
     27ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     28>>> f.clean(None)
     29Traceback (most recent call last):
     30...
     31ValidationError: [u'This field is required.']
     32>>> f.clean('')
     33Traceback (most recent call last):
     34...
     35ValidationError: [u'This field is required.']
     36
     37>>> from django.contrib.localflavor.my.forms import MYIdentityCardNumberField
     38>>> f = MYIdentityCardNumberField(required=False)
     39>>> f.clean('821226-10-5007')
     40u'821226-10-5007'
     41>>> f.clean('821226105007')
     42u'821226-10-5007'
     43>>> f.clean('821X2-1@-5007') # non-numeric
     44Traceback (most recent call last):
     45...
     46ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     47>>> f.clean('82122-10-5007') # missing digits
     48Traceback (most recent call last):
     49...
     50ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     51>>> f.clean('821232-10-5007') # invalid date
     52Traceback (most recent call last):
     53...
     54ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     55>>> f.clean('821226-99-5007') # invalid BP code
     56Traceback (most recent call last):
     57...
     58ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     59>>> f.clean(None)
     60u''
     61>>> f.clean('')
     62u''
     63
     64>>> from django.contrib.localflavor.my.forms import MYIdentityCardNumberField
     65>>> f = MYIdentityCardNumberField(check_bp_code=False)
     66>>> f.clean('821226-10-5007')
     67u'821226-10-5007'
     68>>> f.clean('821226105007')
     69u'821226-10-5007'
     70>>> f.clean('821X2-1@-5007') # non-numeric
     71Traceback (most recent call last):
     72...
     73ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     74>>> f.clean('82122-10-5007') # missing digits
     75Traceback (most recent call last):
     76...
     77ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     78>>> f.clean('821232-10-5007') # invalid date
     79Traceback (most recent call last):
     80...
     81ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     82>>> f.clean('821226-99-5007') # ignores invalid BP code
     83u'821226-99-5007'
     84>>> f.clean(None)
     85Traceback (most recent call last):
     86...
     87ValidationError: [u'This field is required.']
     88>>> f.clean('')
     89Traceback (most recent call last):
     90...
     91ValidationError: [u'This field is required.']
     92
     93>>> from django.contrib.localflavor.my.forms import MYIdentityCardNumberField
     94>>> f = MYIdentityCardNumberField(check_bp_code=False, required=False)
     95>>> f.clean('821226-10-5007')
     96u'821226-10-5007'
     97>>> f.clean('821226105007')
     98u'821226-10-5007'
     99>>> f.clean('821X2-1@-5007') # non-numeric
     100Traceback (most recent call last):
     101...
     102ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     103>>> f.clean('82122-10-5007') # missing digits
     104Traceback (most recent call last):
     105...
     106ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     107>>> f.clean('821232-10-5007') # invalid date
     108Traceback (most recent call last):
     109...
     110ValidationError: [u'Enter a valid Identity Card number in XXXXXX-XX-XXXX format.']
     111>>> f.clean('821226-99-5007') # ignores invalid BP code
     112u'821226-99-5007'
     113>>> f.clean(None)
     114u''
     115>>> f.clean('')
     116u''
     117
     118# MYPostCodeField #############################################################
     119
     120>>> from django.contrib.localflavor.my.forms import MYPostCodeField
     121>>> f = MYPostCodeField()
     122>>> f.clean('41150')
     123u'41150'
     124>>> f.clean(41150)
     125u'41150'
     126>>> f.clean(4115)
     127Traceback (most recent call last):
     128...
     129ValidationError: [u'Enter a postcode in the format XXXXX.']
     130>>> f.clean(None)
     131Traceback (most recent call last):
     132...
     133ValidationError: [u'This field is required.']
     134>>> f.clean('')
     135Traceback (most recent call last):
     136...
     137ValidationError: [u'This field is required.']
     138
     139>>> from django.contrib.localflavor.my.forms import MYPostCodeField
     140>>> f = MYPostCodeField(required=False)
     141>>> f.clean('41150')
     142u'41150'
     143>>> f.clean(41150)
     144u'41150'
     145>>> f.clean(4115)
     146Traceback (most recent call last):
     147...
     148ValidationError: [u'Enter a postcode in the format XXXXX.']
     149>>> f.clean(None)
     150u''
     151>>> f.clean('')
     152u''
     153
     154# MYPhoneNumberField ##########################################################
     155
     156>>> from django.contrib.localflavor.my.forms import MYPhoneNumberField
     157>>> f = MYPhoneNumberField()
     158>>> f.clean('+603-33430633')  # full, 2-digit prefix
     159u'+603-33430633'
     160>>> f.clean('+60333430633')   # no dash
     161u'+60333430633'
     162>>> f.clean('03-33430633')    # no icc
     163u'03-33430633'
     164>>> f.clean('0333430633')     # no dash, no icc
     165u'0333430633'
     166>>> f.clean('+6016-3469486')  # full, 3-digit prefix (mobile phones)
     167u'+6016-3469486'
     168>>> f.clean('+60163469486')   # no dash
     169u'+60163469486'
     170>>> f.clean('016-3469486')    # no icc
     171u'016-3469486'
     172>>> f.clean('0163469486')     # no dash, no icc
     173u'0163469486'
     174>>> f.clean('0163-469486')
     175Traceback (most recent call last):
     176...
     177ValidationError: [u'Enter a phone number in the format XXX-XXXXXXXX.']
     178>>> f.clean('016-46948')
     179Traceback (most recent call last):
     180...
     181ValidationError: [u'Enter a phone number in the format XXX-XXXXXXXX.']
     182>>> f.clean('016-469486789')
     183Traceback (most recent call last):
     184...
     185ValidationError: [u'Enter a phone number in the format XXX-XXXXXXXX.']
     186>>> f.clean(None)
     187Traceback (most recent call last):
     188...
     189ValidationError: [u'This field is required.']
     190>>> f.clean('')
     191Traceback (most recent call last):
     192...
     193ValidationError: [u'This field is required.']
     194
     195>>> from django.contrib.localflavor.my.forms import MYPhoneNumberField
     196>>> f = MYPhoneNumberField(required=False)
     197>>> f.clean('+603-33430633')  # full, 2-digit prefix
     198u'+603-33430633'
     199>>> f.clean('+60333430633')   # no dash
     200u'+60333430633'
     201>>> f.clean('03-33430633')    # no icc
     202u'03-33430633'
     203>>> f.clean('0333430633')     # no dash, no icc
     204u'0333430633'
     205>>> f.clean('+6016-3469486')  # full, 3-digit prefix (mobile phones)
     206u'+6016-3469486'
     207>>> f.clean('+60163469486')   # no dash
     208u'+60163469486'
     209>>> f.clean('016-3469486')    # no icc
     210u'016-3469486'
     211>>> f.clean('0163469486')     # no dash, no icc
     212u'0163469486'
     213>>> f.clean('0163-469486')
     214Traceback (most recent call last):
     215...
     216ValidationError: [u'Enter a phone number in the format XXX-XXXXXXXX.']
     217>>> f.clean('016-46948')
     218Traceback (most recent call last):
     219...
     220ValidationError: [u'Enter a phone number in the format XXX-XXXXXXXX.']
     221>>> f.clean('016-469486789')
     222Traceback (most recent call last):
     223...
     224ValidationError: [u'Enter a phone number in the format XXX-XXXXXXXX.']
     225>>> f.clean(None)
     226u''
     227>>> f.clean('')
     228u''
     229
     230# MYStateSelect ##########################################################
     231
     232>>> from django.contrib.localflavor.my.forms import MYStateSelect
     233>>> f = MYStateSelect()
     234>>> print f.render('state', 'SGR')
     235<select name="state">
     236<option value="JHR">Johor</option>
     237<option value="KDH">Kedah</option>
     238<option value="KTN">Kelantan</option>
     239<option value="MLK">Melaka</option>
     240<option value="NSN">Negeri Sembilan</option>
     241<option value="PHG">Pahang </option>
     242<option value="PNG">Pulau Pinang</option>
     243<option value="PRK">Perak</option>
     244<option value="PLS">Perlis</option>
     245<option value="SGR" selected="selected">Selangor</option>
     246<option value="TRG">Terengganu</option>
     247<option value="SBH">Sabah</option>
     248<option value="SRW">Sarawak</option>
     249<option value="KUL">W.P Kuala Lumpur</option>
     250<option value="LBN">W.P Labuan</option>
     251<option value="PJY">W.P Putrajaya</option>
     252<option value="WP">Wilayah Persekutuan</option>
     253</select>
     254"""
  • tests/regressiontests/forms/tests.py

     
    1919from localflavor.is_ import tests as localflavor_is_tests
    2020from localflavor.it import tests as localflavor_it_tests
    2121from localflavor.jp import tests as localflavor_jp_tests
     22from localflavor.my import tests as localflavor_my_tests
    2223from localflavor.nl import tests as localflavor_nl_tests
    2324from localflavor.pl import tests as localflavor_pl_tests
    2425from localflavor.ro import tests as localflavor_ro_tests
     
    5354    'localflavor_is_tests': localflavor_is_tests,
    5455    'localflavor_it_tests': localflavor_it_tests,
    5556    'localflavor_jp_tests': localflavor_jp_tests,
     57    'localflavor_my_tests': localflavor_my_tests,
    5658    'localflavor_nl_tests': localflavor_nl_tests,
    5759    'localflavor_pl_tests': localflavor_pl_tests,
    5860    'localflavor_ro_tests': localflavor_ro_tests,
  • docs/ref/contrib/localflavor.txt

     
    5252    * India_
    5353    * Italy_
    5454    * Japan_
     55    * Malaysia_
    5556    * Mexico_
    5657    * `The Netherlands`_
    5758    * Norway_
     
    9394.. _India: `India (in\_)`_
    9495.. _Italy: `Italy (it)`_
    9596.. _Japan: `Japan (jp)`_
     97.. _Malaysia: `Malaysia (my)`_
    9698.. _Mexico: `Mexico (mx)`_
    9799.. _Norway: `Norway (no)`_
    98100.. _Peru: `Peru (pe)`_
     
    166168
    167169.. class:: at.forms.ATStateSelect
    168170
    169     A ``Select`` widget that uses a list of Austrian states as its choices. 
     171    A ``Select`` widget that uses a list of Austrian states as its choices.
    170172
    171173.. class:: at.forms.ATSocialSecurityNumberField
    172174
     
    406408
    407409    A ``Select`` widget that uses a list of Japanese prefectures as its choices.
    408410
     411Malaysia (``my``)
     412=================
     413
     414.. class:: my.forms.MYIdentityCardNumberField
     415
     416    A form field that validates its input is a valid Malaysian Identity Card number.
     417    (YYMMDD-BP-XXXX)
     418
     419    The regular expression used is sourced from: http://en.wikipedia.org/wiki/MyKad
     420
     421    Takes one extra optional argument:
     422
     423    .. attribute:: MYIdentityCardNumberField.check_bp_code
     424
     425        If ``True``, field will validate the BP digits is a valid BP (birth place)
     426        code (see above Wikipedia link for more information). Default: ``True``
     427
     428.. class:: my.forms.MYPostCodeField
     429
     430    A form field that validates its input is a 5-digit Malaysian postcode.
     431
     432.. class:: my.forms.MYPhoneNumberField
     433
     434    A form field that validates its input is a Malaysian phone number.
     435
     436    Accepts phone numbers in the format +6XXX-XXXXXXXX, with or without the
     437    international country code prefix (+6)
     438
     439.. class:: my.forms.MYStateSelect
     440
     441    A ``Select`` widget that uses a list of Malaysian states as its choices.
     442
     443.. class:: my.models.IdentityCardNumberField
     444
     445    A model field that forms represent as a ``forms.MYIdentityCardNumberField``
     446    and validates its input is a Malaysian Identity Card number.
     447
     448.. class:: my.models.PostCodeField
     449
     450    A model field that forms represent as a ``forms.MYPostCodeField`` and
     451    validates its input is a Malaysian postcode.
     452
     453.. class:: my.models.PhoneNumberField
     454
     455    A model field that forms represent as a ``forms.MYPhoneNumberField`` and
     456    validates its input is a Malaysian phone number.
     457
     458.. class:: my.models.StateField
     459
     460    A model field that forms represent as a ``forms.MYStateField`` and stores the
     461    three-letter Malaysian state abbreviation in the database.
     462
    409463Mexico (``mx``)
    410464===============
    411465
     
    516570
    517571.. class:: ro.forms.ROIBANField
    518572
    519     A form field that validates its input as a Romanian International Bank 
     573    A form field that validates its input as a Romanian International Bank
    520574    Account Number (IBAN). The valid format is ROXX-XXXX-XXXX-XXXX-XXXX-XXXX,
    521575    with or without hyphens.
    522576
Back to Top