Code

Ticket #9289: django_localflavor_se_r3.diff

File django_localflavor_se_r3.diff, 23.5 KB (added by peritus, 6 years ago)

Updated patch which works when the checksum digit is 0, tests updated.

Line 
1diff --git a/AUTHORS b/AUTHORS
2index 7bbec38..558f0ff 100644
3--- a/AUTHORS
4+++ b/AUTHORS
5@@ -297,6 +297,7 @@ answer newbie questions, and generally made Django that much better:
6     Carlos Eduardo de Paula <carlosedp@gmail.com>
7     pavithran s <pavithran.s@gmail.com>
8     Barry Pederson <bp@barryp.org>
9+    Andreas Pelme <andreas@pelme.se>
10     permonik@mesias.brnonet.cz
11     peter@mymart.com
12     pgross@thoughtworks.com
13diff --git a/django/contrib/localflavor/se/__init__.py b/django/contrib/localflavor/se/__init__.py
14new file mode 100644
15index 0000000..e69de29
16diff --git a/django/contrib/localflavor/se/forms.py b/django/contrib/localflavor/se/forms.py
17new file mode 100644
18index 0000000..84233c8
19--- /dev/null
20+++ b/django/contrib/localflavor/se/forms.py
21@@ -0,0 +1,153 @@
22+# -*- coding: utf-8 -*-
23+"""
24+Swedish specific Form helpers
25+"""
26+
27+from django import forms
28+from django.utils.translation import ugettext_lazy as _
29+from django.forms.fields import EMPTY_VALUES
30+
31+from utils import *
32+
33+__all__ = ('SECountySelect', 'SEOrganisationNumberField', 'SEPersonalIdentityNumberField', 'SEPostalCodeField')
34+
35+class SECountySelect(forms.Select):
36+    """
37+    A Select form widget that uses a list of the Swedish counties (län)
38+    as its choices.
39+
40+    The cleaned value is the official county code -- see
41+    http://en.wikipedia.org/wiki/Counties_of_Sweden for a list.
42+    """
43+
44+    def __init__(self, attrs=None):
45+        from se_counties import COUNTY_CHOICES
46+        super(SECountySelect, self).__init__(attrs=attrs, choices=COUNTY_CHOICES)
47+
48+
49+class SEOrganisationNumberField(forms.CharField):
50+    """
51+    A form field that validates input as a Swedish organisation number
52+    (organisationsnummer).
53+
54+    This fields accepts the same input as SEPersonalIdentityField (for
55+    sole proprietorships (enskild firma)). However, co-ordination numbers are
56+    not accepted.
57+
58+    It also accepts ordinary Swedish organisation numbers with the format
59+    NNNNNNNNNN.
60+
61+    The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN
62+    for other organisations.
63+    """
64+
65+    default_error_messages = {
66+        'invalid': _('Enter a valid Swedish organisation number.'),
67+    }
68+
69+    def clean(self, value):
70+        value = super(SEOrganisationNumberField, self).clean(value)
71+       
72+        if value in EMPTY_VALUES:
73+            return u''
74+       
75+        match = SWEDISH_ID_NUMBER.match(value)
76+        if not match:
77+            raise forms.ValidationError(self.error_messages['invalid'])
78+
79+        gd = match.groupdict()
80+       
81+        # Compare the calculated value with the checksum
82+        if id_number_checksum(gd) != int(gd['checksum']):
83+            raise forms.ValidationError(self.error_messages['invalid'])
84+       
85+        # First: check if this is a real organisation_number
86+        if valid_organisation(gd):
87+            return format_organisation_number(gd)
88+
89+        # Is this a single properitor (enskild firma)?
90+        try:
91+            birth_day = validate_id_birthday(gd, False)
92+            return format_personal_id_number(birth_day, gd)
93+        except ValueError:
94+            raise forms.ValidationError(self.error_messages['invalid'])
95+
96+
97+class SEPersonalIdentityNumberField(forms.CharField):
98+    """
99+    A form field that validates input as a Swedish personal identity number
100+    (personnummer).
101+
102+    The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX,
103+    YYMMDDXXXX and YYMMDD+XXXX.
104+
105+    + indicates that the person is older than 100 years, which will be
106+    taken into consideration when the date is validated.
107+   
108+    The checksum will be calculated and checked. The birth date is checked
109+    to be a valid date.
110+
111+    By default, co-ordination numbers (samordningsnummer) will be accepted.
112+    To only allow real personal identity numbers, pass the keyword argument
113+    coordination_number=False to the constructor.
114+
115+    The cleaned value will always have the format YYYYMMDDXXXX.
116+    """
117+
118+    def __init__(self, *args, **kwargs):
119+        self.coordination_number = kwargs.pop('coordination_number', True)
120+        super(SEPersonalIdentityNumberField, self).__init__(*args, **kwargs)
121+
122+    default_error_messages = {
123+        'invalid': _('Enter a valid Swedish personal identity number.'),
124+        'coordination_number': _('Co-ordination numbers are not allowed.'),
125+    }
126+
127+    def clean(self, value):
128+        value = super(SEPersonalIdentityNumberField, self).clean(value)
129+
130+        if value in EMPTY_VALUES:
131+            return u''
132+
133+        match = SWEDISH_ID_NUMBER.match(value)
134+        if match is None:
135+            raise forms.ValidationError(self.error_messages['invalid'])
136+
137+        gd = match.groupdict()
138+
139+        # compare the calculated value with the checksum
140+        if id_number_checksum(gd) != int(gd['checksum']):
141+            raise forms.ValidationError(self.error_messages['invalid'])
142+
143+        # check for valid birthday
144+        try:
145+            birth_day = validate_id_birthday(gd)
146+        except ValueError:
147+            raise forms.ValidationError(self.error_messages['invalid'])
148+
149+        # make sure that co-ordination numbers do not pass if not allowed
150+        if not self.coordination_number and int(gd['day']) > 60:
151+            raise forms.ValidationError(self.error_messages['coordination_number'])
152
153+        return format_personal_id_number(birth_day, gd)
154+
155+
156+class SEPostalCodeField(forms.RegexField):
157+    """
158+    A form field that validates input as a Swedish postal code (postnummer).
159+    Valid codes consist of five digits (XXXXX). The number can optionally be
160+    formatted with a space after the third digit (XXX XX).
161+
162+    The cleaned value will never contain the space.
163+    """
164+
165+    default_error_messages = {
166+        'invalid': _('Enter a Swedish postal code in the format XXXXX.'),
167+    }
168+
169+    def __init__(self, *args, **kwargs):
170+        super(SEPostalCodeField, self).__init__(SE_POSTAL_CODE, *args, **kwargs)
171+
172+    def clean(self, value):
173+        return super(SEPostalCodeField, self).clean(value).replace(' ', '')
174+
175diff --git a/django/contrib/localflavor/se/se_counties.py b/django/contrib/localflavor/se/se_counties.py
176new file mode 100644
177index 0000000..0944064
178--- /dev/null
179+++ b/django/contrib/localflavor/se/se_counties.py
180@@ -0,0 +1,37 @@
181+# -*- coding: utf-8 -*-
182+"""
183+A alphabetical list of Swedish counties, sorted by county codes.
184+
185+
186+http://en.wikipedia.org/wiki/Counties_of_Sweden
187+
188+This exists in this standalone file so that it's only imported into memory
189+when explicitly needed.
190+
191+"""
192+
193+from django.utils.translation import ugettext_lazy as _
194+
195+COUNTY_CHOICES = (
196+    ('AB', _(u'Stockholm County')),
197+    ('AC', _(u'Västerbotten County')),
198+    ('BD', _(u'Norrbotten County')),
199+    ('C', _(u'Uppsala County')),
200+    ('D', _(u'Södermanland County')),
201+    ('E', _(u'Östergötland County')),
202+    ('F', _(u'Jönköping County')),
203+    ('G', _(u'Kronoberg County')),
204+    ('H', _(u'Kalmar County')),
205+    ('I', _(u'Gotland County')),
206+    ('K', _(u'Blekinge County')),
207+    ('M', _(u'Skåne County')),
208+    ('N', _(u'Halland County')),
209+    ('O', _(u'Västra Götaland County')),
210+    ('S', _(u'Värmland County')),
211+    ('T', _(u'Örebro County')),
212+    ('U', _(u'Västmanland County')),
213+    ('W', _(u'Dalarna County')),
214+    ('X', _(u'Gävleborg County')),
215+    ('Y', _(u'Västernorrland County')),
216+    ('Z', _(u'Jämtland County')),
217+)
218diff --git a/django/contrib/localflavor/se/utils.py b/django/contrib/localflavor/se/utils.py
219new file mode 100644
220index 0000000..36b22d6
221--- /dev/null
222+++ b/django/contrib/localflavor/se/utils.py
223@@ -0,0 +1,88 @@
224+import re
225+import datetime
226+
227+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)$')
228+SE_POSTAL_CODE = re.compile(r'^[1-9]\d{2} ?\d{2}$')
229+
230+def id_number_checksum(gd):
231+    """
232+    Calculates a Swedish id number checksum, using the
233+    "Luhn"-algoritm
234+    """
235+
236+    n = s = 0
237+    for c in (gd['year'] + gd['month'] + gd['day'] + gd['serial']):
238+        tmp = ((n % 2) and 1 or 2) * int(c)
239+
240+        if tmp > 9:
241+            tmp = sum([int(i) for i in str(tmp)])
242+
243+        s += tmp
244+        n += 1
245+
246+    if (s % 10) == 0:
247+        return 0
248+
249+    return (((s / 10) + 1) * 10) - s
250+
251+def validate_id_birthday(gd, fix_coordination_number_day=True):
252+    """
253+    Validates the birth_day and returns the datetime.date object for
254+    the birth_day.
255+
256+    If the date is an invalid birth day, a ValueError will be raised.
257+    """
258+   
259+    today = datetime.date.today()
260+   
261+    day = int(gd['day'])
262+    if fix_coordination_number_day and day > 60:
263+        day -= 60
264+
265+    if gd['century'] is None:
266+
267+        # The century was not specified, and need to be calculated from todays date
268+        current_year = today.year
269+        year = int(today.strftime('%Y')) - int(today.strftime('%y')) + int(gd['year'])
270+   
271+        if ('%s%s%02d' % (gd['year'], gd['month'], day)) > today.strftime('%y%m%d'):
272+            year -= 100
273+
274+        # If the person is older than 100 years
275+        if gd['sign'] == '+':
276+            year -= 100
277+    else:
278+        year = int(gd['century'] + gd['year'])
279+       
280+        # Make sure the year is valid
281+        # There are no swedish personal identity numbers where year < 1800
282+        if year < 1800:
283+            raise ValueError
284+
285+    # ValueError will be raise for invalid dates
286+    birth_day = datetime.date(year, int(gd['month']), day)
287+   
288+    # birth_day must not be in the future
289+    if birth_day > today:
290+        raise ValueError
291+   
292+    return birth_day
293+
294+def format_personal_id_number(birth_day, gd):
295+    # birth_day.strftime cannot be used, since it does not support dates < 1900
296+    return unicode(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum'])
297+
298+def format_organisation_number(gd):
299+    if gd['century'] is None:
300+        century = ''
301+    else:
302+        century = gd['century']
303+
304+    return unicode(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum'])
305+
306+def valid_organisation(gd):
307+    return gd['century'] in (None, 16) and \
308+        int(gd['month']) >= 20 and \
309+        gd['sign'] in (None, '-') and \
310+        gd['year'][0] in ('2', '5', '7', '8', '9') # group identifier
311+
312diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt
313index 6771efc..93c8f42 100644
314--- a/docs/ref/contrib/localflavor.txt
315+++ b/docs/ref/contrib/localflavor.txt
316@@ -60,6 +60,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
317     * Slovakia_
318     * `South Africa`_
319     * Spain_
320+    * Sweden_
321     * Switzerland_
322     * `United Kingdom`_
323     * `United States of America`_
324@@ -99,6 +100,7 @@ Here's an example of how to use them::
325 .. _Slovakia: `Slovakia (sk)`_
326 .. _South Africa: `South Africa (za)`_
327 .. _Spain: `Spain (es)`_
328+.. _Sweden: `Sweden (se)`_
329 .. _Switzerland: `Switzerland (ch)`_
330 .. _United Kingdom: `United Kingdom (uk)`_
331 .. _United States of America: `United States of America (us)`_
332@@ -573,6 +575,60 @@ Spain (``es``)
333 
334     A ``Select`` widget that uses a list of Spanish regions as its choices.
335 
336+Sweden (``se``)
337+===============
338+
339+.. class:: se.forms.SECountySelect
340+
341+    A Select form widget that uses a list of the Swedish counties (län)
342+    as its choices.
343+
344+    The cleaned value is the official county code -- see
345+    http://en.wikipedia.org/wiki/Counties_of_Sweden for a list.
346+
347+.. class:: se.forms.SEOrganisationNumber
348+
349+    A form field that validates input as a Swedish organisation number
350+    (organisationsnummer).
351+
352+    This fields accepts the same input as SEPersonalIdentityField (for
353+    sole proprietorships (enskild firma)). However, co-ordination numbers are
354+    not accepted.
355+
356+    It also accepts ordinary Swedish organisation numbers with the format
357+    NNNNNNNNNN.
358+
359+    The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN
360+    for other organisations.
361+
362+.. class:: se.forms.SEPersonalIdentityNumber
363+
364+    A form field that validates input as a Swedish personal identity number
365+    (personnummer).
366+
367+    The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX,
368+    YYMMDDXXXX and YYMMDD+XXXX.
369+
370+    \+ indicates that the person is older than 100 years, which will be
371+    taken into consideration when the date is validated.
372+   
373+    The checksum will be calculated and checked. The birth date is checked
374+    to be a valid date.
375+
376+    By default, co-ordination numbers (samordningsnummer) will be accepted.
377+    To only allow real personal identity numbers, pass the keyword argument
378+    coordination_number=False to the constructor.
379+
380+    The cleaned value will always have the format YYYYMMDDXXXX.
381+
382+.. class:: se.forms.SEPostalCodeField
383+
384+    A form field that validates input as a Swedish postal code (postnummer).
385+    Valid codes consist of five digits (XXXXX). The number can optionally be
386+    formatted with a space after the third digit (XXX XX).
387+
388+    The cleaned value will never contain the space.
389+
390 Switzerland (``ch``)
391 ====================
392 
393diff --git a/tests/regressiontests/forms/localflavor/se.py b/tests/regressiontests/forms/localflavor/se.py
394new file mode 100644
395index 0000000..e73b1e3
396--- /dev/null
397+++ b/tests/regressiontests/forms/localflavor/se.py
398@@ -0,0 +1,333 @@
399+# -*- coding: utf-8 -*-
400+# Tests for the contrib/localflavor/se form fields.
401+
402+tests = r"""
403+# Monkey-patch datetime.date
404+>>> import datetime
405+>>> class MockDate(datetime.date):
406+...     def today(cls):
407+...         return datetime.date(2008, 5, 14)
408+...     today = classmethod(today)
409+...
410+>>> olddate = datetime.date
411+>>> datetime.date = MockDate
412+>>> datetime.date.today()
413+MockDate(2008, 5, 14)
414+
415+
416+# SECountySelect #####################################################
417+>>> from django.contrib.localflavor.se.forms import SECountySelect
418+
419+>>> w = SECountySelect()
420+>>> w.render('swedish_county', 'E')
421+u'<select name="swedish_county">\n<option value="AB">Stockholm County</option>\n<option value="AC">V\xe4sterbotten County</option>\n<option value="BD">Norrbotten County</option>\n<option value="C">Uppsala County</option>\n<option value="D">S\xf6dermanland County</option>\n<option value="E" selected="selected">\xd6sterg\xf6tland County</option>\n<option value="F">J\xf6nk\xf6ping County</option>\n<option value="G">Kronoberg County</option>\n<option value="H">Kalmar County</option>\n<option value="I">Gotland County</option>\n<option value="K">Blekinge County</option>\n<option value="M">Sk\xe5ne County</option>\n<option value="N">Halland County</option>\n<option value="O">V\xe4stra G\xf6taland County</option>\n<option value="S">V\xe4rmland County</option>\n<option value="T">\xd6rebro County</option>\n<option value="U">V\xe4stmanland County</option>\n<option value="W">Dalarna County</option>\n<option value="X">G\xe4vleborg County</option>\n<option value="Y">V\xe4sternorrland County</option>\n<option value="Z">J\xe4mtland County</option>\n</select>'
422+
423+
424+# SEOrganisationNumberField #######################################
425+
426+>>> from django.contrib.localflavor.se.forms import SEOrganisationNumberField
427+
428+>>> f = SEOrganisationNumberField()
429+
430+# Ordinary personal identity numbers for sole proprietors
431+# The same rules as for SEPersonalIdentityField applies here
432+>>> f.clean('870512-1989')
433+u'198705121989'
434+>>> f.clean('19870512-1989')
435+u'198705121989'
436+>>> f.clean('870512-2128')
437+u'198705122128'
438+>>> f.clean('081015-6315')
439+u'190810156315'
440+>>> f.clean('081015+6315')
441+u'180810156315'
442+>>> f.clean('0810156315')
443+u'190810156315'
444+
445+>>> f.clean('081015 6315')
446+Traceback (most recent call last):
447+...
448+ValidationError: [u'Enter a valid Swedish organisation number.']
449+>>> f.clean('950231-4496')
450+Traceback (most recent call last):
451+...
452+ValidationError: [u'Enter a valid Swedish organisation number.']
453+>>> f.clean('6914104499')
454+Traceback (most recent call last):
455+...
456+ValidationError: [u'Enter a valid Swedish organisation number.']
457+>>> f.clean('950d314496')
458+Traceback (most recent call last):
459+...
460+ValidationError: [u'Enter a valid Swedish organisation number.']
461+>>> f.clean('invalid!!!')
462+Traceback (most recent call last):
463+...
464+ValidationError: [u'Enter a valid Swedish organisation number.']
465+>>> f.clean('870514-1111')
466+Traceback (most recent call last):
467+...
468+ValidationError: [u'Enter a valid Swedish organisation number.']
469+
470+
471+# Empty values
472+>>> f.clean('')
473+Traceback (most recent call last):
474+...
475+ValidationError: [u'This field is required.']
476+
477+>>> f.clean(None)
478+Traceback (most recent call last):
479+...
480+ValidationError: [u'This field is required.']
481+
482+# Co-ordination number checking
483+# Co-ordination numbers are not valid organisation numbers
484+>>> f.clean('870574-1315')
485+Traceback (most recent call last):
486+...
487+ValidationError: [u'Enter a valid Swedish organisation number.']
488+
489+>>> f.clean('870573-1311')
490+Traceback (most recent call last):
491+...
492+ValidationError: [u'Enter a valid Swedish organisation number.']
493+
494+# Test some different organisation numbers
495+>>> f.clean('556074-7569') # IKEA Linköping
496+u'5560747569'
497+
498+>>> f.clean('556074-3089') # Volvo Personvagnar
499+u'5560743089'
500+
501+>>> f.clean('822001-5476') # LJS (organisation)
502+u'8220015476'
503+
504+>>> f.clean('8220015476') # LJS (organisation)
505+u'8220015476'
506+
507+>>> f.clean('2120000449') # Katedralskolan Linköping (school)
508+u'2120000449'
509+
510+# Faux organisation number, which tests that the checksum can be 0
511+>>> f.clean('232518-5060')
512+u'2325185060'
513+
514+>>> f.clean('556074+3089') # Volvo Personvagnar, bad format
515+Traceback (most recent call last):
516+...
517+ValidationError: [u'Enter a valid Swedish organisation number.']
518+
519+
520+# Invalid checksum
521+>>> f.clean('2120000441')
522+Traceback (most recent call last):
523+...
524+ValidationError: [u'Enter a valid Swedish organisation number.']
525+
526+# Valid checksum but invalid organisation type
527+f.clean('1120000441')
528+Traceback (most recent call last):
529+...
530+ValidationError: [u'Enter a valid Swedish organisation number.']
531+
532+# Empty values with required=False
533+>>> f = SEOrganisationNumberField(required=False)
534+
535+>>> f.clean(None)
536+u''
537+
538+>>> f.clean('')
539+u''
540+
541+
542+# SEPersonalIdentityNumberField #######################################
543+
544+>>> from django.contrib.localflavor.se.forms import SEPersonalIdentityNumberField
545+
546+>>> f = SEPersonalIdentityNumberField()
547+
548+# Valid id numbers
549+>>> f.clean('870512-1989')
550+u'198705121989'
551+
552+>>> f.clean('870512-2128')
553+u'198705122128'
554+
555+>>> f.clean('19870512-1989')
556+u'198705121989'
557+
558+>>> f.clean('198705121989')
559+u'198705121989'
560+
561+>>> f.clean('081015-6315')
562+u'190810156315'
563+
564+>>> f.clean('0810156315')
565+u'190810156315'
566+
567+# This is a "special-case" in the checksum calculation,
568+# where the sum is divisible by 10 (the checksum digit == 0)
569+>>> f.clean('8705141060')
570+u'198705141060'
571+
572+# + means that the person is older than 100 years
573+>>> f.clean('081015+6315')
574+u'180810156315'
575+
576+# Bogus values
577+>>> f.clean('081015 6315')
578+Traceback (most recent call last):
579+...
580+ValidationError: [u'Enter a valid Swedish personal identity number.']
581+
582+>>> f.clean('950d314496')
583+Traceback (most recent call last):
584+...
585+ValidationError: [u'Enter a valid Swedish personal identity number.']
586+
587+>>> f.clean('invalid!!!')
588+Traceback (most recent call last):
589+...
590+ValidationError: [u'Enter a valid Swedish personal identity number.']
591+
592+
593+# Invalid dates
594+
595+# February 31st does not exist
596+>>> f.clean('950231-4496')
597+Traceback (most recent call last):
598+...
599+ValidationError: [u'Enter a valid Swedish personal identity number.']
600+
601+# Month 14 does not exist
602+>>> f.clean('6914104499')
603+Traceback (most recent call last):
604+...
605+ValidationError: [u'Enter a valid Swedish personal identity number.']
606+
607+# There are no Swedish personal id numbers where year < 1800
608+>>> f.clean('17430309-7135')
609+Traceback (most recent call last):
610+...
611+ValidationError: [u'Enter a valid Swedish personal identity number.']
612+
613+# Invalid checksum
614+>>> f.clean('870514-1111')
615+Traceback (most recent call last):
616+...
617+ValidationError: [u'Enter a valid Swedish personal identity number.']
618+
619+# Empty values
620+>>> f.clean('')
621+Traceback (most recent call last):
622+...
623+ValidationError: [u'This field is required.']
624+
625+>>> f.clean(None)
626+Traceback (most recent call last):
627+...
628+ValidationError: [u'This field is required.']
629+
630+# Co-ordination number checking
631+>>> f.clean('870574-1315')
632+u'198705741315'
633+
634+>>> f.clean('870574+1315')
635+u'188705741315'
636+
637+>>> f.clean('198705741315')
638+u'198705741315'
639+
640+# Co-ordination number with bad checksum
641+>>> f.clean('870573-1311')
642+Traceback (most recent call last):
643+...
644+ValidationError: [u'Enter a valid Swedish personal identity number.']
645+
646+
647+# Check valid co-ordination numbers, that should not be accepted
648+# because of coordination_number=False
649+>>> f = SEPersonalIdentityNumberField(coordination_number=False)
650+
651+>>> f.clean('870574-1315')
652+Traceback (most recent call last):
653+...
654+ValidationError: [u'Co-ordination numbers are not allowed.']
655+
656+>>> f.clean('870574+1315')
657+Traceback (most recent call last):
658+...
659+ValidationError: [u'Co-ordination numbers are not allowed.']
660+
661+>>> f.clean('8705741315')
662+Traceback (most recent call last):
663+...
664+ValidationError: [u'Co-ordination numbers are not allowed.']
665+
666+# Invalid co-ordination numbers should be treated as invalid, and not
667+# as co-ordination numbers
668+>>> f.clean('870573-1311')
669+Traceback (most recent call last):
670+...
671+ValidationError: [u'Enter a valid Swedish personal identity number.']
672+
673+# Empty values with required=False
674+>>> f = SEPersonalIdentityNumberField(required=False)
675+
676+>>> f.clean(None)
677+u''
678+
679+>>> f.clean('')
680+u''
681+
682+# SEPostalCodeField ###############################################
683+>>> from django.contrib.localflavor.se.forms import SEPostalCodeField
684+>>> f = SEPostalCodeField()
685+>>>
686+Postal codes can have spaces
687+>>> f.clean('589 37')
688+u'58937'
689+
690+... but the dont have to
691+>>> f.clean('58937')
692+u'58937'
693+>>> f.clean('abcasfassadf')
694+Traceback (most recent call last):
695+...
696+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
697+
698+# Only one space is allowed for separation
699+>>> f.clean('589  37')
700+Traceback (most recent call last):
701+...
702+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
703+
704+# The postal code must not start with 0
705+>>> f.clean('01234')
706+Traceback (most recent call last):
707+...
708+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
709+
710+# Empty values
711+>>> f.clean('')
712+Traceback (most recent call last):
713+...
714+ValidationError: [u'This field is required.']
715+
716+>>> f.clean(None)
717+Traceback (most recent call last):
718+...
719+ValidationError: [u'This field is required.']
720+
721+# Empty values, required=False
722+>>> f = SEPostalCodeField(required=False)
723+>>> f.clean('')
724+u''
725+>>> f.clean(None)
726+u''
727+
728+# Revert the monkey patching
729+>>> datetime.date = olddate
730+
731+"""
732diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
733index 6a8b017..f930003 100644
734--- a/tests/regressiontests/forms/tests.py
735+++ b/tests/regressiontests/forms/tests.py
736@@ -21,6 +21,7 @@ from localflavor.jp import tests as localflavor_jp_tests
737 from localflavor.nl import tests as localflavor_nl_tests
738 from localflavor.pl import tests as localflavor_pl_tests
739 from localflavor.ro import tests as localflavor_ro_tests
740+from localflavor.se import tests as localflavor_se_tests
741 from localflavor.sk import tests as localflavor_sk_tests
742 from localflavor.uk import tests as localflavor_uk_tests
743 from localflavor.us import tests as localflavor_us_tests
744@@ -54,6 +55,7 @@ __test__ = {
745     'localflavor_nl_tests': localflavor_nl_tests,
746     'localflavor_pl_tests': localflavor_pl_tests,
747     'localflavor_ro_tests': localflavor_ro_tests,
748+    'localflavor_se_tests': localflavor_se_tests,
749     'localflavor_sk_tests': localflavor_sk_tests,
750     'localflavor_uk_tests': localflavor_uk_tests,
751     'localflavor_us_tests': localflavor_us_tests,