web/lib/django/contrib/localflavor/se/forms.py
changeset 29 cc9b7e14412b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/lib/django/contrib/localflavor/se/forms.py	Tue May 25 02:43:45 2010 +0200
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+"""
+Swedish specific Form helpers
+"""
+import re
+from django import forms
+from django.utils.translation import ugettext_lazy as _
+from django.core.validators import EMPTY_VALUES
+from django.contrib.localflavor.se.utils import (id_number_checksum,
+    validate_id_birthday, format_personal_id_number, valid_organisation,
+    format_organisation_number)
+
+__all__ = ('SECountySelect', 'SEOrganisationNumberField',
+    'SEPersonalIdentityNumberField', 'SEPostalCodeField')
+
+SWEDISH_ID_NUMBER = re.compile(r'^(?P<century>\d{2})?(?P<year>\d{2})(?P<month>\d{2})(?P<day>\d{2})(?P<sign>[\-+])?(?P<serial>\d{3})(?P<checksum>\d)$')
+SE_POSTAL_CODE = re.compile(r'^[1-9]\d{2} ?\d{2}$')
+
+class SECountySelect(forms.Select):
+    """
+    A Select form widget that uses a list of the Swedish counties (län) as its
+    choices.
+
+    The cleaned value is the official county code -- see
+    http://en.wikipedia.org/wiki/Counties_of_Sweden for a list.
+    """
+
+    def __init__(self, attrs=None):
+        from se_counties import COUNTY_CHOICES
+        super(SECountySelect, self).__init__(attrs=attrs,
+                                             choices=COUNTY_CHOICES)
+
+class SEOrganisationNumberField(forms.CharField):
+    """
+    A form field that validates input as a Swedish organisation number
+    (organisationsnummer).
+
+    It accepts the same input as SEPersonalIdentityField (for sole
+    proprietorships (enskild firma). However, co-ordination numbers are not
+    accepted.
+
+    It also accepts ordinary Swedish organisation numbers with the format
+    NNNNNNNNNN.
+
+    The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN
+    for other organisations.
+    """
+
+    default_error_messages = {
+        'invalid': _('Enter a valid Swedish organisation number.'),
+    }
+
+    def clean(self, value):
+        value = super(SEOrganisationNumberField, self).clean(value)
+        
+        if value in EMPTY_VALUES:
+            return u''
+        
+        match = SWEDISH_ID_NUMBER.match(value)
+        if not match:
+            raise forms.ValidationError(self.error_messages['invalid'])
+
+        gd = match.groupdict()
+        
+        # Compare the calculated value with the checksum 
+        if id_number_checksum(gd) != int(gd['checksum']):
+            raise forms.ValidationError(self.error_messages['invalid'])
+        
+        # First: check if this is a real organisation_number
+        if valid_organisation(gd):
+            return format_organisation_number(gd)
+
+        # Is this a single properitor (enskild firma)?
+        try:
+            birth_day = validate_id_birthday(gd, False)
+            return format_personal_id_number(birth_day, gd)
+        except ValueError:
+            raise forms.ValidationError(self.error_messages['invalid'])
+
+
+class SEPersonalIdentityNumberField(forms.CharField):
+    """
+    A form field that validates input as a Swedish personal identity number
+    (personnummer).
+
+    The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX,
+    YYMMDDXXXX and YYMMDD+XXXX.
+
+    A + indicates that the person is older than 100 years, which will be taken
+    into consideration when the date is validated.
+    
+    The checksum will be calculated and checked. The birth date is checked to
+    be a valid date.
+
+    By default, co-ordination numbers (samordningsnummer) will be accepted. To
+    only allow real personal identity numbers, pass the keyword argument
+    coordination_number=False to the constructor.
+
+    The cleaned value will always have the format YYYYMMDDXXXX.
+    """
+
+    def __init__(self, coordination_number=True, *args, **kwargs):
+        self.coordination_number = coordination_number
+        super(SEPersonalIdentityNumberField, self).__init__(*args, **kwargs)
+
+    default_error_messages = {
+        'invalid': _('Enter a valid Swedish personal identity number.'),
+        'coordination_number': _('Co-ordination numbers are not allowed.'),
+    }
+
+    def clean(self, value):
+        value = super(SEPersonalIdentityNumberField, self).clean(value)
+
+        if value in EMPTY_VALUES:
+            return u''
+ 
+        match = SWEDISH_ID_NUMBER.match(value)
+        if match is None:
+            raise forms.ValidationError(self.error_messages['invalid'])
+
+        gd = match.groupdict()
+ 
+        # compare the calculated value with the checksum 
+        if id_number_checksum(gd) != int(gd['checksum']):
+            raise forms.ValidationError(self.error_messages['invalid'])
+
+        # check for valid birthday
+        try:
+            birth_day = validate_id_birthday(gd)
+        except ValueError:
+            raise forms.ValidationError(self.error_messages['invalid'])
+
+        # make sure that co-ordination numbers do not pass if not allowed 
+        if not self.coordination_number and int(gd['day']) > 60:
+            raise forms.ValidationError(self.error_messages['coordination_number'])
+  
+        return format_personal_id_number(birth_day, gd)
+
+
+class SEPostalCodeField(forms.RegexField):
+    """
+    A form field that validates input as a Swedish postal code (postnummer).
+    Valid codes consist of five digits (XXXXX). The number can optionally be
+    formatted with a space after the third digit (XXX XX).
+
+    The cleaned value will never contain the space. 
+    """
+
+    default_error_messages = {
+        'invalid': _('Enter a Swedish postal code in the format XXXXX.'),
+    }
+
+    def __init__(self, *args, **kwargs):
+        super(SEPostalCodeField, self).__init__(SE_POSTAL_CODE, *args, **kwargs)
+
+    def clean(self, value):
+        return super(SEPostalCodeField, self).clean(value).replace(' ', '')