158 lines
5.5 KiB
Python
158 lines
5.5 KiB
Python
# -*- 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(' ', '')
|