From e6819d13ae149c158034065d1fd53eab37618542 Mon Sep 17 00:00:00 2001
From: Nicolas ROCHE <nroche@entrouvert.com>
Date: Mon, 25 Feb 2019 14:43:31 +0100
Subject: [PATCH] templatetags: add is_valid_xxx filters (#30849)

---
 tests/test_formdata.py            |  19 +++++
 tests/test_templates.py           |  31 ++++++++
 wcs/qommon/templatetags/qommon.py | 115 ++++++++++++++++++++++++++++++
 3 files changed, 165 insertions(+)

diff --git a/tests/test_formdata.py b/tests/test_formdata.py
index 3bb828a7..a7f28138 100644
--- a/tests/test_formdata.py
+++ b/tests/test_formdata.py
@@ -1336,3 +1336,22 @@ def test_lazy_formdata_decimal_filter(pub):
 
         tmpl = Template('{{ 4.12|decimal:form_var_arg }}')
         assert tmpl.render(context) == '4.120'
+
+def test_lazy_formdata_is_valid_filters(pub, variable_test_data):
+    formdef = FormDef.select()[0]
+    formdata = formdef.data_class().select()[0]
+    for mode in (None, 'lazy'):
+        pub.substitutions.reset()
+        pub.substitutions.feed(formdef)
+
+        nir = '1 78 05 75 114 287 79'
+        rib = '2004 1010 1247 3252 4N03 321'
+        iban = 'FR77 2004 1010 1247 3252 4N03 321'
+
+        with pub.substitutions.temporary_feed(formdata, force_mode=mode):
+            tmpl = Template('{{ nir|is_valid_nir }}')
+            assert tmpl.render({'nir': nir}) == nir
+            tmpl = Template('{{ rib|is_valid_rib }}')
+            assert tmpl.render({'rib': rib}) == rib
+            tmpl = Template('{{ iban|is_valid_iban }}')
+            assert tmpl.render({'iban': iban}) == iban
diff --git a/tests/test_templates.py b/tests/test_templates.py
index 24a5d411..7067a837 100644
--- a/tests/test_templates.py
+++ b/tests/test_templates.py
@@ -323,3 +323,34 @@ def test_decimal_templatetag():
     assert tmpl.render() == 'hello'
     tmpl = Template('{% if 3|decimal|decimal == 3 %}hello{% endif %}')
     assert tmpl.render() == 'hello'
+
+def test_is_valid_templatetag():
+    nir = '1 78 05 75 114 287 79'
+    rib = '3000 6000 0112 3456 7890 189'
+    wrong_nir = '1 78 05 75 114 287 78'
+    wrong_rib = '2004 1010 1247 3352 4N03 321'
+    wrong_iban = 'FR77 2004 1010 1247 3352 4N03 321'
+
+    ibans = [
+        'BE71 0961 2345 6769',                # Belgium
+        'FR76 3000 6000 0112 3456 7890 189',  # France
+        'DE91 1000 0000 0123 4567 89',        # Germany
+        'GR96 0810 0010 0000 0123 4567 890',  # Greece
+        'RO09 BCYP 0000 0012 3456 7890',      # Romania
+        'SA44 2000 0001 2345 6789 1234',      # Saudi Arabia
+        'ES79 2100 0813 6101 2345 6789',      # Spain
+        'CH56 0483 5012 3456 7800 9',         # Switzerland
+        'GB98 MIDL 0700 9312 3456 78']        # United Kingdom
+
+    tmpl = Template('{{ nir|is_valid_nir }}')
+    assert tmpl.render({'nir': nir}) == nir
+    assert tmpl.render({'nir': wrong_nir}) == 'Invalid NIR: %s' % wrong_nir
+
+    tmpl = Template('{{ rib|is_valid_rib }}')
+    assert tmpl.render({'rib': rib}) == rib
+    assert tmpl.render({'rib': wrong_rib}) == 'Invalid RIB: %s' % wrong_rib
+
+    tmpl = Template('{{ iban|is_valid_iban }}')
+    for iban in ibans:
+        assert tmpl.render({'iban': iban}) == iban
+    assert tmpl.render({'iban': wrong_iban}) == 'Invalid IBAN: %s' % wrong_iban
diff --git a/wcs/qommon/templatetags/qommon.py b/wcs/qommon/templatetags/qommon.py
index 12c8684a..40070146 100644
--- a/wcs/qommon/templatetags/qommon.py
+++ b/wcs/qommon/templatetags/qommon.py
@@ -24,6 +24,7 @@ from django.utils.safestring import mark_safe
 from wcs.qommon import evalutils
 from wcs.qommon import tokens
 from wcs.qommon.admin.texts import TextsDirectory
+from wcs.qommon import _
 
 register = template.Library()
 
@@ -227,3 +228,117 @@ def action_button(context, action_id, label, delay=3):
     }
     token.store()
     return '---===BUTTON:%s:%s===---' % (token.id, label)
+
+def compute_big_modulo_97(string_number):
+    '''https://en.wikipedia.org/wiki/International_Bank_Account_Number'''
+    tmp = string_number[0:9]
+    string_number = string_number[9:]
+    modulo = int(tmp) % 97
+
+    while string_number != '':
+        tmp = "%s%s" % (modulo, string_number[0:9])
+        string_number = string_number[9:]
+        modulo = int(tmp) % 97
+
+    return modulo
+
+def char_to_rib_char(car):
+    if '0' <= car <= '9':
+        return car
+    if car >= 'A' and car <= 'I':
+        return chr(ord(car) - ord('A') + ord('1'))
+    if car >= 'J' and car <= 'R':
+        return chr(ord(car) - ord('J') + ord('1'))
+    if car >= 'S' and car <= 'Z':
+        return chr(ord(car) - ord('S') + ord('2'))  # yes 2, not an error
+    error = "unexpected '%c' char for rib number trnaslation" % (car)
+    raise Exception(error)
+
+def char_to_iban_char(car):
+    if '0' <= car <= '9':
+        return car
+    if car >= 'A' and car <= 'Z':
+        return str(ord(car) - ord('A') + 10)
+    error = "unexpected '%c' char for iban number translation" % (car)
+    raise Exception(error)
+
+def compute_key_rib(bank_code, office_code, account_number):
+    '''https://fr.wikipedia.org/wiki/Cl%C3%A9_RIB'''
+    account_number_converted = ''
+    for car in account_number:
+        account_number_converted += char_to_rib_char(car)
+
+    account_number_part1 = account_number_converted[0:6]
+    account_number_part2 = account_number_converted[6:11]
+    assert account_number_converted == "%s%s" % (account_number_part1, account_number_part2)
+
+    return str(97 - ((89 * int(bank_code)
+                    + 15 * int(office_code)
+                    + 76 * int(account_number_part1)
+                    + 3 * int(account_number_part2)) % 97))
+
+def compute_key_iban(country_code, bban):
+    '''https://fr.wikipedia.org/wiki/International_Bank_Account_Number'''
+    dummy_iban = "%s%s00" % (bban, country_code)
+    dummy_iban_converted = ''
+
+    for car in dummy_iban:
+        dummy_iban_converted += char_to_iban_char(car)
+
+    return "%02i" % (98 - compute_big_modulo_97(dummy_iban_converted))
+
+def compute_key_nir(sex, year, month, dept, comm, order):
+    '''https://fr.wikipedia.org/wiki/Num%C3%A9ro_de_s%C3%A9curit%C3%A9_sociale_en_France'''
+    if dept == '2A':
+        dept = '19'
+    if dept == '2B':
+        dept = 18
+    return str((97 - int("%s%s%s%s%s%s" % (sex, year, month, dept, comm, order)) % 97))
+
+def parse_identifier_number(value):
+    if hasattr(value, 'get_value'):
+        value = value.get_value()  # unlazy
+    value = value.replace(' ', '')
+    value = value.replace('-', '')
+    return value
+
+@register.filter
+def is_valid_rib(value):
+    rib = parse_identifier_number(value)
+    bank_code = rib[0:5]
+    office_code = rib[5:10]
+    account_number = rib[10:21]
+    rib_key = rib[21:23]
+
+    my_rib_key = compute_key_rib(bank_code, office_code, account_number)
+    if my_rib_key != rib_key:
+        return _('Invalid RIB: %s') % value
+    return value
+
+@register.filter
+def is_valid_iban(value):
+    iban = parse_identifier_number(value)
+    country_code = iban[0:2]
+    iban_key = iban[2:4]
+    bban = iban[4:27]
+
+    my_iban_key = compute_key_iban(country_code, bban)
+    if my_iban_key != iban_key:
+        return _('Invalid IBAN: %s') % value
+    return value
+
+@register.filter
+def is_valid_nir(value):
+    nir = parse_identifier_number(value)
+    sex = nir[0]
+    year = nir[1:3]
+    month = nir[3:5]
+    dept = nir[5:7]
+    comm = nir[7:10]
+    order = nir[10:13]
+    nir_key = nir[13:15]
+
+    my_nir_key = compute_key_nir(sex, year, month, dept, comm, order)
+    if my_nir_key != nir_key:
+        return _('Invalid NIR: %s') % value
+    return value
-- 
2.20.1

