web/lib/django/contrib/localflavor/es/forms.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 # -*- coding: utf-8 -*-
       
     2 """
       
     3 Spanish-specific Form helpers
       
     4 """
       
     5 
       
     6 from django.core.validators import EMPTY_VALUES
       
     7 from django.forms import ValidationError
       
     8 from django.forms.fields import RegexField, Select
       
     9 from django.utils.translation import ugettext_lazy as _
       
    10 import re
       
    11 
       
    12 class ESPostalCodeField(RegexField):
       
    13     """
       
    14     A form field that validates its input as a spanish postal code.
       
    15 
       
    16     Spanish postal code is a five digits string, with two first digits
       
    17     between 01 and 52, assigned to provinces code.
       
    18     """
       
    19     default_error_messages = {
       
    20         'invalid': _('Enter a valid postal code in the range and format 01XXX - 52XXX.'),
       
    21     }
       
    22 
       
    23     def __init__(self, *args, **kwargs):
       
    24         super(ESPostalCodeField, self).__init__(
       
    25                 r'^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$',
       
    26                 max_length=None, min_length=None, *args, **kwargs)
       
    27 
       
    28 class ESPhoneNumberField(RegexField):
       
    29     """
       
    30     A form field that validates its input as a Spanish phone number.
       
    31     Information numbers are ommited.
       
    32 
       
    33     Spanish phone numbers are nine digit numbers, where first digit is 6 (for
       
    34     cell phones), 8 (for special phones), or 9 (for landlines and special
       
    35     phones)
       
    36 
       
    37     TODO: accept and strip characters like dot, hyphen... in phone number
       
    38     """
       
    39     default_error_messages = {
       
    40         'invalid': _('Enter a valid phone number in one of the formats 6XXXXXXXX, 8XXXXXXXX or 9XXXXXXXX.'),
       
    41     }
       
    42 
       
    43     def __init__(self, *args, **kwargs):
       
    44         super(ESPhoneNumberField, self).__init__(r'^(6|8|9)\d{8}$',
       
    45                 max_length=None, min_length=None, *args, **kwargs)
       
    46 
       
    47 class ESIdentityCardNumberField(RegexField):
       
    48     """
       
    49     Spanish NIF/NIE/CIF (Fiscal Identification Number) code.
       
    50 
       
    51     Validates three diferent formats:
       
    52 
       
    53         NIF (individuals): 12345678A
       
    54         CIF (companies): A12345678
       
    55         NIE (foreigners): X12345678A
       
    56 
       
    57     according to a couple of simple checksum algorithms.
       
    58 
       
    59     Value can include a space or hyphen separator between number and letters.
       
    60     Number length is not checked for NIF (or NIE), old values start with a 1,
       
    61     and future values can contain digits greater than 8. The CIF control digit
       
    62     can be a number or a letter depending on company type. Algorithm is not
       
    63     public, and different authors have different opinions on which ones allows
       
    64     letters, so both validations are assumed true for all types.
       
    65     """
       
    66     default_error_messages = {
       
    67         'invalid': _('Please enter a valid NIF, NIE, or CIF.'),
       
    68         'invalid_only_nif': _('Please enter a valid NIF or NIE.'),
       
    69         'invalid_nif': _('Invalid checksum for NIF.'),
       
    70         'invalid_nie': _('Invalid checksum for NIE.'),
       
    71         'invalid_cif': _('Invalid checksum for CIF.'),
       
    72     }
       
    73 
       
    74     def __init__(self, only_nif=False, *args, **kwargs):
       
    75         self.only_nif = only_nif
       
    76         self.nif_control = 'TRWAGMYFPDXBNJZSQVHLCKE'
       
    77         self.cif_control = 'JABCDEFGHI'
       
    78         self.cif_types = 'ABCDEFGHKLMNPQS'
       
    79         self.nie_types = 'XT'
       
    80         id_card_re = re.compile(r'^([%s]?)[ -]?(\d+)[ -]?([%s]?)$' % (self.cif_types + self.nie_types, self.nif_control + self.cif_control), re.IGNORECASE)
       
    81         super(ESIdentityCardNumberField, self).__init__(id_card_re, max_length=None, min_length=None,
       
    82                 error_message=self.default_error_messages['invalid%s' % (self.only_nif and '_only_nif' or '')],
       
    83                 *args, **kwargs)
       
    84 
       
    85     def clean(self, value):
       
    86         super(ESIdentityCardNumberField, self).clean(value)
       
    87         if value in EMPTY_VALUES:
       
    88             return u''
       
    89         nif_get_checksum = lambda d: self.nif_control[int(d)%23]
       
    90 
       
    91         value = value.upper().replace(' ', '').replace('-', '')
       
    92         m = re.match(r'^([%s]?)[ -]?(\d+)[ -]?([%s]?)$' % (self.cif_types + self.nie_types, self.nif_control + self.cif_control), value)
       
    93         letter1, number, letter2 = m.groups()
       
    94 
       
    95         if not letter1 and letter2:
       
    96             # NIF
       
    97             if letter2 == nif_get_checksum(number):
       
    98                 return value
       
    99             else:
       
   100                 raise ValidationError(self.error_messages['invalid_nif'])
       
   101         elif letter1 in self.nie_types and letter2:
       
   102             # NIE
       
   103             if letter2 == nif_get_checksum(number):
       
   104                 return value
       
   105             else:
       
   106                 raise ValidationError(self.error_messages['invalid_nie'])
       
   107         elif not self.only_nif and letter1 in self.cif_types and len(number) in [7, 8]:
       
   108             # CIF
       
   109             if not letter2:
       
   110                 number, letter2 = number[:-1], int(number[-1])
       
   111             checksum = cif_get_checksum(number)
       
   112             if letter2 in (checksum, self.cif_control[checksum]):
       
   113                 return value
       
   114             else:
       
   115                 raise ValidationError(self.error_messages['invalid_cif'])
       
   116         else:
       
   117             raise ValidationError(self.error_messages['invalid'])
       
   118 
       
   119 class ESCCCField(RegexField):
       
   120     """
       
   121     A form field that validates its input as a Spanish bank account or CCC
       
   122     (Codigo Cuenta Cliente).
       
   123 
       
   124         Spanish CCC is in format EEEE-OOOO-CC-AAAAAAAAAA where:
       
   125 
       
   126             E = entity
       
   127             O = office
       
   128             C = checksum
       
   129             A = account
       
   130 
       
   131         It's also valid to use a space as delimiter, or to use no delimiter.
       
   132 
       
   133         First checksum digit validates entity and office, and last one
       
   134         validates account. Validation is done multiplying every digit of 10
       
   135         digit value (with leading 0 if necessary) by number in its position in
       
   136         string 1, 2, 4, 8, 5, 10, 9, 7, 3, 6. Sum resulting numbers and extract
       
   137         it from 11.  Result is checksum except when 10 then is 1, or when 11
       
   138         then is 0.
       
   139 
       
   140         TODO: allow IBAN validation too
       
   141     """
       
   142     default_error_messages = {
       
   143         'invalid': _('Please enter a valid bank account number in format XXXX-XXXX-XX-XXXXXXXXXX.'),
       
   144         'checksum': _('Invalid checksum for bank account number.'),
       
   145     }
       
   146 
       
   147     def __init__(self, *args, **kwargs):
       
   148         super(ESCCCField, self).__init__(r'^\d{4}[ -]?\d{4}[ -]?\d{2}[ -]?\d{10}$',
       
   149             max_length=None, min_length=None, *args, **kwargs)
       
   150 
       
   151     def clean(self, value):
       
   152         super(ESCCCField, self).clean(value)
       
   153         if value in EMPTY_VALUES:
       
   154             return u''
       
   155         control_str = [1, 2, 4, 8, 5, 10, 9, 7, 3, 6]
       
   156         m = re.match(r'^(\d{4})[ -]?(\d{4})[ -]?(\d{2})[ -]?(\d{10})$', value)
       
   157         entity, office, checksum, account = m.groups()
       
   158         get_checksum = lambda d: str(11 - sum([int(digit) * int(control) for digit, control in zip(d, control_str)]) % 11).replace('10', '1').replace('11', '0')
       
   159         if get_checksum('00' + entity + office) + get_checksum(account) == checksum:
       
   160             return value
       
   161         else:
       
   162             raise ValidationError(self.error_messages['checksum'])
       
   163 
       
   164 class ESRegionSelect(Select):
       
   165     """
       
   166     A Select widget that uses a list of spanish regions as its choices.
       
   167     """
       
   168     def __init__(self, attrs=None):
       
   169         from es_regions import REGION_CHOICES
       
   170         super(ESRegionSelect, self).__init__(attrs, choices=REGION_CHOICES)
       
   171 
       
   172 class ESProvinceSelect(Select):
       
   173     """
       
   174     A Select widget that uses a list of spanish provinces as its choices.
       
   175     """
       
   176     def __init__(self, attrs=None):
       
   177         from es_provinces import PROVINCE_CHOICES
       
   178         super(ESProvinceSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
       
   179 
       
   180 
       
   181 def cif_get_checksum(number):
       
   182     s1 = sum([int(digit) for pos, digit in enumerate(number) if int(pos) % 2])
       
   183     s2 = sum([sum([int(unit) for unit in str(int(digit) * 2)]) for pos, digit in enumerate(number) if not int(pos) % 2])
       
   184     return (10 - ((s1 + s2) % 10)) % 10
       
   185