|
29
|
1 |
# -*- coding: utf-8 -*- |
|
|
2 |
""" |
|
|
3 |
Swedish specific Form helpers |
|
|
4 |
""" |
|
|
5 |
import re |
|
|
6 |
from django import forms |
|
|
7 |
from django.utils.translation import ugettext_lazy as _ |
|
|
8 |
from django.core.validators import EMPTY_VALUES |
|
|
9 |
from 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 |
|
|
|
16 |
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)$') |
|
|
17 |
SE_POSTAL_CODE = re.compile(r'^[1-9]\d{2} ?\d{2}$') |
|
|
18 |
|
|
|
19 |
class 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 |
|
|
|
33 |
class 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 |
|
|
|
81 |
class 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 |
|
|
|
140 |
class 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(' ', '') |