Ticket #8273: localflavor-numberwithchecksumfield.patch

File localflavor-numberwithchecksumfield.patch, 19.6 KB (added by Piotr Lewandowski <django@…>, 7 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"""
Back to Top