From f67b3fcac3c647e686aebd601ec2b7e1d47f6437 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 23 Jul 2019 17:40:14 +0200 Subject: [PATCH] template filter for Luhn algorithm (#35013) --- tests/test_templates.py | 48 +++++++++++++++++++++++++++++ wcs/qommon/templatetags/qommon.py | 50 +++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/tests/test_templates.py b/tests/test_templates.py index 18f0ea65..38419746 100644 --- a/tests/test_templates.py +++ b/tests/test_templates.py @@ -522,3 +522,51 @@ def test_reproj(): lazy_formdata = LazyFormData(MockFormData()) tmpl = Template('{% with form_geoloc_base|reproj:"EPSG:3946" as c %}{{c.0}}/{{c.1}}{% endwith %}') assert tmpl.render(CompatibilityNamesDict({'form': lazy_formdata})) == '1625337.15483/5422836.71627' + +def test_luhn(): + # Failing cases + tmpl = Template('{% if None|is_valid_siret%}yes{% endif %}') + assert tmpl.render() == '' + tmpl = Template('{% if True|is_valid_siret%}yes{% endif %}') + assert tmpl.render() == '' + tmpl = Template('{% if False|is_valid_siret%}yes{% endif %}') + assert tmpl.render() == '' + + # EO Siret + tmpl = Template('{% if "44317013900036"|is_valid_siret%}yes{% endif %}') + assert tmpl.render() == 'yes' + tmpl = Template('{% if 44317013900036|is_valid_siret%}yes{% endif %}') + assert tmpl.render() == 'yes' + + # with spaces + tmpl = Template('{% if " 443 170\t139 00 036 "|is_valid_siret %}yes{% endif %}') + assert tmpl.render() == 'yes' + tmpl = Template('{{ " 443 170\t139 00 036 "|is_valid_siret }}') + assert tmpl.render() == '44317013900036' + for i in range(10): + if i == 6: + continue + tmpl = Template('{% if "4431701390003' + str(i) + '"|is_valid_siret %}yes{% endif %}') + assert tmpl.render() == '' + + # EO Siren + tmpl = Template('{% if "443170139"|is_valid_siren %}yes{% endif %}') + assert tmpl.render() == 'yes' + for i in range(10): + if i == 9: + continue + tmpl = Template('{% if "44317013' + str(i) + '"|is_valid_siren %}yes{% endif %}') + assert tmpl.render() == '' + + # credit card number + tmpl = Template('{% if "5555 5555 5555 4444"|is_valid_credit_card_number %}yes{% endif %}') + assert tmpl.render() == 'yes' + tmpl = Template('{{ "5555 5555 5555 4444"|is_valid_credit_card_number }}') + assert tmpl.render() == '5555555555554444' + + # La poste + tmpl = Template('{% if "356 000 000"|is_valid_siren%}yes{% endif %}') + assert tmpl.render() == 'yes' + + tmpl = Template('{% if "356 000 000 12345"|is_valid_siret%}yes{% endif %}') + assert tmpl.render() == 'yes' diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py index 5178392d..6b96da93 100644 --- a/wcs/qommon/templatetags/qommon.py +++ b/wcs/qommon/templatetags/qommon.py @@ -20,6 +20,7 @@ from decimal import InvalidOperation as DecimalInvalidOperation from decimal import DivisionByZero as DecimalDivisionByZero import hashlib import math +import re import string import random @@ -348,3 +349,52 @@ def reproj(coords, projection_name): proj = pyproj.Proj(init='EPSG:4326') target_proj = pyproj.Proj(init=projection_name) return pyproj.transform(proj, target_proj, coords['lon'], coords['lat']) + +def clean_numeric_identifier(value): + if not value: + return u'' + string_value = unicode(value) + string_value = re.sub(r'\s', '', string_value) + if not string_value.isdigit(): + return u'' + return string_value + +def is_valid_luhn(value, length=None): + '''Verify Luhn checksum on a string representing a number''' + if not value: + return u'' + string_value = unicode(value) + if length is not None and len(string_value) != length: + return u'' + + # take all digits couting from the right, double value for digits pair + # index (counting from 1), if double has 2 digits take their sum + checksum = 0 + for i, x in enumerate(reversed(string_value)): + if i % 2 == 0: + checksum += int(x) + else: + checksum += sum(int(y) for y in str(2 * int(x))) + if checksum % 10 != 0: + return u'' + return string_value + +@register.filter +def is_valid_siren(value): + string_value = clean_numeric_identifier(value) + # special case : La Poste + if string_value == '356000000': + return string_value + return is_valid_luhn(string_value, length=9) + +@register.filter +def is_valid_siret(value): + string_value = clean_numeric_identifier(value) + # special case : La Poste + if string_value.startswith('356000000') and len(string_value) == 14: + return string_value + return is_valid_luhn(string_value, length=14) + +@register.filter +def is_valid_credit_card_number(value): + return is_valid_luhn(clean_numeric_identifier(value), length=16) -- 2.22.0