Ticket #4036: 4036.diff

File 4036.diff, 15.5 KB (added by oggie_rob, 7 years ago)

New patch with simplified Identity number field & error handling

  • django/contrib/localflavor/es/es_province.py

     
     1# -*- coding: utf-8 -*-
     2"""
     3An alphabetical list of spanish provinces, including two autonomous
     4cities, for use as `choices` in a formfield.
     5
     6This exists in this standalone file so that it's only imported into
     7memory when explicitly needed.
     8"""
     9
     10PROVINCE_CHOICES = (
     11    ('VI', u'Álava'),
     12    ('AB', u'Albacete'),
     13    ('A', u'Alicante'),
     14    ('AL', u'Almería'),
     15    ('O', u'Asturias'),
     16    ('AV', u'Ávila'),
     17    ('BA', u'Badajoz'),
     18    ('PM', u'Islas Baleares'),
     19    ('B', u'Barcelona'),
     20    ('Bu', u'Burgos'),
     21    ('CC', u'Cáceres'),
     22    ('CA', u'Cádiz'),
     23    ('S', u'Cantabria'),
     24    ('CS', u'Castellón'),
     25    ('CE', u'Ceuta'),
     26    ('CR', u'Ciudad Real'),
     27    ('CO', u'Córdoba'),
     28    ('Cu', u'Cuenca'),
     29    ('GI', u'Gerona'),
     30    ('GR', u'Granada'),
     31    ('Gu', u'Guadalajara'),
     32    ('SS', u'Guipúzcoa'),
     33    ('H', u'Huelva'),
     34    ('Hu', u'Huesca'),
     35    ('J', u'Jaén'),
     36    ('C', u'La Coruña'),           
     37    ('LO', u'La Rioja'),
     38    ('GC', u'Las Palmas'),
     39    ('LE', u'León'),
     40    ('L', u'Lérida'),
     41    ('Lu', u'Lugo'),
     42    ('M', u'Madrid'),
     43    ('MA', u'Málaga'),
     44    ('ML', u'Melilla'),
     45    ('Mu', u'Murcia'),
     46    ('NA', u'Navarra'),
     47    ('OR', u'Orense'),
     48    ('P', u'Palencia'),
     49    ('PO', u'Pontevedra'),
     50    ('SA', u'Salamanca'),
     51    ('TF', u'Santa Cruz de Tenerife'),
     52    ('SG', u'Segovia'),
     53    ('SE', u'Sevilla'),
     54    ('SO', u'Soria'),
     55    ('T', u'Tarragona'),
     56    ('TE', u'Teruel'),
     57    ('TO', u'Toledo'),
     58    ('V', u'Valencia'),
     59    ('VA', u'Valladolid'),
     60    ('BI', u'Vizcaya'),
     61    ('ZA', u'Zamora'),
     62    ('Z', u'Zaragoza'),
     63)
  • django/contrib/localflavor/es/forms.py

     
     1# -*- coding: utf-8 -*-
     2"""
     3Spanish-specific Form helpers
     4"""
     5
     6from django.newforms import ValidationError
     7from django.newforms.fields import RegexField, Select, EMPTY_VALUES
     8from django.utils.translation import ugettext as _
     9import re
     10
     11class ESPostalCodeField(RegexField):
     12    """
     13    A form field that validates its input is a spanish postal code.
     14   
     15    Spanish postal code is a five digits string, with two first digits
     16    between 01 and 52, assigned to provinces code.
     17    """
     18    def __init__(self, *args, **kwargs):
     19        super(ESPostalCodeField, self).__init__(r'^(0[1-9]|[1-4][0-9]|5[0-2])\d{3}$',
     20            max_length=None, min_length=None,
     21            error_message=_('Enter a valid postal code in the range and format 01XXX - 52XXX.'),
     22            *args, **kwargs)
     23
     24
     25class ESSubdivisionSelect(Select):
     26    """
     27    A Select widget that uses a list of spanish provinces and autonomous
     28    cities as its choices.
     29    """
     30    def __init__(self, attrs=None):
     31        from es_province import PROVINCE_CHOICES
     32        super(ESSubdivisionSelect, self).__init__(attrs, choices=PROVINCE_CHOICES)
     33
     34
     35class ESIdentityCardNumberField(RegexField):
     36    """
     37    A form field that validates its input as an Spanish NIF, NIE or CIF identity values
     38    Apart from validating the format, this also checks the checksum-like control character
     39   
     40    The NIF (Nœmero de identificaci—n fiscal):
     41        Assigned to Spanish citizens
     42         - 8 digits representing individual (known as the DNI)
     43         - 1 control character using modulus of DNI value against an alphabetical index
     44        e.g. '78699688J'
     45   
     46    The NIE (Nœmero de Identificaci—n de Extranjeros):
     47        Assigned to foreigners
     48         - "X" or "T"
     49         - 7 or 8 digits representing individual (if 7 digits, 0 must be prepended)
     50         - 1 control character using modulus of DNI value against an alphabetical index
     51        e.g. 'X3287690R'
     52       
     53    The CIF (C—digo de identificaci—n fiscal):
     54        Assigned to corporations
     55         - 1 character representing "type"
     56         - 2 digits representing the province (01 - 52)
     57         - 5 digits representing business registry number from the province
     58         - 1 control digit or character using calculation of 7 digits & type
     59        e.g. 'B38790911'
     60   
     61    It acepts two optional arguments:
     62    nif (default True)
     63        accept NIF or NIE values
     64    cif (default True)
     65        accept CIF values
     66    """   
     67    def __init__(self, nif=True, cif=True, *args, **kwargs):
     68        super(ESIdentityCardNumberField, self).__init__(r'^(\d{8}((-|\s|)[A-Za-z]{1}|)|([Xx](-|\s|)(\d{7}|\d{8})((-|\s|)[A-Za-z]{1}|))|([A-Sa-s]{1}(-|\s|)\d{7}(-|\s|)[A-Za-z0-9]{1}))$',
     69            max_length=None, min_length=None,
     70            error_message=_('Please enter a valid NIF, NIE or CIF.'),
     71            *args, **kwargs)
     72        assert nif or cif, _('"nif" or "cif" must be True')
     73        self.nif = nif
     74        self.cif = cif
     75
     76    def clean(self, value):
     77        super(ESIdentityCardNumberField, self).clean(value)       
     78        if value in EMPTY_VALUES:
     79            return u''
     80        value = unicode(value).replace('-','').replace(' ','').upper()
     81        ni_control_chars = 'TRWAGMYFPDXBNJZSQVHLCKE'
     82        ni_control_suffix = lambda x: ni_control_chars[long(x) % 23]
     83        cif_type = 'ABCDEFGHKLMNPQS'
     84       
     85        try:
     86            if re.match(r'^\d{8}[%s]$' % ni_control_chars, value):
     87                if not self.nif or value[-1] != ni_control_suffix(value[:8]):
     88                    raise # invalid NIF
     89            elif re.match(r'^(X|T)\d{7,8}[%s]$' % ni_control_chars, value):
     90                test_val = value[1:]
     91                if len(value) == 9: # 7 digit number only
     92                    test_val = '0' + test_val
     93                if not self.nif or test_val[-1] != ni_control_suffix(test_val[:8]):
     94                    raise # invalid NIE
     95            elif re.match(r'^[%s](0[1-9]|[1-4][0-9]|5[0-2])\d{5}[A-Z0-9]$' % cif_type, value):
     96                digits = value[1:8]
     97                # a = sum of odd digits
     98                # b = sum of digits of 2 * even digits
     99                # c = a + b
     100                # control = 10 - last digit of c
     101                a = sum([int(v) for k,v in enumerate(digits) if k%2]) # sum of odd values
     102                b_list = map(lambda x: 2*int(x), [v for k,v in enumerate(digits) if k%2 == 0])
     103                b = sum([sum(map(int, list(str(x)))) for x in b_list])
     104                c = a + b
     105                control = 10 - int(str(c)[-1])
     106                control_letters = 'JABCDEFGHI'
     107                # the first char determines whether to use the control value or the corresponding control_letter
     108                if (not self.cif) or \
     109                   (value[0] in 'KPQS' and value[-1] != control_letters[control]) or \
     110                   (value[0] in 'ABEH' and value[-1] != str(control)) or \
     111                   (value[0] not in 'ABEHKPQS' and value[-1] != str(control) and value[-1] != control_letters[control]):
     112                   raise # invalid CIF
     113            else:
     114                raise # not valid format
     115            return value
     116        except Exception, e:
     117            # produce appropriate error
     118            if not self.nif:
     119                raise ValidationError, _('Please enter a valid CIF.')
     120            elif not self.cif:
     121                raise ValidationError, _('Please enter a valid NIF or NIE.')
     122            else:
     123                raise ValidationError, _('Please enter a valid NIF, NIE or CIF.')
  • tests/regressiontests/forms/localflavor.py

     
    18181818u''
    18191819>>> f.clean(u'')
    18201820u''
     1821
     1822# ESPostalCodeField #############################################################
     1823
     1824ESPostalCodeField validates that the data is a valid ES postal code.
     1825>>> from django.contrib.localflavor.es.forms import *
     1826>>> f = ESPostalCodeField()
     1827>>> f.clean('01000')
     1828u'01000'
     1829>>> f.clean('52999')
     1830u'52999'
     1831>>> f.clean('00999')
     1832Traceback (most recent call last):
     1833...
     1834ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1835>>> f.clean('53000')
     1836Traceback (most recent call last):
     1837...
     1838ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1839>>> f.clean('0A200')
     1840Traceback (most recent call last):
     1841...
     1842ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1843>>> f.clean('380001')
     1844Traceback (most recent call last):
     1845...
     1846ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1847>>> f.clean(None)
     1848Traceback (most recent call last):
     1849...
     1850ValidationError: [u'This field is required.']
     1851>>> f.clean('')
     1852Traceback (most recent call last):
     1853...
     1854ValidationError: [u'This field is required.']
     1855
     1856>>> f = ESPostalCodeField(required=False)
     1857>>> f.clean('01000')
     1858u'01000'
     1859>>> f.clean('52999')
     1860u'52999'
     1861>>> f.clean('00999')
     1862Traceback (most recent call last):
     1863...
     1864ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1865>>> f.clean('53000')
     1866Traceback (most recent call last):
     1867...
     1868ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1869>>> f.clean('2A200')
     1870Traceback (most recent call last):
     1871...
     1872ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1873>>> f.clean('380001')
     1874Traceback (most recent call last):
     1875...
     1876ValidationError: [u'Enter a valid postal code in the range and format 01XXX - 52XXX.']
     1877>>> f.clean(None)
     1878u''
     1879>>> f.clean('')
     1880u''
     1881
     1882# ESSubdivisionSelect ###############################################################
     1883
     1884ESSubdivisionSelect
     1885>>> w = ESSubdivisionSelect()
     1886>>> w.render('provinces', 'TF')
     1887u'<select name="provinces">\n<option value="VI">\xc1lava</option>\n<option value="AB">Albacete</option>\n<option value="A">Alicante</option>\n<option value="AL">Almer\xeda</option>\n<option value="O">Asturias</option>\n<option value="AV">\xc1vila</option>\n<option value="BA">Badajoz</option>\n<option value="PM">Islas Baleares</option>\n<option value="B">Barcelona</option>\n<option value="Bu">Burgos</option>\n<option value="CC">C\xe1ceres</option>\n<option value="CA">C\xe1diz</option>\n<option value="S">Cantabria</option>\n<option value="CS">Castell\xf3n</option>\n<option value="CE">Ceuta</option>\n<option value="CR">Ciudad Real</option>\n<option value="CO">C\xf3rdoba</option>\n<option value="Cu">Cuenca</option>\n<option value="GI">Gerona</option>\n<option value="GR">Granada</option>\n<option value="Gu">Guadalajara</option>\n<option value="SS">Guip\xfazcoa</option>\n<option value="H">Huelva</option>\n<option value="Hu">Huesca</option>\n<option value="J">Ja\xe9n</option>\n<option value="C">La Coru\xf1a</option>\n<option value="LO">La Rioja</option>\n<option value="GC">Las Palmas</option>\n<option value="LE">Le\xf3n</option>\n<option value="L">L\xe9rida</option>\n<option value="Lu">Lugo</option>\n<option value="M">Madrid</option>\n<option value="MA">M\xe1laga</option>\n<option value="ML">Melilla</option>\n<option value="Mu">Murcia</option>\n<option value="NA">Navarra</option>\n<option value="OR">Orense</option>\n<option value="P">Palencia</option>\n<option value="PO">Pontevedra</option>\n<option value="SA">Salamanca</option>\n<option value="TF" selected="selected">Santa Cruz de Tenerife</option>\n<option value="SG">Segovia</option>\n<option value="SE">Sevilla</option>\n<option value="SO">Soria</option>\n<option value="T">Tarragona</option>\n<option value="TE">Teruel</option>\n<option value="TO">Toledo</option>\n<option value="V">Valencia</option>\n<option value="VA">Valladolid</option>\n<option value="BI">Vizcaya</option>\n<option value="ZA">Zamora</option>\n<option value="Z">Zaragoza</option>\n</select>'
     1888
     1889# ESIdentityCardNumberField #############################################################
     1890
     1891ESIdentityCardNumberField
     1892>>> f = ESIdentityCardNumberField()
     1893>>> f.clean('78699688J')
     1894u'78699688J'
     1895>>> f.clean('78699688-J')
     1896u'78699688J'
     1897>>> f.clean('78699688 J')
     1898u'78699688J'
     1899>>> f.clean('78699688 j')
     1900u'78699688J'
     1901>>> f.clean('78699688T')
     1902Traceback (most recent call last):
     1903...
     1904ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1905>>> f.clean('X0901797J')
     1906u'X0901797J'
     1907>>> f.clean('X-6124387-Q')
     1908u'X6124387Q'
     1909>>> f.clean('X 0012953 G')
     1910u'X0012953G'
     1911>>> f.clean('x-3287690-r')
     1912u'X3287690R'
     1913>>> f.clean('X-03287690r')
     1914u'X03287690R'
     1915>>> f.clean('X-03287690')
     1916Traceback (most recent call last):
     1917...
     1918ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1919>>> f.clean('X-03287690-T')
     1920Traceback (most recent call last):
     1921...
     1922ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1923>>> f.clean('B38790911')
     1924u'B38790911'
     1925>>> f.clean('B-3879091A')
     1926Traceback (most recent call last):
     1927...
     1928ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1929>>> f.clean('B 38790917')
     1930Traceback (most recent call last):
     1931...
     1932ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1933>>> f.clean('P-3900800-H')
     1934u'P3900800H'
     1935>>> f.clean('P 39008008')
     1936Traceback (most recent call last):
     1937...
     1938ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1939>>> f.clean('C-28795565')
     1940u'C28795565'
     1941>>> f.clean('C 2879556E')
     1942u'C2879556E'
     1943>>> f.clean('C28795567')
     1944Traceback (most recent call last):
     1945...
     1946ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1947>>> f.clean('I38790911')
     1948Traceback (most recent call last):
     1949...
     1950ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1951>>> f.clean('78699688-2')
     1952Traceback (most recent call last):
     1953...
     1954ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1955>>> f.clean('999999999')
     1956Traceback (most recent call last):
     1957...
     1958ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1959>>> f.clean(None)
     1960Traceback (most recent call last):
     1961...
     1962ValidationError: [u'This field is required.']
     1963>>> f.clean('')
     1964Traceback (most recent call last):
     1965...
     1966ValidationError: [u'This field is required.']
     1967>>> f = ESIdentityCardNumberField(required=False)
     1968>>> f.clean(None)
     1969u''
     1970>>> f.clean('')
     1971u''
     1972>>> f = ESIdentityCardNumberField()
     1973>>> f.clean('78699688J')
     1974u'78699688J'
     1975>>> f.clean('78699688T')
     1976Traceback (most recent call last):
     1977...
     1978ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1979>>> f.clean('X-03287690-T')
     1980Traceback (most recent call last):
     1981...
     1982ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1983>>> f.clean('B-3879091A')
     1984Traceback (most recent call last):
     1985...
     1986ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1987>>> f.clean('P 39008008')
     1988Traceback (most recent call last):
     1989...
     1990ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1991>>> f.clean('C28795567')
     1992Traceback (most recent call last):
     1993...
     1994ValidationError: [u'Please enter a valid NIF, NIE or CIF.']
     1995>>> f = ESIdentityCardNumberField(cif=False)
     1996>>> f.clean('78699688J')
     1997u'78699688J'
     1998>>> f.clean('X-6124387-Q')
     1999u'X6124387Q'
     2000>>> f.clean('B38790911')
     2001Traceback (most recent call last):
     2002...
     2003ValidationError: [u'Please enter a valid NIF or NIE.']
     2004>>> f = ESIdentityCardNumberField(nif=False)
     2005>>> f.clean('78699688J')
     2006Traceback (most recent call last):
     2007...
     2008ValidationError: [u'Please enter a valid CIF.']
     2009>>> f.clean('X-6124387-Q')
     2010Traceback (most recent call last):
     2011...
     2012ValidationError: [u'Please enter a valid CIF.']
     2013>>> f.clean('B38790911')
     2014u'B38790911'
    18212015"""
Back to Top