From e0dc28df5517d44dab05c01d1e8855711dab6c0a Mon Sep 17 00:00:00 2001
From: Jannis Leidel <jannis@leidel.info>
Date: Fri, 1 Jan 2010 21:34:50 +0000
Subject: [PATCH] Fixed #8612 - Added Indonesian (id) localflavor. Thanks to
 Ronny Haryanto for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12046 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
 AUTHORS                                       |   1 +
 django/contrib/localflavor/id/__init__.py     |   0
 django/contrib/localflavor/id/forms.py        | 210 ++++++++++++++++++
 django/contrib/localflavor/id/id_choices.py   | 101 +++++++++
 docs/ref/contrib/localflavor.txt              |  35 +++
 tests/regressiontests/forms/localflavor/id.py | 175 +++++++++++++++
 tests/regressiontests/forms/tests.py          |   2 +
 7 files changed, 524 insertions(+)
 create mode 100644 django/contrib/localflavor/id/__init__.py
 create mode 100644 django/contrib/localflavor/id/forms.py
 create mode 100644 django/contrib/localflavor/id/id_choices.py
 create mode 100644 tests/regressiontests/forms/localflavor/id.py

diff --git a/AUTHORS b/AUTHORS
index 5593562b40c..066a07e2135 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -196,6 +196,7 @@ answer newbie questions, and generally made Django that much better:
     hambaloney
     Brian Harring <ferringb@gmail.com>
     Brant Harris
+    Ronny Haryanto <http://ronny.haryan.to/>
     Hawkeye
     Joe Heck <http://www.rhonabwy.com/wp/>
     Joel Heenan <joelh-django@planetjoel.com>
diff --git a/django/contrib/localflavor/id/__init__.py b/django/contrib/localflavor/id/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/django/contrib/localflavor/id/forms.py b/django/contrib/localflavor/id/forms.py
new file mode 100644
index 00000000000..0d68fa32d5d
--- /dev/null
+++ b/django/contrib/localflavor/id/forms.py
@@ -0,0 +1,210 @@
+"""
+ID-specific Form helpers
+"""
+
+import re
+import time
+
+from django.forms import ValidationError
+from django.forms.fields import Field, Select, EMPTY_VALUES
+from django.utils.translation import ugettext_lazy as _
+from django.utils.encoding import smart_unicode
+
+postcode_re = re.compile(r'^[1-9]\d{4}$')
+phone_re = re.compile(r'^(\+62|0)[2-9]\d{7,10}$')
+plate_re = re.compile(r'^(?P<prefix>[A-Z]{1,2}) ' + \
+            r'(?P<number>\d{1,5})( (?P<suffix>([A-Z]{1,3}|[1-9][0-9]{,2})))?$')
+nik_re = re.compile(r'^\d{16}$')
+
+
+class IDPostCodeField(Field):
+    """
+    An Indonesian post code field.
+
+    http://id.wikipedia.org/wiki/Kode_pos
+    """
+    default_error_messages = {
+        'invalid': _('Enter a valid post code'),
+    }
+
+    def clean(self, value):
+        super(IDPostCodeField, self).clean(value)
+        if value in EMPTY_VALUES:
+            return u''
+
+        value = value.strip()
+        if not postcode_re.search(value):
+            raise ValidationError(self.error_messages['invalid'])
+
+        if int(value) < 10110:
+            raise ValidationError(self.error_messages['invalid'])
+
+        # 1xxx0
+        if value[0] == '1' and value[4] != '0':
+            raise ValidationError(self.error_messages['invalid'])
+
+        return u'%s' % (value, )
+
+
+class IDProvinceSelect(Select):
+    """
+    A Select widget that uses a list of provinces of Indonesia as its
+    choices.
+    """
+
+    def __init__(self, attrs=None):
+        from id_choices import PROVINCE_CHOICES
+        super(IDProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
+
+
+class IDPhoneNumberField(Field):
+    """
+    An Indonesian telephone number field.
+
+    http://id.wikipedia.org/wiki/Daftar_kode_telepon_di_Indonesia
+    """
+    default_error_messages = {
+        'invalid': _('Enter a valid phone number'),
+    }
+
+    def clean(self, value):
+        super(IDPhoneNumberField, self).clean(value)
+        if value in EMPTY_VALUES:
+            return u''
+
+        phone_number = re.sub(r'[\-\s\(\)]', '', smart_unicode(value))
+
+        if phone_re.search(phone_number):
+            return smart_unicode(value)
+
+        raise ValidationError(self.error_messages['invalid'])
+
+
+class IDLicensePlatePrefixSelect(Select):
+    """
+    A Select widget that uses a list of vehicle license plate prefix code
+    of Indonesia as its choices.
+
+    http://id.wikipedia.org/wiki/Tanda_Nomor_Kendaraan_Bermotor
+    """
+
+    def __init__(self, attrs=None):
+        from id_choices import LICENSE_PLATE_PREFIX_CHOICES
+        super(IDLicensePlatePrefixSelect, self).__init__(attrs,
+            choices=LICENSE_PLATE_PREFIX_CHOICES)
+
+
+class IDLicensePlateField(Field):
+    """
+    An Indonesian vehicle license plate field.
+
+    http://id.wikipedia.org/wiki/Tanda_Nomor_Kendaraan_Bermotor
+
+    Plus: "B 12345 12"
+    """
+    default_error_messages = {
+        'invalid': _('Enter a valid vehicle license plate number'),
+    }
+
+    def clean(self, value):
+        super(IDLicensePlateField, self).clean(value)
+        if value in EMPTY_VALUES:
+            return u''
+
+        plate_number = re.sub(r'\s+', ' ',
+            smart_unicode(value.strip())).upper()
+
+        matches = plate_re.search(plate_number)
+        if matches is None:
+            raise ValidationError(self.error_messages['invalid'])
+
+        # Make sure prefix is in the list of known codes.
+        from id_choices import LICENSE_PLATE_PREFIX_CHOICES
+        prefix = matches.group('prefix')
+        if prefix not in [choice[0] for choice in LICENSE_PLATE_PREFIX_CHOICES]:
+            raise ValidationError(self.error_messages['invalid'])
+
+        # Only Jakarta (prefix B) can have 3 letter suffix.
+        suffix = matches.group('suffix')
+        if suffix is not None and len(suffix) == 3 and prefix != 'B':
+            raise ValidationError(self.error_messages['invalid'])
+
+        # RI plates don't have suffix.
+        if prefix == 'RI' and suffix is not None and suffix != '':
+            raise ValidationError(self.error_messages['invalid'])
+
+        # Number can't be zero.
+        number = matches.group('number')
+        if number == '0':
+            raise ValidationError(self.error_messages['invalid'])
+
+        # CD, CC and B 12345 12
+        if len(number) == 5 or prefix in ('CD', 'CC'):
+            # suffix must be numeric and non-empty
+            if re.match(r'^\d+$', suffix) is None:
+                raise ValidationError(self.error_messages['invalid'])
+
+            # Known codes range is 12-124
+            if prefix in ('CD', 'CC') and not (12 <= int(number) <= 124):
+                raise ValidationError(self.error_messages['invalid'])
+            if len(number) == 5 and not (12 <= int(suffix) <= 124):
+                raise ValidationError(self.error_messages['invalid'])
+        else:
+            # suffix must be non-numeric
+            if suffix is not None and re.match(r'^[A-Z]{,3}$', suffix) is None:
+                raise ValidationError(self.error_messages['invalid'])
+
+        return plate_number
+
+
+class IDNationalIdentityNumberField(Field):
+    """
+    An Indonesian national identity number (NIK/KTP#) field.
+
+    http://id.wikipedia.org/wiki/Nomor_Induk_Kependudukan
+
+    xx.xxxx.ddmmyy.xxxx - 16 digits (excl. dots)
+    """
+    default_error_messages = {
+        'invalid': _('Enter a valid NIK/KTP number'),
+    }
+
+    def clean(self, value):
+        super(IDNationalIdentityNumberField, self).clean(value)
+        if value in EMPTY_VALUES:
+            return u''
+
+        value = re.sub(r'[\s.]', '', smart_unicode(value))
+
+        if not nik_re.search(value):
+            raise ValidationError(self.error_messages['invalid'])
+
+        if int(value) == 0:
+            raise ValidationError(self.error_messages['invalid'])
+
+        def valid_nik_date(year, month, day):
+            try:
+                t1 = (int(year), int(month), int(day), 0, 0, 0, 0, 0, -1)
+                d = time.mktime(t1)
+                t2 = time.localtime(d)
+                if t1[:3] != t2[:3]:
+                    return False
+                else:
+                    return True
+            except (OverflowError, ValueError):
+                return False
+
+        year = int(value[10:12])
+        month = int(value[8:10])
+        day = int(value[6:8])
+        current_year = time.localtime().tm_year
+        if year < int(str(current_year)[-2:]):
+            if not valid_nik_date(2000 + int(year), month, day):
+                raise ValidationError(self.error_messages['invalid'])
+        elif not valid_nik_date(1900 + int(year), month, day):
+            raise ValidationError(self.error_messages['invalid'])
+
+        if value[:6] == '000000' or value[12:] == '0000':
+            raise ValidationError(self.error_messages['invalid'])
+
+        return '%s.%s.%s.%s' % (value[:2], value[2:6], value[6:12], value[12:])
diff --git a/django/contrib/localflavor/id/id_choices.py b/django/contrib/localflavor/id/id_choices.py
new file mode 100644
index 00000000000..ed1ea017b90
--- /dev/null
+++ b/django/contrib/localflavor/id/id_choices.py
@@ -0,0 +1,101 @@
+from django.utils.translation import ugettext_lazy as _
+
+# Reference: http://id.wikipedia.org/wiki/Daftar_provinsi_Indonesia
+
+# Indonesia does not have an official Province code standard.
+# I decided to use unambiguous and consistent (some are common) 3-letter codes.
+
+PROVINCE_CHOICES = (
+    ('BLI', _('Bali')),
+    ('BTN', _('Banten')),
+    ('BKL', _('Bengkulu')),
+    ('DIY', _('Yogyakarta')),
+    ('JKT', _('Jakarta')),
+    ('GOR', _('Gorontalo')),
+    ('JMB', _('Jambi')),
+    ('JBR', _('Jawa Barat')),
+    ('JTG', _('Jawa Tengah')),
+    ('JTM', _('Jawa Timur')),
+    ('KBR', _('Kalimantan Barat')),
+    ('KSL', _('Kalimantan Selatan')),
+    ('KTG', _('Kalimantan Tengah')),
+    ('KTM', _('Kalimantan Timur')),
+    ('BBL', _('Kepulauan Bangka-Belitung')),
+    ('KRI', _('Kepulauan Riau')),
+    ('LPG', _('Lampung')),
+    ('MLK', _('Maluku')),
+    ('MUT', _('Maluku Utara')),
+    ('NAD', _('Nanggroe Aceh Darussalam')),
+    ('NTB', _('Nusa Tenggara Barat')),
+    ('NTT', _('Nusa Tenggara Timur')),
+    ('PPA', _('Papua')),
+    ('PPB', _('Papua Barat')),
+    ('RIU', _('Riau')),
+    ('SLB', _('Sulawesi Barat')),
+    ('SLS', _('Sulawesi Selatan')),
+    ('SLT', _('Sulawesi Tengah')),
+    ('SLR', _('Sulawesi Tenggara')),
+    ('SLU', _('Sulawesi Utara')),
+    ('SMB', _('Sumatera Barat')),
+    ('SMS', _('Sumatera Selatan')),
+    ('SMU', _('Sumatera Utara')),
+)
+
+LICENSE_PLATE_PREFIX_CHOICES = (
+    ('A', _('Banten')),
+    ('AA', _('Magelang')),
+    ('AB', _('Yogyakarta')),
+    ('AD', _('Surakarta - Solo')),
+    ('AE', _('Madiun')),
+    ('AG', _('Kediri')),
+    ('B', _('Jakarta')),
+    ('BA', _('Sumatera Barat')),
+    ('BB', _('Tapanuli')),
+    ('BD', _('Bengkulu')),
+    ('BE', _('Lampung')),
+    ('BG', _('Sumatera Selatan')),
+    ('BH', _('Jambi')),
+    ('BK', _('Sumatera Utara')),
+    ('BL', _('Nanggroe Aceh Darussalam')),
+    ('BM', _('Riau')),
+    ('BN', _('Kepulauan Bangka Belitung')),
+    ('BP', _('Kepulauan Riau')),
+    ('CC', _('Corps Consulate')),
+    ('CD', _('Corps Diplomatic')),
+    ('D', _('Bandung')),
+    ('DA', _('Kalimantan Selatan')),
+    ('DB', _('Sulawesi Utara Daratan')),
+    ('DC', _('Sulawesi Barat')),
+    ('DD', _('Sulawesi Selatan')),
+    ('DE', _('Maluku')),
+    ('DG', _('Maluku Utara')),
+    ('DH', _('NTT - Timor')),
+    ('DK', _('Bali')),
+    ('DL', _('Sulawesi Utara Kepulauan')),
+    ('DM', _('Gorontalo')),
+    ('DN', _('Sulawesi Tengah')),
+    ('DR', _('NTB - Lombok')),
+    ('DS', _('Papua dan Papua Barat')),
+    ('DT', _('Sulawesi Tenggara')),
+    ('E', _('Cirebon')),
+    ('EA', _('NTB - Sumbawa')),
+    ('EB', _('NTT - Flores')),
+    ('ED', _('NTT - Sumba')),
+    ('F', _('Bogor')),
+    ('G', _('Pekalongan')),
+    ('H', _('Semarang')),
+    ('K', _('Pati')),
+    ('KB', _('Kalimantan Barat')),
+    ('KH', _('Kalimantan Tengah')),
+    ('KT', _('Kalimantan Timur')),
+    ('L', _('Surabaya')),
+    ('M', _('Madura')),
+    ('N', _('Malang')),
+    ('P', _('Jember')),
+    ('R', _('Banyumas')),
+    ('RI', _('Federal Government')),
+    ('S', _('Bojonegoro')),
+    ('T', _('Purwakarta')),
+    ('W', _('Sidoarjo')),
+    ('Z', _('Garut')),
+)
diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt
index 251729fbf6f..81c6f2431b6 100644
--- a/docs/ref/contrib/localflavor.txt
+++ b/docs/ref/contrib/localflavor.txt
@@ -50,6 +50,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
     * Germany_
     * Iceland_
     * India_
+    * Indonesia_
     * Ireland_
     * Italy_
     * Japan_
@@ -95,6 +96,7 @@ Here's an example of how to use them::
 .. _The Netherlands: `The Netherlands (nl)`_
 .. _Iceland: `Iceland (is\_)`_
 .. _India: `India (in\_)`_
+.. _Indonesia: `Indonesia (id)`_
 .. _Ireland: `Ireland (ie)`_
 .. _Italy: `Italy (it)`_
 .. _Japan: `Japan (jp)`_
@@ -382,6 +384,39 @@ Ireland (``ie``)
 
     A ``Select`` widget that uses a list of Irish Counties as its choices.
 
+Indonesia (``id``)
+==================
+
+.. class:: id.forms.IDPostCodeField
+
+    A form field that validates input as an Indonesian post code field.
+
+.. class:: id.forms.IDProvinceSelect
+
+    A ``Select`` widget that uses a list of Indonesian provinces as its choices.
+
+.. class:: id.forms.IDPhoneNumberField
+
+    A form field that validates input as an Indonesian telephone number.
+
+.. class:: id.forms.IDLicensePlatePrefixSelect
+
+    A ``Select`` widget that uses a list of Indonesian license plate
+    prefix code as its choices.
+
+.. class:: id.forms.IDLicensePlateField
+
+    A form field that validates input as an Indonesian vehicle license plate.
+
+.. class:: id.forms.IDNationalIdentityNumberField
+
+    A form field that validates input as an Indonesian national identity
+    number (`NIK`_/KTP). The output will be in the format of
+    'XX.XXXX.DDMMYY.XXXX'. Dots or spaces can be used in the input to break
+    down the numbers.
+
+.. _NIK: http://en.wikipedia.org/wiki/Indonesian_identity_card
+
 Italy (``it``)
 ==============
 
diff --git a/tests/regressiontests/forms/localflavor/id.py b/tests/regressiontests/forms/localflavor/id.py
new file mode 100644
index 00000000000..9098b9d6c04
--- /dev/null
+++ b/tests/regressiontests/forms/localflavor/id.py
@@ -0,0 +1,175 @@
+# -*- coding: utf-8 -*-
+# Tests for the contrib/localflavor/ ID form fields.
+
+tests = r"""
+
+# IDPhoneNumberField ########################################################
+
+>>> from django.contrib.localflavor.id.forms import IDPhoneNumberField
+>>> f = IDPhoneNumberField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean('0812-3456789')
+u'0812-3456789'
+>>> f.clean('081234567890')
+u'081234567890'
+>>> f.clean('021 345 6789')
+u'021 345 6789'
+>>> f.clean('0213456789')
+u'0213456789'
+>>> f.clean('0123456789')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid phone number']
+>>> f.clean('+62-21-3456789')
+u'+62-21-3456789'
+>>> f.clean('+62-021-3456789')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid phone number']
+>>> f.clean('(021) 345 6789')
+u'(021) 345 6789'
+>>> f.clean('+62-021-3456789')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid phone number']
+>>> f.clean('+62-0812-3456789')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid phone number']
+>>> f.clean('0812345678901')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid phone number']
+>>> f.clean('foo')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid phone number']
+
+# IDPostCodeField ############################################################
+
+>>> from django.contrib.localflavor.id.forms import IDPostCodeField
+>>> f = IDPostCodeField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean('12340')
+u'12340'
+>>> f.clean('25412')
+u'25412'
+>>> f.clean(' 12340 ')
+u'12340'
+>>> f.clean('12 3 4 0')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid post code']
+>>> f.clean('12345')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid post code']
+>>> f.clean('10100')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid post code']
+>>> f.clean('123456')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid post code']
+>>> f.clean('foo')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid post code']
+
+# IDNationalIdentityNumberField #########################################################
+
+>>> from django.contrib.localflavor.id.forms import IDNationalIdentityNumberField
+>>> f = IDNationalIdentityNumberField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(' 12.3456.010178 3456 ')
+u'12.3456.010178.3456'
+>>> f.clean('1234560101783456')
+u'12.3456.010178.3456'
+>>> f.clean('12.3456.010101.3456')
+u'12.3456.010101.3456'
+>>> f.clean('12.3456.310278.3456')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid NIK/KTP number']
+>>> f.clean('00.0000.010101.0000')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid NIK/KTP number']
+>>> f.clean('1234567890123456')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid NIK/KTP number']
+>>> f.clean('foo')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid NIK/KTP number']
+
+# IDProvinceSelect ##########################################################
+
+>>> from django.contrib.localflavor.id.forms import IDProvinceSelect
+>>> s = IDProvinceSelect()
+>>> s.render('provinces', 'LPG')
+u'<select name="provinces">\n<option value="BLI">Bali</option>\n<option value="BTN">Banten</option>\n<option value="BKL">Bengkulu</option>\n<option value="DIY">Yogyakarta</option>\n<option value="JKT">Jakarta</option>\n<option value="GOR">Gorontalo</option>\n<option value="JMB">Jambi</option>\n<option value="JBR">Jawa Barat</option>\n<option value="JTG">Jawa Tengah</option>\n<option value="JTM">Jawa Timur</option>\n<option value="KBR">Kalimantan Barat</option>\n<option value="KSL">Kalimantan Selatan</option>\n<option value="KTG">Kalimantan Tengah</option>\n<option value="KTM">Kalimantan Timur</option>\n<option value="BBL">Kepulauan Bangka-Belitung</option>\n<option value="KRI">Kepulauan Riau</option>\n<option value="LPG" selected="selected">Lampung</option>\n<option value="MLK">Maluku</option>\n<option value="MUT">Maluku Utara</option>\n<option value="NAD">Nanggroe Aceh Darussalam</option>\n<option value="NTB">Nusa Tenggara Barat</option>\n<option value="NTT">Nusa Tenggara Timur</option>\n<option value="PPA">Papua</option>\n<option value="PPB">Papua Barat</option>\n<option value="RIU">Riau</option>\n<option value="SLB">Sulawesi Barat</option>\n<option value="SLS">Sulawesi Selatan</option>\n<option value="SLT">Sulawesi Tengah</option>\n<option value="SLR">Sulawesi Tenggara</option>\n<option value="SLU">Sulawesi Utara</option>\n<option value="SMB">Sumatera Barat</option>\n<option value="SMS">Sumatera Selatan</option>\n<option value="SMU">Sumatera Utara</option>\n</select>'
+
+# IDLicensePlatePrefixelect ########################################################################
+
+>>> from django.contrib.localflavor.id.forms import IDLicensePlatePrefixSelect
+>>> s = IDLicensePlatePrefixSelect()
+>>> s.render('codes', 'BE')
+u'<select name="codes">\n<option value="A">Banten</option>\n<option value="AA">Magelang</option>\n<option value="AB">Yogyakarta</option>\n<option value="AD">Surakarta - Solo</option>\n<option value="AE">Madiun</option>\n<option value="AG">Kediri</option>\n<option value="B">Jakarta</option>\n<option value="BA">Sumatera Barat</option>\n<option value="BB">Tapanuli</option>\n<option value="BD">Bengkulu</option>\n<option value="BE" selected="selected">Lampung</option>\n<option value="BG">Sumatera Selatan</option>\n<option value="BH">Jambi</option>\n<option value="BK">Sumatera Utara</option>\n<option value="BL">Nanggroe Aceh Darussalam</option>\n<option value="BM">Riau</option>\n<option value="BN">Kepulauan Bangka Belitung</option>\n<option value="BP">Kepulauan Riau</option>\n<option value="CC">Corps Consulate</option>\n<option value="CD">Corps Diplomatic</option>\n<option value="D">Bandung</option>\n<option value="DA">Kalimantan Selatan</option>\n<option value="DB">Sulawesi Utara Daratan</option>\n<option value="DC">Sulawesi Barat</option>\n<option value="DD">Sulawesi Selatan</option>\n<option value="DE">Maluku</option>\n<option value="DG">Maluku Utara</option>\n<option value="DH">NTT - Timor</option>\n<option value="DK">Bali</option>\n<option value="DL">Sulawesi Utara Kepulauan</option>\n<option value="DM">Gorontalo</option>\n<option value="DN">Sulawesi Tengah</option>\n<option value="DR">NTB - Lombok</option>\n<option value="DS">Papua dan Papua Barat</option>\n<option value="DT">Sulawesi Tenggara</option>\n<option value="E">Cirebon</option>\n<option value="EA">NTB - Sumbawa</option>\n<option value="EB">NTT - Flores</option>\n<option value="ED">NTT - Sumba</option>\n<option value="F">Bogor</option>\n<option value="G">Pekalongan</option>\n<option value="H">Semarang</option>\n<option value="K">Pati</option>\n<option value="KB">Kalimantan Barat</option>\n<option value="KH">Kalimantan Tengah</option>\n<option value="KT">Kalimantan Timur</option>\n<option value="L">Surabaya</option>\n<option value="M">Madura</option>\n<option value="N">Malang</option>\n<option value="P">Jember</option>\n<option value="R">Banyumas</option>\n<option value="RI">Federal Government</option>\n<option value="S">Bojonegoro</option>\n<option value="T">Purwakarta</option>\n<option value="W">Sidoarjo</option>\n<option value="Z">Garut</option>\n</select>'
+
+# IDLicensePlateField #######################################################################
+
+>>> from django.contrib.localflavor.id.forms import IDLicensePlateField
+>>> f = IDLicensePlateField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(' b 1234  ab ')
+u'B 1234 AB'
+>>> f.clean('B 1234 ABC')
+u'B 1234 ABC'
+>>> f.clean('A 12')
+u'A 12'
+>>> f.clean('DK 12345 12')
+u'DK 12345 12'
+>>> f.clean('RI 10')
+u'RI 10'
+>>> f.clean('CD 12 12')
+u'CD 12 12'
+>>> f.clean('CD 10 12')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('CD 1234 12')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('RI 10 AB')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('B 12345 01')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('N 1234 12')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('A 12 XYZ')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('Q 1234 AB')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+>>> f.clean('foo')
+Traceback (most recent call last):
+    ...
+ValidationError: [u'Enter a valid vehicle license plate number']
+"""
\ No newline at end of file
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index e246d7290c5..72dcad89c96 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -15,6 +15,7 @@ from localflavor.es import tests as localflavor_es_tests
 from localflavor.fi import tests as localflavor_fi_tests
 from localflavor.fr import tests as localflavor_fr_tests
 from localflavor.generic import tests as localflavor_generic_tests
+from localflavor.id import tests as localflavor_id_tests
 from localflavor.ie import tests as localflavor_ie_tests
 from localflavor.is_ import tests as localflavor_is_tests
 from localflavor.it import tests as localflavor_it_tests
@@ -54,6 +55,7 @@ __test__ = {
     'localflavor_fi_tests': localflavor_fi_tests,
     'localflavor_fr_tests': localflavor_fr_tests,
     'localflavor_generic_tests': localflavor_generic_tests,
+    'localflavor_id_tests': localflavor_id_tests,
     'localflavor_ie_tests': localflavor_ie_tests,
     'localflavor_is_tests': localflavor_is_tests,
     'localflavor_it_tests': localflavor_it_tests,