Code

Ticket #9289: django_localflavor_se_r2.diff

File django_localflavor_se_r2.diff, 23.2 KB (added by peritus, 6 years ago)

Updated patch

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..cd3eb0f
221--- /dev/null
222+++ b/django/contrib/localflavor/se/utils.py
223@@ -0,0 +1,84 @@
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+        if tmp > 9:
240+            tmp = sum([int(i) for i in str(tmp)])
241+
242+        s += tmp
243+        n += 1
244
245+    return (((s / 10) + 1) * 10) - s
246+
247+def validate_id_birthday(gd, fix_coordination_number_day=True):
248+    """
249+    Validates the birth_day and returns the datetime.date object for
250+    the birth_day.
251+
252+    If the date is an invalid birth day, a ValueError will be raised.
253+    """
254+   
255+    today = datetime.date.today()
256+   
257+    day = int(gd['day'])
258+    if fix_coordination_number_day and day > 60:
259+        day -= 60
260+
261+    if gd['century'] is None:
262+
263+        # The century was not specified, and need to be calculated from todays date
264+        current_year = today.year
265+        year = int(today.strftime('%Y')) - int(today.strftime('%y')) + int(gd['year'])
266+   
267+        if ('%s%s%02d' % (gd['year'], gd['month'], day)) > today.strftime('%y%m%d'):
268+            year -= 100
269+
270+        # If the person is older than 100 years
271+        if gd['sign'] == '+':
272+            year -= 100
273+    else:
274+        year = int(gd['century'] + gd['year'])
275+       
276+        # Make sure the year is valid
277+        # There are no swedish personal identity numbers where year < 1800
278+        if year < 1800:
279+            raise ValueError
280+
281+    # ValueError will be raise for invalid dates
282+    birth_day = datetime.date(year, int(gd['month']), day)
283+   
284+    # birth_day must not be in the future
285+    if birth_day > today:
286+        raise ValueError
287+   
288+    return birth_day
289+
290+def format_personal_id_number(birth_day, gd):
291+    # birth_day.strftime cannot be used, since it does not support dates < 1900
292+    return unicode(str(birth_day.year) + gd['month'] + gd['day'] + gd['serial'] + gd['checksum'])
293+
294+def format_organisation_number(gd):
295+    if gd['century'] is None:
296+        century = ''
297+    else:
298+        century = gd['century']
299+
300+    return unicode(century + gd['year'] + gd['month'] + gd['day'] + gd['serial'] + gd['checksum'])
301+
302+def valid_organisation(gd):
303+    return gd['century'] in (None, 16) and \
304+        int(gd['month']) >= 20 and \
305+        gd['sign'] in (None, '-') and \
306+        gd['year'][0] in ('2', '5', '7', '8', '9') # group identifier
307+
308diff --git a/docs/ref/contrib/localflavor.txt b/docs/ref/contrib/localflavor.txt
309index 6771efc..93c8f42 100644
310--- a/docs/ref/contrib/localflavor.txt
311+++ b/docs/ref/contrib/localflavor.txt
312@@ -60,6 +60,7 @@ Countries currently supported by :mod:`~django.contrib.localflavor` are:
313     * Slovakia_
314     * `South Africa`_
315     * Spain_
316+    * Sweden_
317     * Switzerland_
318     * `United Kingdom`_
319     * `United States of America`_
320@@ -99,6 +100,7 @@ Here's an example of how to use them::
321 .. _Slovakia: `Slovakia (sk)`_
322 .. _South Africa: `South Africa (za)`_
323 .. _Spain: `Spain (es)`_
324+.. _Sweden: `Sweden (se)`_
325 .. _Switzerland: `Switzerland (ch)`_
326 .. _United Kingdom: `United Kingdom (uk)`_
327 .. _United States of America: `United States of America (us)`_
328@@ -573,6 +575,60 @@ Spain (``es``)
329 
330     A ``Select`` widget that uses a list of Spanish regions as its choices.
331 
332+Sweden (``se``)
333+===============
334+
335+.. class:: se.forms.SECountySelect
336+
337+    A Select form widget that uses a list of the Swedish counties (län)
338+    as its choices.
339+
340+    The cleaned value is the official county code -- see
341+    http://en.wikipedia.org/wiki/Counties_of_Sweden for a list.
342+
343+.. class:: se.forms.SEOrganisationNumber
344+
345+    A form field that validates input as a Swedish organisation number
346+    (organisationsnummer).
347+
348+    This fields accepts the same input as SEPersonalIdentityField (for
349+    sole proprietorships (enskild firma)). However, co-ordination numbers are
350+    not accepted.
351+
352+    It also accepts ordinary Swedish organisation numbers with the format
353+    NNNNNNNNNN.
354+
355+    The return value will be YYYYMMDDXXXX for sole proprietors, and NNNNNNNNNN
356+    for other organisations.
357+
358+.. class:: se.forms.SEPersonalIdentityNumber
359+
360+    A form field that validates input as a Swedish personal identity number
361+    (personnummer).
362+
363+    The correct formats are YYYYMMDD-XXXX, YYYYMMDDXXXX, YYMMDD-XXXX,
364+    YYMMDDXXXX and YYMMDD+XXXX.
365+
366+    \+ indicates that the person is older than 100 years, which will be
367+    taken into consideration when the date is validated.
368+   
369+    The checksum will be calculated and checked. The birth date is checked
370+    to be a valid date.
371+
372+    By default, co-ordination numbers (samordningsnummer) will be accepted.
373+    To only allow real personal identity numbers, pass the keyword argument
374+    coordination_number=False to the constructor.
375+
376+    The cleaned value will always have the format YYYYMMDDXXXX.
377+
378+.. class:: se.forms.SEPostalCodeField
379+
380+    A form field that validates input as a Swedish postal code (postnummer).
381+    Valid codes consist of five digits (XXXXX). The number can optionally be
382+    formatted with a space after the third digit (XXX XX).
383+
384+    The cleaned value will never contain the space.
385+
386 Switzerland (``ch``)
387 ====================
388 
389diff --git a/tests/regressiontests/forms/localflavor/se.py b/tests/regressiontests/forms/localflavor/se.py
390new file mode 100644
391index 0000000..ad819e7
392--- /dev/null
393+++ b/tests/regressiontests/forms/localflavor/se.py
394@@ -0,0 +1,323 @@
395+# -*- coding: utf-8 -*-
396+# Tests for the contrib/localflavor/se form fields.
397+
398+tests = r"""
399+# Monkey-patch datetime.date
400+>>> import datetime
401+>>> class MockDate(datetime.date):
402+...     def today(cls):
403+...         return datetime.date(2008, 5, 14)
404+...     today = classmethod(today)
405+...
406+>>> olddate = datetime.date
407+>>> datetime.date = MockDate
408+>>> datetime.date.today()
409+MockDate(2008, 5, 14)
410+
411+
412+# SECountySelect #####################################################
413+>>> from django.contrib.localflavor.se.forms import SECountySelect
414+
415+>>> w = SECountySelect()
416+>>> w.render('swedish_county', 'E')
417+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>'
418+
419+
420+# SEOrganisationNumberField #######################################
421+
422+>>> from django.contrib.localflavor.se.forms import SEOrganisationNumberField
423+
424+>>> f = SEOrganisationNumberField()
425+
426+# Ordinary personal identity numbers for sole proprietors
427+# The same rules as for SEPersonalIdentityField applies here
428+>>> f.clean('870512-1989')
429+u'198705121989'
430+>>> f.clean('19870512-1989')
431+u'198705121989'
432+>>> f.clean('870512-2128')
433+u'198705122128'
434+>>> f.clean('081015-6315')
435+u'190810156315'
436+>>> f.clean('081015+6315')
437+u'180810156315'
438+>>> f.clean('0810156315')
439+u'190810156315'
440+>>> f.clean('081015 6315')
441+Traceback (most recent call last):
442+...
443+ValidationError: [u'Enter a valid Swedish organisation number.']
444+>>> f.clean('950231-4496')
445+Traceback (most recent call last):
446+...
447+ValidationError: [u'Enter a valid Swedish organisation number.']
448+>>> f.clean('6914104499')
449+Traceback (most recent call last):
450+...
451+ValidationError: [u'Enter a valid Swedish organisation number.']
452+>>> f.clean('950d314496')
453+Traceback (most recent call last):
454+...
455+ValidationError: [u'Enter a valid Swedish organisation number.']
456+>>> f.clean('invalid!!!')
457+Traceback (most recent call last):
458+...
459+ValidationError: [u'Enter a valid Swedish organisation number.']
460+>>> f.clean('870514-1111')
461+Traceback (most recent call last):
462+...
463+ValidationError: [u'Enter a valid Swedish organisation number.']
464+
465+
466+# Empty values
467+>>> f.clean('')
468+Traceback (most recent call last):
469+...
470+ValidationError: [u'This field is required.']
471+
472+>>> f.clean(None)
473+Traceback (most recent call last):
474+...
475+ValidationError: [u'This field is required.']
476+
477+# Co-ordination number checking
478+# Co-ordination numbers are not valid organisation numbers
479+>>> f.clean('870574-1315')
480+Traceback (most recent call last):
481+...
482+ValidationError: [u'Enter a valid Swedish organisation number.']
483+
484+>>> f.clean('870573-1311')
485+Traceback (most recent call last):
486+...
487+ValidationError: [u'Enter a valid Swedish organisation number.']
488+
489+# Test some different organisation numbers
490+>>> f.clean('556074-7569') # IKEA Linköping
491+u'5560747569'
492+
493+>>> f.clean('556074-3089') # Volvo Personvagnar
494+u'5560743089'
495+
496+>>> f.clean('822001-5476') # LJS (organisation)
497+u'8220015476'
498+
499+>>> f.clean('8220015476') # LJS (organisation)
500+u'8220015476'
501+
502+>>> f.clean('2120000449') # Katedralskolan Linköping (school)
503+u'2120000449'
504+
505+>>> f.clean('556074+3089') # Volvo Personvagnar, bad format
506+Traceback (most recent call last):
507+...
508+ValidationError: [u'Enter a valid Swedish organisation number.']
509+
510+
511+# Invalid checksum
512+>>> f.clean('2120000441')
513+Traceback (most recent call last):
514+...
515+ValidationError: [u'Enter a valid Swedish organisation number.']
516+
517+# Valid checksum but invalid organisation type
518+f.clean('1120000441')
519+Traceback (most recent call last):
520+...
521+ValidationError: [u'Enter a valid Swedish organisation number.']
522+
523+# Empty values with required=False
524+>>> f = SEOrganisationNumberField(required=False)
525+
526+>>> f.clean(None)
527+u''
528+
529+>>> f.clean('')
530+u''
531+
532+
533+# SEPersonalIdentityNumberField #######################################
534+
535+>>> from django.contrib.localflavor.se.forms import SEPersonalIdentityNumberField
536+
537+>>> f = SEPersonalIdentityNumberField()
538+
539+# Valid id numbers
540+>>> f.clean('870512-1989')
541+u'198705121989'
542+
543+>>> f.clean('870512-2128')
544+u'198705122128'
545+
546+>>> f.clean('19870512-1989')
547+u'198705121989'
548+
549+>>> f.clean('198705121989')
550+u'198705121989'
551+
552+>>> f.clean('081015-6315')
553+u'190810156315'
554+
555+>>> f.clean('0810156315')
556+u'190810156315'
557+
558+# + means that the person is older than 100 years
559+>>> f.clean('081015+6315')
560+u'180810156315'
561+
562+# Bogus values
563+>>> f.clean('081015 6315')
564+Traceback (most recent call last):
565+...
566+ValidationError: [u'Enter a valid Swedish personal identity number.']
567+
568+>>> f.clean('950d314496')
569+Traceback (most recent call last):
570+...
571+ValidationError: [u'Enter a valid Swedish personal identity number.']
572+
573+>>> f.clean('invalid!!!')
574+Traceback (most recent call last):
575+...
576+ValidationError: [u'Enter a valid Swedish personal identity number.']
577+
578+
579+# Invalid dates
580+
581+# February 31st does not exist
582+>>> f.clean('950231-4496')
583+Traceback (most recent call last):
584+...
585+ValidationError: [u'Enter a valid Swedish personal identity number.']
586+
587+# Month 14 does not exist
588+>>> f.clean('6914104499')
589+Traceback (most recent call last):
590+...
591+ValidationError: [u'Enter a valid Swedish personal identity number.']
592+
593+# There are no Swedish personal id numbers where year < 1800
594+>>> f.clean('17430309-7135')
595+Traceback (most recent call last):
596+...
597+ValidationError: [u'Enter a valid Swedish personal identity number.']
598+
599+# Invalid checksum
600+>>> f.clean('870514-1111')
601+Traceback (most recent call last):
602+...
603+ValidationError: [u'Enter a valid Swedish personal identity number.']
604+
605+# Empty values
606+>>> f.clean('')
607+Traceback (most recent call last):
608+...
609+ValidationError: [u'This field is required.']
610+
611+>>> f.clean(None)
612+Traceback (most recent call last):
613+...
614+ValidationError: [u'This field is required.']
615+
616+# Co-ordination number checking
617+>>> f.clean('870574-1315')
618+u'198705741315'
619+
620+>>> f.clean('870574+1315')
621+u'188705741315'
622+
623+>>> f.clean('198705741315')
624+u'198705741315'
625+
626+# Co-ordination number with bad checksum
627+>>> f.clean('870573-1311')
628+Traceback (most recent call last):
629+...
630+ValidationError: [u'Enter a valid Swedish personal identity number.']
631+
632+
633+# Check valid co-ordination numbers, that should not be accepted
634+# because of coordination_number=False
635+>>> f = SEPersonalIdentityNumberField(coordination_number=False)
636+
637+>>> f.clean('870574-1315')
638+Traceback (most recent call last):
639+...
640+ValidationError: [u'Co-ordination numbers are not allowed.']
641+
642+>>> f.clean('870574+1315')
643+Traceback (most recent call last):
644+...
645+ValidationError: [u'Co-ordination numbers are not allowed.']
646+
647+>>> f.clean('8705741315')
648+Traceback (most recent call last):
649+...
650+ValidationError: [u'Co-ordination numbers are not allowed.']
651+
652+# Invalid co-ordination numbers should be treated as invalid, and not
653+# as co-ordination numbers
654+>>> f.clean('870573-1311')
655+Traceback (most recent call last):
656+...
657+ValidationError: [u'Enter a valid Swedish personal identity number.']
658+
659+# Empty values with required=False
660+>>> f = SEPersonalIdentityNumberField(required=False)
661+
662+>>> f.clean(None)
663+u''
664+
665+>>> f.clean('')
666+u''
667+
668+# SEPostalCodeField ###############################################
669+>>> from django.contrib.localflavor.se.forms import SEPostalCodeField
670+>>> f = SEPostalCodeField()
671+>>>
672+Postal codes can have spaces
673+>>> f.clean('589 37')
674+u'58937'
675+
676+... but the dont have to
677+>>> f.clean('58937')
678+u'58937'
679+>>> f.clean('abcasfassadf')
680+Traceback (most recent call last):
681+...
682+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
683+
684+# Only one space is allowed for separation
685+>>> f.clean('589  37')
686+Traceback (most recent call last):
687+...
688+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
689+
690+# The postal code must not start with 0
691+>>> f.clean('01234')
692+Traceback (most recent call last):
693+...
694+ValidationError: [u'Enter a Swedish postal code in the format XXXXX.']
695+
696+# Empty values
697+>>> f.clean('')
698+Traceback (most recent call last):
699+...
700+ValidationError: [u'This field is required.']
701+
702+>>> f.clean(None)
703+Traceback (most recent call last):
704+...
705+ValidationError: [u'This field is required.']
706+
707+# Empty values, required=False
708+>>> f = SEPostalCodeField(required=False)
709+>>> f.clean('')
710+u''
711+>>> f.clean(None)
712+u''
713+
714+# Revert the monkey patching
715+>>> datetime.date = olddate
716+
717+"""
718diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
719index 6a8b017..f930003 100644
720--- a/tests/regressiontests/forms/tests.py
721+++ b/tests/regressiontests/forms/tests.py
722@@ -21,6 +21,7 @@ from localflavor.jp import tests as localflavor_jp_tests
723 from localflavor.nl import tests as localflavor_nl_tests
724 from localflavor.pl import tests as localflavor_pl_tests
725 from localflavor.ro import tests as localflavor_ro_tests
726+from localflavor.se import tests as localflavor_se_tests
727 from localflavor.sk import tests as localflavor_sk_tests
728 from localflavor.uk import tests as localflavor_uk_tests
729 from localflavor.us import tests as localflavor_us_tests
730@@ -54,6 +55,7 @@ __test__ = {
731     'localflavor_nl_tests': localflavor_nl_tests,
732     'localflavor_pl_tests': localflavor_pl_tests,
733     'localflavor_ro_tests': localflavor_ro_tests,
734+    'localflavor_se_tests': localflavor_se_tests,
735     'localflavor_sk_tests': localflavor_sk_tests,
736     'localflavor_uk_tests': localflavor_uk_tests,
737     'localflavor_us_tests': localflavor_us_tests,