diff --git a/django/core/validators.py b/django/core/validators.py index bb8224f728..a62f04e1a3 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -254,11 +254,14 @@ def ip_address_validators(protocol, unpack_ipv4): raise ValueError("The protocol '%s' is unknown. Supported: %s" % (protocol, list(ip_address_validator_map))) -comma_separated_int_list_re = re.compile('^[\d,]+$') -validate_comma_separated_integer_list = RegexValidator( - comma_separated_int_list_re, - _('Enter only digits separated by commas.'), - 'invalid' + +def int_list_validator(sep=',', message=None, code='invalid'): + regexp = re.compile('^\d+(?:%s\d+)*$' % re.escape(sep)) + return RegexValidator(regexp, message=message, code=code) + + +validate_comma_separated_integer_list = int_list_validator( + message=_('Enter only digits separated by commas.'), ) diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index cf078bee21..c297209b03 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -214,6 +214,16 @@ to, or in lieu of custom ``field.clean()`` methods. A :class:`RegexValidator` instance that ensures a value is a comma-separated list of integers. +``int_list_validator`` +---------------------- + +.. function:: int_list_validator(sep=',', message=None, code='invalid') + + .. versionadded:: 1.9 + + Returns a :class:`RegexValidator` instance that ensures a string + consists of integers separated by ``sep``. + ``MaxValueValidator`` --------------------- diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index 3626cd0a40..d33028f134 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -213,7 +213,8 @@ URLs Validators ^^^^^^^^^^ -* ... +* Added :func:`django.core.validators.int_list_validator` to generate + validators of strings containing integers separated with a custom character. Backwards incompatible changes in 1.9 ===================================== @@ -321,6 +322,9 @@ Miscellaneous that value to construct absolute URLs have been moved to CSS for easier customization. +* ``CommaSeparatedIntegerField`` validation has been refined to forbid values + like ``','``, ``',1'``, and ``'1,,2'``. + .. _deprecated-features-1.9: Features deprecated in 1.9 diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 1e86c4d7ba..377d96e64b 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -2136,24 +2136,28 @@ class ModelOtherFieldTests(TestCase): model = CommaSeparatedInteger fields = '__all__' + f = CommaSeparatedIntegerForm({'field': '1'}) + self.assertTrue(f.is_valid()) + self.assertEqual(f.cleaned_data, {'field': '1'}) + f = CommaSeparatedIntegerForm({'field': '12'}) + self.assertTrue(f.is_valid()) + self.assertEqual(f.cleaned_data, {'field': '12'}) f = CommaSeparatedIntegerForm({'field': '1,2,3'}) self.assertTrue(f.is_valid()) self.assertEqual(f.cleaned_data, {'field': '1,2,3'}) + f = CommaSeparatedIntegerForm({'field': '10,32'}) + self.assertTrue(f.is_valid()) + self.assertEqual(f.cleaned_data, {'field': '10,32'}) f = CommaSeparatedIntegerForm({'field': '1a,2'}) self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': ',,,,'}) - self.assertTrue(f.is_valid()) - self.assertEqual(f.cleaned_data, {'field': ',,,,'}) + self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': '1.2'}) self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': '1,a,2'}) self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) f = CommaSeparatedIntegerForm({'field': '1,,2'}) - self.assertTrue(f.is_valid()) - self.assertEqual(f.cleaned_data, {'field': '1,,2'}) - f = CommaSeparatedIntegerForm({'field': '1'}) - self.assertTrue(f.is_valid()) - self.assertEqual(f.cleaned_data, {'field': '1'}) + self.assertEqual(f.errors, {'field': ['Enter only digits separated by commas.']}) def test_url_on_modelform(self): "Check basic URL field validation on model forms" diff --git a/tests/validators/tests.py b/tests/validators/tests.py index 869c6d91cb..4be4b9de4f 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -12,9 +12,9 @@ from django.core.exceptions import ValidationError from django.core.validators import ( BaseValidator, EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator, MinValueValidator, RegexValidator, URLValidator, - validate_comma_separated_integer_list, validate_email, validate_integer, - validate_ipv4_address, validate_ipv6_address, validate_ipv46_address, - validate_slug, + int_list_validator, validate_comma_separated_integer_list, validate_email, + validate_integer, validate_ipv4_address, validate_ipv6_address, + validate_ipv46_address, validate_slug, ) from django.test import SimpleTestCase from django.test.utils import str_prefix @@ -120,12 +120,23 @@ TEST_DATA = [ (validate_ipv46_address, '12345::', ValidationError), (validate_comma_separated_integer_list, '1', None), + (validate_comma_separated_integer_list, '12', None), + (validate_comma_separated_integer_list, '1,2', None), (validate_comma_separated_integer_list, '1,2,3', None), - (validate_comma_separated_integer_list, '1,2,3,', None), + (validate_comma_separated_integer_list, '10,32', None), (validate_comma_separated_integer_list, '', ValidationError), + (validate_comma_separated_integer_list, 'a', ValidationError), (validate_comma_separated_integer_list, 'a,b,c', ValidationError), (validate_comma_separated_integer_list, '1, 2, 3', ValidationError), + (validate_comma_separated_integer_list, ',', ValidationError), + (validate_comma_separated_integer_list, '1,2,3,', ValidationError), + (validate_comma_separated_integer_list, '1,2,', ValidationError), + (validate_comma_separated_integer_list, ',1', ValidationError), + (validate_comma_separated_integer_list, '1,,2', ValidationError), + + (int_list_validator(sep='.'), '1.2.3', None), + (int_list_validator(sep='.'), '1,2,3', ValidationError), (MaxValueValidator(10), 10, None), (MaxValueValidator(10), -10, None),