Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ New fields for existing flavors:

Modifications to existing flavors:

- None
- Deprecated `generic.checksums.luhn` and `generic.checksums.ean`. Please use the python-stdnum library instead.

Other changes:

Expand Down
5 changes: 2 additions & 3 deletions localflavor/ca/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
from django.forms import ValidationError
from django.forms.fields import CharField, Field, Select
from django.utils.translation import ugettext_lazy as _

from localflavor.generic.checksums import luhn
from stdnum import luhn

sin_re = re.compile(r"^(\d{3})-(\d{3})-(\d{3})$")

Expand Down Expand Up @@ -113,6 +112,6 @@ def clean(self, value):
match.group(1),
match.group(2),
match.group(3))
if not luhn(check_number):
if not luhn.is_valid(check_number):
raise ValidationError(self.error_messages['invalid'])
return number
17 changes: 8 additions & 9 deletions localflavor/fr/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@
import re
from datetime import date

from django.core.validators import EMPTY_VALUES
from django.forms import ValidationError
from django.forms.fields import CharField, RegexField, Select
from django.utils.translation import ugettext_lazy as _

from localflavor.generic.checksums import luhn
from stdnum import luhn

from .fr_department import DEPARTMENT_CHOICES_PER_REGION
from .fr_region import REGION_2016_CHOICES, REGION_CHOICES
Expand Down Expand Up @@ -189,7 +187,7 @@ def clean(self, value):
return self.empty_value

value = value.replace(' ', '').replace('-', '')
if not self.r_valid.match(value) or not luhn(value):
if not self.r_valid.match(value) or not luhn.is_valid(value):
raise ValidationError(self.error_messages['invalid'])
return value

Expand Down Expand Up @@ -234,14 +232,15 @@ class FRSIRETField(FRSIRENENumberMixin, CharField):
}

def clean(self, value):
if value not in EMPTY_VALUES:
value = value.replace(' ', '').replace('-', '')
value = super(FRSIRETField, self).clean(value)
if value in self.empty_values:
return self.empty_value

ret = super(FRSIRETField, self).clean(value)
value = value.replace(' ', '').replace('-', '')

if ret is not None and not luhn(ret[:9]):
if not luhn.is_valid(value[:9]):
raise ValidationError(self.error_messages['invalid'])
return ret
return value

def prepare_value(self, value):
if value is None:
Expand Down
53 changes: 34 additions & 19 deletions localflavor/generic/checksums.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
"""Common checksum routines."""
import warnings

from django.utils import six
from stdnum import ean as stdnum_ean
from stdnum import luhn as stdnum_luhn

__all__ = ['luhn', 'ean']
from localflavor.deprecation import RemovedInLocalflavor30Warning

LUHN_ODD_LOOKUP = (0, 2, 4, 6, 8, 1, 3, 5, 7, 9) # sum_of_digits(index * 2)
EAN_LOOKUP = (3, 1)
__all__ = ['luhn', 'ean']


def luhn(candidate):
Expand All @@ -13,15 +16,25 @@ def luhn(candidate):

Luhn algorithm is used in validation of, for example, credit cards.
Both numeric and string candidates are accepted.

.. deprecated:: 2.2
Use the luhn function in the python-stdnum_ library instead.

.. _python-stdnum: https://arthurdejong.org/python-stdnum/
"""
warnings.warn(
'luhn is deprecated in favor of the luhn function in the python-stdnum library.',
RemovedInLocalflavor30Warning,
)

if not isinstance(candidate, six.string_types):
candidate = str(candidate)
try:
evens = sum(int(c) for c in candidate[-1::-2])
odds = sum(LUHN_ODD_LOOKUP[int(c)] for c in candidate[-2::-2])
return ((evens + odds) % 10 == 0)
except ValueError: # Raised if an int conversion fails
return False

# Our version returned True for empty strings.
if candidate == '':
return True

return stdnum_luhn.is_valid(candidate)


def ean(candidate):
Expand All @@ -31,16 +44,18 @@ def ean(candidate):
Note that this validator does not enforce any length checks (usually 13 or 8).

http://en.wikipedia.org/wiki/International_Article_Number_(EAN)

.. deprecated:: 2.2
Use the ean function in the python-stdnum_ library instead.

.. _python-stdnum: https://arthurdejong.org/python-stdnum/
"""
warnings.warn(
'ean is deprecated in favor of the ean function in the python-stdnum library.',
RemovedInLocalflavor30Warning,
)

if not isinstance(candidate, six.string_types):
candidate = str(candidate)
if len(candidate) <= 1:
return False
given_number, given_checksum = candidate[:-1], candidate[-1]
try:
calculated_checksum = sum(
int(digit) * EAN_LOOKUP[i % 2] for i, digit in enumerate(reversed(given_number)))
calculated_checksum = 9 - ((calculated_checksum - 1) % 10)
return str(calculated_checksum) == given_checksum
except ValueError: # Raised if an int conversion fails
return False

return stdnum_ean.is_valid(candidate)
4 changes: 2 additions & 2 deletions localflavor/generic/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from django.core.exceptions import ImproperlyConfigured, ValidationError
from django.utils.deconstruct import deconstructible
from django.utils.translation import ugettext_lazy as _
from stdnum import ean

from . import checksums
from .countries.iso_3166 import ISO_3166_1_ALPHA2_COUNTRY_CODES

# Dictionary of ISO country code to IBAN length.
Expand Down Expand Up @@ -272,7 +272,7 @@ def __call__(self, value):
return value
if self.strip_nondigits:
value = re.compile(r'[^\d]+').sub('', value)
if not checksums.ean(value):
if not ean.is_valid(value):
raise ValidationError(self.message, code='invalid')


Expand Down
5 changes: 2 additions & 3 deletions localflavor/gr/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
from django.forms import Field, RegexField, ValidationError
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _

from localflavor.generic.checksums import luhn
from stdnum import luhn


class GRPostalCodeField(RegexField):
Expand Down Expand Up @@ -100,7 +99,7 @@ def clean(self, value):
raise ValidationError(self.error_messages['invalid'])

self.check_date(val)
if not luhn(val):
if not luhn.is_valid(val):
raise ValidationError(self.error_messages['invalid'])

return val
5 changes: 2 additions & 3 deletions localflavor/hr/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
from django.forms.fields import Field, RegexField, Select
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _

from localflavor.generic.checksums import luhn
from stdnum import luhn

from .hr_choices import HR_COUNTY_CHOICES, HR_LICENSE_PLATE_PREFIX_CHOICES

Expand Down Expand Up @@ -227,7 +226,7 @@ def clean(self, value):
raise ValidationError(self.error_messages['copy'])

# Validate checksum using Luhn algorithm.
if not luhn(value):
if not luhn.is_valid(value):
raise ValidationError(self.error_messages['invalid'])

return '%s' % value
5 changes: 2 additions & 3 deletions localflavor/il/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from django.core.validators import EMPTY_VALUES
from django.forms.fields import Field, RegexField
from django.utils.translation import ugettext_lazy as _

from localflavor.generic.checksums import luhn
from stdnum import luhn

id_number_re = re.compile(r'^(?P<number>\d{1,8})-?(?P<check>\d)$')

Expand Down Expand Up @@ -66,6 +65,6 @@ def clean(self, value):
raise ValidationError(self.error_messages['invalid'])

value = match.group('number') + match.group('check')
if not luhn(value):
if not luhn.is_valid(value):
raise ValidationError(self.error_messages['invalid'])
return value
5 changes: 2 additions & 3 deletions localflavor/za/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
from django.forms import ValidationError
from django.forms.fields import CharField, RegexField, Select
from django.utils.translation import gettext_lazy as _

from localflavor.generic.checksums import luhn
from stdnum import luhn

id_re = re.compile(r'^(?P<yy>\d\d)(?P<mm>\d\d)(?P<dd>\d\d)(?P<mid>\d{4})(?P<end>\d{3})')

Expand Down Expand Up @@ -49,7 +48,7 @@ def clean(self, value):
except ValueError:
raise ValidationError(self.error_messages['invalid'])

if not luhn(value):
if not luhn.is_valid(value):
raise ValidationError(self.error_messages['invalid'])

return value
Expand Down
5 changes: 4 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,10 @@ def find_package_data(where='.', package='',
author_email='foundation@djangoproject.com',
packages=find_packages(exclude=['tests', 'tests.*']),
package_data=find_package_data(),
install_requires=['django>=1.11'],
install_requires=[
'django>=1.11',
'python-stdnum>=1.0',
],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Web Environment',
Expand Down