diff --git a/filters/tests/test_validations.py b/filters/tests/test_validations.py index 3fd52e9..20e924d 100644 --- a/filters/tests/test_validations.py +++ b/filters/tests/test_validations.py @@ -1,6 +1,7 @@ import unittest from nose2.tools.such import helper from voluptuous import Invalid +from django.core.exceptions import ImproperlyConfigured from filters import validations INT = 1 @@ -75,3 +76,27 @@ class CSVofIntegersTestCase(BaseValidationTestCase, unittest.TestCase): NON_INT_UNICODE, NON_ALNUM_STR, NON_ALNUM_UNICODE, NON_INT_CSV, INT, LONG ] + +class GenericSeparatedValidatorTestCase(unittest.TestCase): + def test_default_separator(self): + validator = validations.GenericSeparatedValidator(int) + self.assertEqual(validator('1,2,3'), [1,2,3]) + self.assertRaises(Invalid, validator, 'a,b,c') + self.assertEqual(validator('1', [1])) + + def test_custom_separator(self): + validator = validations.GenericSeparatedValidator(int, 'mm') + self.assertEqual(validator('1mm2mm3'), [1,2,3]) + self.assertRaises(Invalid, validator, 'ammbmmc') + self.assertEqual(validator('1', [1])) + + def test_custom_type(self): + validator = validations.GenericSeparatedValidator( + validations.IntegerLike()) + self.assertEqual(validator('1,2,3'), ['1','2','3']) + self.assertRaises(Invalid, validator, 'a,b,c') + self.assertEqual(validator('1', ['1'])) + + def test_invalid_separator(self): + self.assertRaises(ImproperlyConfigured, + validations.GenericSeparatedValidator, 12) diff --git a/filters/validations.py b/filters/validations.py index 8ac9f55..2e10b71 100644 --- a/filters/validations.py +++ b/filters/validations.py @@ -3,6 +3,7 @@ import numbers from voluptuous import Invalid from django.utils.dateparse import parse_datetime, parse_date +from django.core.exceptions import ImproperlyConfigured # Forward compatibility with Python 3.x if sys.version_info.major == 3: @@ -118,3 +119,68 @@ def fn(value): '<{0}> is not a valid csv of integers'.format(value) ) return fn + + + +class GenericSeparatedValidator(object): + ''' + Creates list like validator for any voluptuous validation function + and any custom string separator. + Instance of the class should be passed to the schema like this: + >>> CSVofIntegers = GenericSeparatedValidator(int, ',') + >>> Schema({ "field": CSVofIntegers() }) + + >>> CSVofIntegers = GenericSeparatedValidator(int, ',') + >>> CSVofIntegers('1,2,3') + [1,2,3] + + >>> WeirdSeparatedValidation = GenericSeparatedValidator(int, '^^') + >>> WeirdSeparatedValidation('1^^2^^3') + [1, 2, 3] + + >>> CSVofIntegerLike = GenericSeparatedValidator(IntegerLike(), ',') + >>> CSVofIntegerLike('a,b,c') + Traceback (most recent call last): + ... + voluptuous.error.Invalid: is not valid set of ,\ + Invalid input ; expected an integer + ''' + + def __init__(self, validation_function, separator=',', msg=None): + if not isinstance(separator, basestring): + raise ImproperlyConfigured( + 'GenericSeparatedValidator separator \ + must be of type basestring' + ) + self.separator = separator + self.validation_function = validation_function + self.msg = msg + if sys.version_info.major == 3: + self.value_type = validation_function.__qualname__.split('.')[0] + else: + self.value_type = validation_function.__name__ + + def __call__(self, value): + ''' + Checks whether a value is list of given validation_function. + Returns list of validated values or just one valid value in + list if there is only one element in given CSV string. + ''' + try: + if isinstance(value, basestring): + if self.separator in value: + seperated_string_values =[item.strip() for item + in value.split(self.separator)] + values = [self.validation_function(item) for item + in seperated_string_values] + else: + values = [self.validation_function(value)] + return values + else: + raise ValueError + except (Invalid, ValueError) as e: + raise Invalid(self.msg or + ('<{0}> is not valid set of <{1}>, {2}'.format( + value, + self.value_type, + e)))