|
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(' ', '') |