Code

Ticket #8273: localflavor-numberwithchecksumfield.patch

File localflavor-numberwithchecksumfield.patch, 19.6 KB (added by Piotr Lewandowski <django@…>, 6 years ago)
  • django/contrib/localflavor/ar/forms.py

     
    33AR-specific Form helpers. 
    44""" 
    55 
     6from django.contrib.localflavor.generic.forms import NumberWithChecksumField 
    67from django.forms import ValidationError 
    78from django.forms.fields import RegexField, CharField, Select, EMPTY_VALUES 
    89from django.utils.encoding import smart_unicode 
     
    7071 
    7172        return value 
    7273 
    73 class ARCUITField(RegexField): 
     74class ARCUITField(NumberWithChecksumField): 
    7475    """ 
    7576    This field validates a CUIT (Código Único de Identificación Tributaria). A 
    7677    CUIT is of the form XX-XXXXXXXX-V. The last digit is a check digit. 
    7778    """ 
    78     default_error_messages = { 
    79         'invalid': _('Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'), 
    80         'checksum': _("Invalid CUIT."), 
    81     } 
    8279 
    83     def __init__(self, *args, **kwargs): 
    84         super(ARCUITField, self).__init__(r'^\d{2}-?\d{8}-?\d$', 
    85             *args, **kwargs) 
     80    weights = ((5, 4, 3, 2, 7, 6, 5, 4, 3, 2, 0),) 
    8681 
    87     def clean(self, value): 
    88         """ 
    89         Value can be either a string in the format XX-XXXXXXXX-X or an 
    90         11-digit number. 
    91         """ 
    92         value = super(ARCUITField, self).clean(value) 
    93         if value in EMPTY_VALUES: 
    94             return u'' 
    95         value, cd = self._canon(value) 
    96         if self._calc_cd(value) != cd: 
    97             raise ValidationError(self.error_messages['checksum']) 
    98         return self._format(value, cd) 
     82    def format(self, digits, value): 
     83        return '-'.join([digits[:2], digits[2:-1], digits[-1:]]) 
    9984 
    100     def _canon(self, cuit): 
    101         cuit = cuit.replace('-', '') 
    102         return cuit[:-1], cuit[-1] 
    103  
    104     def _calc_cd(self, cuit): 
    105         mults = (5, 4, 3, 2, 7, 6, 5, 4, 3, 2) 
    106         tmp = sum([m * int(cuit[idx]) for idx, m in enumerate(mults)]) 
    107         return str(11 - tmp % 11) 
    108  
    109     def _format(self, cuit, check_digit=None): 
    110         if check_digit == None: 
    111             check_digit = cuit[-1] 
    112             cuit = cuit[:-1] 
    113         return u'%s-%s-%s' % (cuit[:2], cuit[2:], check_digit) 
    114  
     85    def verify(self, checksum, digits): 
     86        return 11 - checksum % 11 == digits[-1] 
  • django/contrib/localflavor/br/forms.py

     
    33BR-specific Form helpers 
    44""" 
    55 
     6from django.contrib.localflavor.generic.forms import NumberWithChecksumField 
    67from django.forms import ValidationError 
    78from django.forms.fields import Field, RegexField, CharField, Select, EMPTY_VALUES 
    89from django.utils.encoding import smart_unicode 
  • django/contrib/localflavor/at/forms.py

     
    44 
    55import re 
    66 
     7from django.contrib.localflavor.generic.forms import NumberWithChecksumField 
    78from django.utils.translation import ugettext_lazy as _ 
    89from django.forms.fields import Field, RegexField, Select 
    910from django.forms import ValidationError 
     
    3132        from django.contrib.localflavor.at.at_states import STATE_CHOICES 
    3233        super(ATStateSelect, self).__init__(attrs, choices=STATE_CHOICES) 
    3334 
    34 class ATSocialSecurityNumberField(Field): 
     35class ATSocialSecurityNumberField(NumberWithChecksumField): 
    3536    """ 
    3637    Austrian Social Security numbers are composed of a 4 digits and 6 digits 
    3738    field. The latter represents in most cases the person's birthdate while 
     
    4445    http://de.wikipedia.org/wiki/Sozialversicherungsnummer#.C3.96sterreich 
    4546    """ 
    4647 
    47     default_error_messages = { 
    48         'invalid': _(u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'), 
    49     } 
     48    weights = ((3, 7, 9, -1, 5, 8, 4, 2, 1, 6)), 
    5049 
    51     def clean(self, value): 
    52         if not re_ssn.search(value): 
    53             raise ValidationError(self.error_messages['invalid']) 
    54         sqnr, date = value.split(" ") 
    55         sqnr, check = (sqnr[:3], (sqnr[3])) 
    56         if int(sqnr) < 100: 
    57            raise ValidationError(self.error_messages['invalid']) 
    58         res = int(sqnr[0])*3 + int(sqnr[1])*7 + int(sqnr[2])*9 \ 
    59            + int(date[0])*5 + int(date[1])*8 + int(date[2])*4 \ 
    60            + int(date[3])*2 + int(date[4])*1 + int(date[5])*6 
    61         res = res % 11 
    62         if res != int(check): 
    63            raise ValidationError(self.error_messages['invalid']) 
    64         return u'%s%s %s'%(sqnr, check, date,) 
    65  
     50    def format(self, digits, value): 
     51        return u'%s %s' % (digits[:4], digits[4:]) 
  • django/contrib/localflavor/pl/forms.py

     
    77from django.forms import ValidationError 
    88from django.forms.fields import Select, RegexField 
    99from django.utils.translation import ugettext_lazy as _ 
     10from django.contrib.localflavor.generic.forms import NumberWithChecksumField 
    1011 
    1112class PLVoivodeshipSelect(Select): 
    1213    """ 
     
    2526        from pl_administrativeunits import ADMINISTRATIVE_UNIT_CHOICES 
    2627        super(PLAdministrativeUnitSelect, self).__init__(attrs, choices=ADMINISTRATIVE_UNIT_CHOICES) 
    2728 
    28 class PLNationalIdentificationNumberField(RegexField): 
     29class PLNationalIdentificationNumberField(NumberWithChecksumField): 
    2930    """ 
    3031    A form field that validates as Polish Identification Number (PESEL). 
    3132 
     
    3536 
    3637    The algorithm is documented at http://en.wikipedia.org/wiki/PESEL. 
    3738    """ 
    38     default_error_messages = { 
    39         'invalid': _(u'National Identification Number consists of 11 digits.'), 
    40         'checksum': _(u'Wrong checksum for the National Identification Number.'), 
    41     } 
     39    weights = ((1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1),) 
    4240 
    43     def __init__(self, *args, **kwargs): 
    44         super(PLNationalIdentificationNumberField, self).__init__(r'^\d{11}$', 
    45             max_length=None, min_length=None, *args, **kwargs) 
     41    def verify(self, *args, **kwargs): 
     42        kwargs['modulo'] = 10 
     43        return super(PLNationalIdentificationNumberField, self).verify(*args, **kwargs) 
    4644 
    47     def clean(self,value): 
    48         super(PLNationalIdentificationNumberField, self).clean(value) 
    49         if not self.has_valid_checksum(value): 
    50             raise ValidationError(self.error_messages['checksum']) 
    51         return u'%s' % value 
    52  
    53     def has_valid_checksum(self, number): 
    54         """ 
    55         Calculates a checksum with the provided algorithm. 
    56         """ 
    57         multiple_table = (1, 3, 7, 9, 1, 3, 7, 9, 1, 3, 1) 
    58         result = 0 
    59         for i in range(len(number)): 
    60             result += int(number[i]) * multiple_table[i] 
    61         return result % 10 == 0 
    62  
    63 class PLTaxNumberField(RegexField): 
     45class PLTaxNumberField(NumberWithChecksumField): 
    6446    """ 
    6547    A form field that validates as Polish Tax Number (NIP). 
    6648    Valid forms are: XXX-XXX-YY-YY or XX-XX-YYY-YYY. 
     
    6850    Checksum algorithm based on documentation at 
    6951    http://wipos.p.lodz.pl/zylla/ut/nip-rego.html 
    7052    """ 
    71     default_error_messages = { 
    72         'invalid': _(u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'), 
    73         'checksum': _(u'Wrong checksum for the Tax Number (NIP).'), 
    74     } 
     53    weights = ((6, 5, 7, 2, 3, 4, 5, 6, 7, -1),) 
    7554 
    76     def __init__(self, *args, **kwargs): 
    77         super(PLTaxNumberField, self).__init__(r'^\d{3}-\d{3}-\d{2}-\d{2}$|^\d{2}-\d{2}-\d{3}-\d{3}$', 
    78             max_length=None, min_length=None, *args, **kwargs) 
    7955 
    80     def clean(self,value): 
    81         super(PLTaxNumberField, self).clean(value) 
    82         value = re.sub("[-]", "", value) 
    83         if not self.has_valid_checksum(value): 
    84             raise ValidationError(self.error_messages['checksum']) 
    85         return u'%s' % value 
    86  
    87     def has_valid_checksum(self, number): 
    88         """ 
    89         Calculates a checksum with the provided algorithm. 
    90         """ 
    91         multiple_table = (6, 5, 7, 2, 3, 4, 5, 6, 7) 
    92         result = 0 
    93         for i in range(len(number)-1): 
    94             result += int(number[i]) * multiple_table[i] 
    95  
    96         result %= 11 
    97         if result == int(number[-1]): 
    98             return True 
    99         else: 
    100             return False 
    101  
    102 class PLNationalBusinessRegisterField(RegexField): 
     56class PLNationalBusinessRegisterField(NumberWithChecksumField): 
    10357    """ 
    10458    A form field that validated as Polish National Official Business Register Number (REGON) 
    10559    Valid forms are: 7 or 9 digits number 
     
    10862 
    10963    The checksum algorithm is documented at http://wipos.p.lodz.pl/zylla/ut/nip-rego.html 
    11064    """ 
    111     default_error_messages = { 
    112         'invalid': _(u'National Business Register Number (REGON) consists of 7 or 9 digits.'), 
    113         'checksum': _(u'Wrong checksum for the National Business Register Number (REGON).'), 
    114     } 
     65    weights = ( 
     66        (2, 3, 4, 5, 6, 7, -1), 
     67        (8, 9, 2, 3, 4, 5, 6, 7, -1), 
     68        (2, 4, 8, 5, 0, 9, 7, 3, 6, 1, 2, 4, 8, -1), 
     69    ) 
    11570 
    116     def __init__(self, *args, **kwargs): 
    117         super(PLNationalBusinessRegisterField, self).__init__(r'^\d{7,9}$', 
    118             max_length=None, min_length=None, *args, **kwargs) 
    119  
    120     def clean(self,value): 
    121         super(PLNationalBusinessRegisterField, self).clean(value) 
    122         if not self.has_valid_checksum(value): 
    123             raise ValidationError(self.error_messages['checksum']) 
    124         return u'%s' % value 
    125  
    126     def has_valid_checksum(self, number): 
    127         """ 
    128         Calculates a checksum with the provided algorithm. 
    129         """ 
    130         multiple_table_7 = (2, 3, 4, 5, 6, 7) 
    131         multiple_table_9 = (8, 9, 2, 3, 4, 5, 6, 7) 
    132         result = 0 
    133  
    134         if len(number) == 7: 
    135             multiple_table = multiple_table_7 
    136         else: 
    137             multiple_table = multiple_table_9 
    138  
    139         for i in range(len(number)-1): 
    140             result += int(number[i]) * multiple_table[i] 
    141  
    142         result %= 11 
    143         if result == 10: 
    144             result = 0 
    145         if result  == int(number[-1]): 
    146             return True 
    147         else: 
    148             return False 
    149  
    15071class PLPostalCodeField(RegexField): 
    15172    """ 
    15273    A form field that validates as Polish postal code. 
  • django/contrib/localflavor/generic/forms.py

     
    11from django import forms 
     2from django.utils.translation import ugettext_lazy, ungettext_lazy 
     3from itertools import izip 
     4import re 
    25 
    36DEFAULT_DATE_INPUT_FORMATS = ( 
    47    '%Y-%m-%d', '%d/%m/%Y', '%d/%m/%y', # '2006-10-25', '25/10/2006', '25/10/06' 
     
    3639    def __init__(self, input_formats=None, *args, **kwargs): 
    3740        input_formats = input_formats or DEFAULT_DATETIME_INPUT_FORMATS 
    3841        super(DateTimeField, self).__init__(input_formats=input_formats, *args, **kwargs) 
     42 
     43class NumberWithChecksumField(forms.Field): 
     44    """ 
     45    A number input field which verifies checksum using provided weights. 
     46    """ 
     47 
     48    def __init__(self, *args, **kwargs): 
     49        msg_data = dict( 
     50            number = len(self.weights[-1]) 
     51        ) 
     52        if len(self.weights) == 1: 
     53            errmsg = ungettext_lazy( 
     54                'This field requires %(number)d digits.', 
     55                'This field requires %(number)d digits.', 
     56                msg_data['number'] 
     57            ) % msg_data 
     58        else: 
     59            msg_data['numbers'] = ', '.join([ 
     60                str(len(set_)) for set_ in self.weights[:-1] 
     61            ]) 
     62            errmsg = ungettext_lazy( 
     63                'This field requires %(numbers)s or %(number)d digits.', 
     64                'This field requires %(numbers)s or %(number)d digits.', 
     65                msg_data['number'] 
     66            ) % msg_data 
     67        error_messages = kwargs.setdefault('error_messages', {}) 
     68        error_messages.setdefault('invalid', errmsg) 
     69        error_messages.setdefault('checksum', ugettext_lazy('This value has invalid checksum.')) 
     70        super(NumberWithChecksumField, self).__init__(*args, **kwargs) 
     71 
     72    def verify(self, checksum, digits, modulo = 11): 
     73        """ 
     74        Verifies whether the provided checksum if correct. 
     75        """ 
     76        return checksum % modulo % 10 == 0 
     77 
     78    def format(self, digits, value): 
     79        """ 
     80        Normalizes cleaned value. 
     81        """ 
     82        return digits 
     83 
     84    def get_digits(self, value): 
     85        """ 
     86        Extracts digits from provided value. 
     87        """ 
     88        digits = re.sub(r'[^0-9]', '', value) 
     89        return [int(i) for i in digits] 
     90 
     91    def calculate_checksum(self, digits, weights): 
     92        return sum([v * w for v, w in izip(digits, weights)]) 
     93     
     94    def clean(self, value): 
     95        value = super(NumberWithChecksumField, self).clean(value) 
     96        if value in forms.fields.EMPTY_VALUES: 
     97            return u'' 
     98        digits = self.get_digits(value) 
     99        for weights in self.weights: 
     100            if len(digits) != len(weights): 
     101                continue 
     102            checksum = self.calculate_checksum(digits, weights) 
     103            if not self.verify(checksum, digits): 
     104                raise forms.ValidationError(self.error_messages['checksum']) 
     105            number = u''.join([str(n) for n in digits]) 
     106            value = self.format(number, value) 
     107            return value 
     108        else: 
     109            raise forms.ValidationError(self.error_messages['invalid']) 
  • tests/regressiontests/forms/localflavor/ar.py

     
    200200>>> f.clean('2-10123456-9') 
    201201Traceback (most recent call last): 
    202202... 
    203 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     203ValidationError: [u'This field requires 11 digits.'] 
    204204>>> f.clean('210123456-9') 
    205205Traceback (most recent call last): 
    206206... 
    207 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     207ValidationError: [u'This field requires 11 digits.'] 
    208208>>> f.clean('20-10123456') 
    209209Traceback (most recent call last): 
    210210... 
    211 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     211ValidationError: [u'This field requires 11 digits.'] 
    212212>>> f.clean('20-10123456-') 
    213213Traceback (most recent call last): 
    214214... 
    215 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     215ValidationError: [u'This field requires 11 digits.'] 
    216216>>> f.clean('20-10123456-5') 
    217217Traceback (most recent call last): 
    218218... 
    219 ValidationError: [u'Invalid CUIT.'] 
     219ValidationError: [u'This value has invalid checksum.'] 
    220220>>> f.clean(u'2-10123456-9') 
    221221Traceback (most recent call last): 
    222222... 
    223 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     223ValidationError: [u'This field requires 11 digits.'] 
    224224>>> f.clean('27-10345678-1') 
    225225Traceback (most recent call last): 
    226226... 
    227 ValidationError: [u'Invalid CUIT.'] 
     227ValidationError: [u'This value has invalid checksum.'] 
    228228>>> f.clean(u'27-10345678-1') 
    229229Traceback (most recent call last): 
    230230... 
    231 ValidationError: [u'Invalid CUIT.'] 
     231ValidationError: [u'This value has invalid checksum.'] 
    232232>>> f.clean(None) 
    233233Traceback (most recent call last): 
    234234... 
     
    256256>>> f.clean('2-10123456-9') 
    257257Traceback (most recent call last): 
    258258... 
    259 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     259ValidationError: [u'This field requires 11 digits.'] 
    260260>>> f.clean('210123456-9') 
    261261Traceback (most recent call last): 
    262262... 
    263 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     263ValidationError: [u'This field requires 11 digits.'] 
    264264>>> f.clean('20-10123456') 
    265265Traceback (most recent call last): 
    266266... 
    267 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     267ValidationError: [u'This field requires 11 digits.'] 
    268268>>> f.clean('20-10123456-') 
    269269Traceback (most recent call last): 
    270270... 
    271 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     271ValidationError: [u'This field requires 11 digits.'] 
    272272>>> f.clean('20-10123456-5') 
    273273Traceback (most recent call last): 
    274274... 
    275 ValidationError: [u'Invalid CUIT.'] 
     275ValidationError: [u'This value has invalid checksum.'] 
    276276>>> f.clean(u'2-10123456-9') 
    277277Traceback (most recent call last): 
    278278... 
    279 ValidationError: [u'Enter a valid CUIT in XX-XXXXXXXX-X or XXXXXXXXXXXX format.'] 
     279ValidationError: [u'This field requires 11 digits.'] 
    280280>>> f.clean('27-10345678-1') 
    281281Traceback (most recent call last): 
    282282... 
    283 ValidationError: [u'Invalid CUIT.'] 
     283ValidationError: [u'This value has invalid checksum.'] 
    284284>>> f.clean(u'27-10345678-1') 
    285285Traceback (most recent call last): 
    286286... 
    287 ValidationError: [u'Invalid CUIT.'] 
     287ValidationError: [u'This value has invalid checksum.'] 
    288288>>> f.clean(None) 
    289289u'' 
    290290>>> f.clean('') 
  • tests/regressiontests/forms/localflavor/at.py

     
    7272>>> f.clean('1237 010181') 
    7373Traceback (most recent call last): 
    7474... 
    75 ValidationError: [u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'] 
     75ValidationError: [u'This value has invalid checksum.'] 
    7676>>> f.clean('12370 010180') 
    7777Traceback (most recent call last): 
    7878... 
    79 ValidationError: [u'Enter a valid Austrian Social Security Number in XXXX XXXXXX format.'] 
     79ValidationError: [u'This field requires 10 digits.'] 
    8080""" 
  • tests/regressiontests/forms/localflavor/pl.py

     
    3434>>> f.clean('43-343-234-323') 
    3535Traceback (most recent call last): 
    3636... 
    37 ValidationError: [u'Enter a tax number field (NIP) in the format XXX-XXX-XX-XX or XX-XX-XXX-XXX.'] 
     37ValidationError: [u'This field requires 10 digits.'] 
    3838>>> f.clean('64-62-414-124') 
    3939u'6462414124' 
    4040>>> f.clean('646-241-41-24') 
     
    4242>>> f.clean('646-241-41-23') 
    4343Traceback (most recent call last): 
    4444... 
    45 ValidationError: [u'Wrong checksum for the Tax Number (NIP).'] 
     45ValidationError: [u'This value has invalid checksum.'] 
    4646  
    4747# PLNationalIdentificationNumberField ############################################ 
    4848 
     
    5353>>> f.clean('80071610610') 
    5454Traceback (most recent call last): 
    5555... 
    56 ValidationError: [u'Wrong checksum for the National Identification Number.'] 
     56ValidationError: [u'This value has invalid checksum.'] 
    5757>>> f.clean('80') 
    5858Traceback (most recent call last): 
    5959... 
    60 ValidationError: [u'National Identification Number consists of 11 digits.'] 
     60ValidationError: [u'This field requires 11 digits.'] 
    6161>>> f.clean('800716106AA') 
    6262Traceback (most recent call last): 
    6363... 
    64 ValidationError: [u'National Identification Number consists of 11 digits.'] 
     64ValidationError: [u'This field requires 11 digits.'] 
    6565 
    6666# PLNationalBusinessRegisterField ################################################ 
    6767 
     
    7272>>> f.clean('590096453') 
    7373Traceback (most recent call last): 
    7474... 
    75 ValidationError: [u'Wrong checksum for the National Business Register Number (REGON).'] 
     75ValidationError: [u'This value has invalid checksum.'] 
    7676>>> f.clean('590096') 
    7777Traceback (most recent call last): 
    7878... 
    79 ValidationError: [u'National Business Register Number (REGON) consists of 7 or 9 digits.'] 
     79ValidationError: [u'This field requires 7, 9 or 14 digits.'] 
    8080 
    8181"""