PageRenderTime 312ms CodeModel.GetById 79ms app.highlight 130ms RepoModel.GetById 94ms app.codeStats 0ms

/django/contrib/localflavor/se/forms.py

https://code.google.com/p/mango-py/
Python | 157 lines | 124 code | 8 blank | 25 comment | 4 complexity | 0d4e0bb4a0d91c0b81a77a031644ddae MD5 | raw file
  1# -*- coding: utf-8 -*-
  2"""
  3Swedish specific Form helpers
  4"""
  5import re
  6from django import forms
  7from django.utils.translation import ugettext_lazy as _
  8from django.core.validators import EMPTY_VALUES
  9from django.contrib.localflavor.se.utils import (id_number_checksum,
 10    validate_id_birthday, format_personal_id_number, valid_organisation,
 11    format_organisation_number)
 12
 13__all__ = ('SECountySelect', 'SEOrganisationNumberField',
 14    'SEPersonalIdentityNumberField', 'SEPostalCodeField')
 15
 16SWEDISH_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)$')
 17SE_POSTAL_CODE = re.compile(r'^[1-9]\d{2} ?\d{2}$')
 18
 19class SECountySelect(forms.Select):
 20    """
 21    A Select form widget that uses a list of the Swedish counties (l?n) as its
 22    choices.
 23
 24    The cleaned value is the official county code -- see
 25    http://en.wikipedia.org/wiki/Counties_of_Sweden for a list.
 26    """
 27
 28    def __init__(self, attrs=None):
 29        from se_counties import COUNTY_CHOICES
 30        super(SECountySelect, self).__init__(attrs=attrs,
 31                                             choices=COUNTY_CHOICES)
 32
 33class SEOrganisationNumberField(forms.CharField):
 34    """
 35    A form field that validates input as a Swedish organisation number
 36    (organisationsnummer).
 37
 38    It accepts the same input as SEPersonalIdentityField (for sole
 39    proprietorships (enskild firma). However, co-ordination numbers are not
 40    accepted.
 41
 42    It also accepts ordinary Swedish organisation numbers with the format
 43    NNNNNNNNNN.
 44
 45    The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN
 46    for other organisations.
 47    """
 48
 49    default_error_messages = {
 50        'invalid': _('Enter a valid Swedish organisation number.'),
 51    }
 52
 53    def clean(self, value):
 54        value = super(SEOrganisationNumberField, self).clean(value)
 55        
 56        if value in EMPTY_VALUES:
 57            return u''
 58        
 59        match = SWEDISH_ID_NUMBER.match(value)
 60        if not match:
 61            raise forms.ValidationError(self.error_messages['invalid'])
 62
 63        gd = match.groupdict()
 64        
 65        # Compare the calculated value with the checksum 
 66        if id_number_checksum(gd) != int(gd['checksum']):
 67            raise forms.ValidationError(self.error_messages['invalid'])
 68        
 69        # First: check if this is a real organisation_number
 70        if valid_organisation(gd):
 71            return format_organisation_number(gd)
 72
 73        # Is this a single properitor (enskild firma)?
 74        try:
 75            birth_day = validate_id_birthday(gd, False)
 76            return format_personal_id_number(birth_day, gd)
 77        except ValueError:
 78            raise forms.ValidationError(self.error_messages['invalid'])
 79
 80
 81class SEPersonalIdentityNumberField(forms.CharField):
 82    """
 83    A form field that validates input as a Swedish personal identity number
 84    (personnummer).
 85
 86    The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX,
 87    YYMMDDXXXX and YYMMDD+XXXX.
 88
 89    A + indicates that the person is older than 100 years, which will be taken
 90    into consideration when the date is validated.
 91    
 92    The checksum will be calculated and checked. The birth date is checked to
 93    be a valid date.
 94
 95    By default, co-ordination numbers (samordningsnummer) will be accepted. To
 96    only allow real personal identity numbers, pass the keyword argument
 97    coordination_number=False to the constructor.
 98
 99    The cleaned value will always have the format YYYYMMDDXXXX.
100    """
101
102    def __init__(self, coordination_number=True, *args, **kwargs):
103        self.coordination_number = coordination_number
104        super(SEPersonalIdentityNumberField, self).__init__(*args, **kwargs)
105
106    default_error_messages = {
107        'invalid': _('Enter a valid Swedish personal identity number.'),
108        'coordination_number': _('Co-ordination numbers are not allowed.'),
109    }
110
111    def clean(self, value):
112        value = super(SEPersonalIdentityNumberField, self).clean(value)
113
114        if value in EMPTY_VALUES:
115            return u''
116 
117        match = SWEDISH_ID_NUMBER.match(value)
118        if match is None:
119            raise forms.ValidationError(self.error_messages['invalid'])
120
121        gd = match.groupdict()
122 
123        # compare the calculated value with the checksum 
124        if id_number_checksum(gd) != int(gd['checksum']):
125            raise forms.ValidationError(self.error_messages['invalid'])
126
127        # check for valid birthday
128        try:
129            birth_day = validate_id_birthday(gd)
130        except ValueError:
131            raise forms.ValidationError(self.error_messages['invalid'])
132
133        # make sure that co-ordination numbers do not pass if not allowed 
134        if not self.coordination_number and int(gd['day']) > 60:
135            raise forms.ValidationError(self.error_messages['coordination_number'])
136  
137        return format_personal_id_number(birth_day, gd)
138
139
140class SEPostalCodeField(forms.RegexField):
141    """
142    A form field that validates input as a Swedish postal code (postnummer).
143    Valid codes consist of five digits (XXXXX). The number can optionally be
144    formatted with a space after the third digit (XXX XX).
145
146    The cleaned value will never contain the space. 
147    """
148
149    default_error_messages = {
150        'invalid': _('Enter a Swedish postal code in the format XXXXX.'),
151    }
152
153    def __init__(self, *args, **kwargs):
154        super(SEPostalCodeField, self).__init__(SE_POSTAL_CODE, *args, **kwargs)
155
156    def clean(self, value):
157        return super(SEPostalCodeField, self).clean(value).replace(' ', '')