Django

Code

Ticket #4036: 4036.diff

File 4036.diff, 15.5 kB (added by oggie_rob, 1 year ago)

New patch with simplified Identity number field & error handling

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

    old new  
     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

    old new  
     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

    old new  
    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"""