diff --git a/CHANGES.txt b/CHANGES.txt index dc5d58f..89e961a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -12,6 +12,8 @@ Next release - Allow ``quant`` and ``rounding`` args to ``colander.Decimal`` constructor. +- ``luhnok`` validator added (credit card luhn mod10 validator). + 0.9.8 (2012-04-27) ------------------ diff --git a/colander/__init__.py b/colander/__init__.py index 904c6bd..d40cb6e 100644 --- a/colander/__init__.py +++ b/colander/__init__.py @@ -355,6 +355,28 @@ class OneOf(object): mapping={'val':value, 'choices':choices}) raise Invalid(node, err) +def luhnok(node, value): + """ Validator which checks to make sure that the value passes a luhn + mod-10 checksum (credit cards). ``value`` must be a string, not an + integer.""" + sum = 0 + num_digits = len(value) + oddeven = num_digits & 1 + + for count in range(0, num_digits): + digit = int(value[count]) + + if not (( count & 1 ) ^ oddeven ): + digit = digit * 2 + if digit > 9: + digit = digit - 9 + + sum = sum + digit + + if not (sum % 10) == 0: + raise Invalid(node, + '%r is not a valid credit card number' % value) + class SchemaType(object): """ Base class for all schema types """ def flatten(self, node, appstruct, prefix='', listitem=False): diff --git a/colander/tests/test_colander.py b/colander/tests/test_colander.py index 72b8b9f..8afb1e9 100644 --- a/colander/tests/test_colander.py +++ b/colander/tests/test_colander.py @@ -408,6 +408,25 @@ class TestOneOf(unittest.TestCase): e = invalid_exc(validator, None, None) self.assertEqual(e.msg.interpolate(), '"None" is not one of 1, 2') +class Test_luhnok(unittest.TestCase): + def _callFUT(self, node, value): + from colander import luhnok + return luhnok(node, value) + + def test_fail(self): + import colander + val = '10' + self.assertRaises(colander.Invalid, self._callFUT, None, val) + + def test_fail2(self): + import colander + val = '99999999999999999999999' + self.assertRaises(colander.Invalid, self._callFUT, None, val) + + def test_success(self): + val = '4111111111111111' + self.assertFalse(self._callFUT(None, val)) + class TestSchemaType(unittest.TestCase): def _makeOne(self, *arg, **kw): from colander import SchemaType diff --git a/docs/api.rst b/docs/api.rst index e92ec02..c05dea3 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -65,6 +65,8 @@ Validators .. autoclass:: Email + .. autofunction:: luhnok + Types ~~~~~