fields: add Luhn algorithm to string field validation (#35013)

This commit is contained in:
Benjamin Dauvergne 2019-07-23 17:40:14 +02:00
parent e5990b82fb
commit b7cbff134c
3 changed files with 102 additions and 0 deletions

View File

@ -630,6 +630,66 @@ def test_wcsextrastringwidget_builtin_validation():
mock_form_submission(req, widget, {'test': '1234'})
assert widget.has_error()
def test_wcsextrastringwidget_siren_validation():
class FakeField: pass
fakefield = FakeField()
fakefield.validation = {'type': 'siren-fr'}
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '443170139'})
assert not widget.has_error()
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '443170130'})
assert widget.has_error()
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '44317013900036'})
assert widget.has_error()
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': 'XXX170130'})
assert widget.has_error()
def test_wcsextrastringwidget_siret_validation():
class FakeField: pass
fakefield = FakeField()
fakefield.validation = {'type': 'siret-fr'}
# regular case
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '44317013900036'})
assert not widget.has_error()
# special case la poste
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '35600000000048'})
assert not widget.has_error()
# failing cases
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '44317013900037'})
assert widget.has_error()
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': 'ABC17013900037'})
assert widget.has_error()
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
mock_form_submission(req, widget, {'test': '443170139'})
assert widget.has_error()
def test_wcsextrastringwidget_django_validation():
class FakeField: pass
fakefield = FakeField()

View File

@ -906,6 +906,8 @@ class ValidationWidget(CompositeWidget):
('digits', {'title': N_('Digits'), 'regex': '\d+'}),
('phone-fr', {'title': N_('Phone Number (France)'), 'regex': '0[\d\.\s]{9}'}),
('zipcode-fr', {'title': N_('Zip Code (France)'), 'regex': '\d{5}'}),
('siren-fr', {'title': N_('SIREN Code (France)'), 'function': 'validate_siren'}),
('siret-fr', {'title': N_('SIRET Code (France)'), 'function': 'validate_siret'}),
('regex', {'title': N_('Regular Expression')}),
('django', {'title': N_('Django Condition')}),
])
@ -965,6 +967,9 @@ class ValidationWidget(CompositeWidget):
condition = ValidationCondition(validation['value'], value=value)
return condition.evaluate()
return django_validation
validation_method = cls.validation_methods.get(validation['type'])
if 'function' in validation_method:
return getattr(misc, validation_method['function'])
@classmethod
def get_validation_pattern(cls, validation):

View File

@ -639,3 +639,40 @@ def html2text(text):
if isinstance(text, (htmltext, str)):
text = unicode(str(text), get_publisher().site_charset)
return site_encode(HTMLParser().unescape(strip_tags(text)))
def validate_luhn(string_value, length=None):
'''Verify Luhn checksum on a string representing a number'''
if not string_value:
return False
if length is not None and len(string_value) != length:
return False
if not string_value.isdigit():
return False
# take all digits counting 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 False
return True
def validate_siren(string_value):
return validate_luhn(string_value, length=9)
def validate_siret(string_value):
# special case : La Poste
if not string_value.isdigit():
return False
if (string_value.startswith('356000000')
and len(string_value) == 14
and sum(int(x) for x in string_value) % 5 == 0):
return True
return validate_luhn(string_value, length=14)