/django/contrib/localflavor/se/forms.py
Python | 157 lines | 124 code | 8 blank | 25 comment | 4 complexity | 0d4e0bb4a0d91c0b81a77a031644ddae MD5 | raw file
1# -*- coding: utf-8 -*- 2""" 3Swedish specific Form helpers 4""" 5import re 6from django import forms 7from django.utils.translation import ugettext_lazy as _ 8from django.core.validators import EMPTY_VALUES 9from 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 16SWEDISH_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)$') 17SE_POSTAL_CODE = re.compile(r'^[1-9]\d{2} ?\d{2}$') 18 19class 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 33class 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 81class 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 140class 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(' ', '')