Loading django/contrib/postgres/fields/array.py +14 −11 Original line number Diff line number Diff line Loading @@ -7,8 +7,9 @@ from django.core import checks, exceptions from django.db.models import Field, IntegerField, Transform from django.db.models.lookups import Exact, In from django.utils import six from django.utils.translation import string_concat, ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _ from ..utils import prefix_validation_error from .utils import AttributeSetter __all__ = ['ArrayField'] Loading Loading @@ -133,14 +134,15 @@ class ArrayField(Field): def validate(self, value, model_instance): super(ArrayField, self).validate(value, model_instance) for i, part in enumerate(value): for index, part in enumerate(value): try: self.base_field.validate(part, model_instance) except exceptions.ValidationError as e: raise exceptions.ValidationError( string_concat(self.error_messages['item_invalid'], e.message), except exceptions.ValidationError as error: raise prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, ) if isinstance(self.base_field, ArrayField): if len({len(i) for i in value}) > 1: Loading @@ -151,14 +153,15 @@ class ArrayField(Field): def run_validators(self, value): super(ArrayField, self).run_validators(value) for i, part in enumerate(value): for index, part in enumerate(value): try: self.base_field.run_validators(part) except exceptions.ValidationError as e: raise exceptions.ValidationError( string_concat(self.error_messages['item_invalid'], ' '.join(e.messages)), except exceptions.ValidationError as error: raise prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, ) def formfield(self, **kwargs): Loading django/contrib/postgres/forms/array.py +37 −32 Original line number Diff line number Diff line import copy from itertools import chain from django import forms from django.contrib.postgres.validators import ( Loading @@ -7,7 +8,9 @@ from django.contrib.postgres.validators import ( from django.core.exceptions import ValidationError from django.utils import six from django.utils.safestring import mark_safe from django.utils.translation import string_concat, ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _ from ..utils import prefix_validation_error class SimpleArrayField(forms.CharField): Loading Loading @@ -38,15 +41,15 @@ class SimpleArrayField(forms.CharField): items = [] errors = [] values = [] for i, item in enumerate(items): for index, item in enumerate(items): try: values.append(self.base_field.to_python(item)) except ValidationError as e: for error in e.error_list: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], error.message), except ValidationError as error: errors.append(prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) if errors: raise ValidationError(errors) Loading @@ -55,15 +58,15 @@ class SimpleArrayField(forms.CharField): def validate(self, value): super(SimpleArrayField, self).validate(value) errors = [] for i, item in enumerate(value): for index, item in enumerate(value): try: self.base_field.validate(item) except ValidationError as e: for error in e.error_list: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], error.message), except ValidationError as error: errors.append(prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) if errors: raise ValidationError(errors) Loading @@ -71,15 +74,15 @@ class SimpleArrayField(forms.CharField): def run_validators(self, value): super(SimpleArrayField, self).run_validators(value) errors = [] for i, item in enumerate(value): for index, item in enumerate(value): try: self.base_field.run_validators(item) except ValidationError as e: for error in e.error_list: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], error.message), except ValidationError as error: errors.append(prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) if errors: raise ValidationError(errors) Loading Loading @@ -159,18 +162,20 @@ class SplitArrayField(forms.Field): if not any(value) and self.required: raise ValidationError(self.error_messages['required']) max_size = max(self.size, len(value)) for i in range(max_size): item = value[i] for index in range(max_size): item = value[index] try: cleaned_data.append(self.base_field.clean(item)) errors.append(None) except ValidationError as error: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], ' '.join(error.messages)), errors.append(prefix_validation_error( error, self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) cleaned_data.append(None) else: errors.append(None) if self.remove_trailing_nulls: null_index = None for i, value in reversed(list(enumerate(cleaned_data))): Loading @@ -183,5 +188,5 @@ class SplitArrayField(forms.Field): errors = errors[:null_index] errors = list(filter(None, errors)) if errors: raise ValidationError(errors) raise ValidationError(list(chain.from_iterable(errors))) return cleaned_data django/contrib/postgres/utils.py 0 → 100644 +30 −0 Original line number Diff line number Diff line from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.utils.functional import SimpleLazyObject from django.utils.translation import string_concat def prefix_validation_error(error, prefix, code, params): """ Prefix a validation error message while maintaining the existing validation data structure. """ if error.error_list == [error]: error_params = error.params or {} return ValidationError( # We can't simply concatenate messages since they might require # their associated parameters to be expressed correctly which # is not something `string_concat` does. For example, proxied # ungettext calls require a count parameter and are converted # to an empty string if they are missing it. message=string_concat( SimpleLazyObject(lambda: prefix % params), SimpleLazyObject(lambda: error.message % error_params), ), code=code, params=dict(error_params, **params), ) return ValidationError([ prefix_validation_error(e, prefix, code, params) for e in error.error_list ]) tests/postgres_tests/test_array.py +48 −2 Original line number Diff line number Diff line Loading @@ -507,16 +507,32 @@ class TestValidation(PostgreSQLTestCase): self.assertEqual(cm.exception.code, 'nested_array_mismatch') self.assertEqual(cm.exception.messages[0], 'Nested arrays must have the same length.') def test_with_base_field_error_params(self): field = ArrayField(models.CharField(max_length=2)) with self.assertRaises(exceptions.ValidationError) as cm: field.clean(['abc'], None) self.assertEqual(len(cm.exception.error_list), 1) exception = cm.exception.error_list[0] self.assertEqual( exception.message, 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).' ) self.assertEqual(exception.code, 'item_invalid') self.assertEqual(exception.params, {'nth': 0, 'value': 'abc', 'limit_value': 2, 'show_value': 3}) def test_with_validators(self): field = ArrayField(models.IntegerField(validators=[validators.MinValueValidator(1)])) field.clean([1, 2], None) with self.assertRaises(exceptions.ValidationError) as cm: field.clean([0], None) self.assertEqual(cm.exception.code, 'item_invalid') self.assertEqual(len(cm.exception.error_list), 1) exception = cm.exception.error_list[0] self.assertEqual( cm.exception.messages[0], exception.message, 'Item 0 in the array did not validate: Ensure this value is greater than or equal to 1.' ) self.assertEqual(exception.code, 'item_invalid') self.assertEqual(exception.params, {'nth': 0, 'value': 0, 'limit_value': 1, 'show_value': 0}) class TestSimpleFormField(PostgreSQLTestCase): Loading @@ -538,6 +554,27 @@ class TestSimpleFormField(PostgreSQLTestCase): field.clean('a,b,') self.assertEqual(cm.exception.messages[0], 'Item 2 in the array did not validate: This field is required.') def test_validate_fail_base_field_error_params(self): field = SimpleArrayField(forms.CharField(max_length=2)) with self.assertRaises(exceptions.ValidationError) as cm: field.clean('abc,c,defg') errors = cm.exception.error_list self.assertEqual(len(errors), 2) first_error = errors[0] self.assertEqual( first_error.message, 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).' ) self.assertEqual(first_error.code, 'item_invalid') self.assertEqual(first_error.params, {'nth': 0, 'value': 'abc', 'limit_value': 2, 'show_value': 3}) second_error = errors[1] self.assertEqual( second_error.message, 'Item 2 in the array did not validate: Ensure this value has at most 2 characters (it has 4).' ) self.assertEqual(second_error.code, 'item_invalid') self.assertEqual(second_error.params, {'nth': 2, 'value': 'defg', 'limit_value': 2, 'show_value': 4}) def test_validators_fail(self): field = SimpleArrayField(forms.RegexField('[a-e]{2}')) with self.assertRaises(exceptions.ValidationError) as cm: Loading Loading @@ -648,3 +685,12 @@ class TestSplitFormField(PostgreSQLTestCase): </td> </tr> ''') def test_invalid_char_length(self): field = SplitArrayField(forms.CharField(max_length=2), size=3) with self.assertRaises(exceptions.ValidationError) as cm: field.clean(['abc', 'c', 'defg']) self.assertEqual(cm.exception.messages, [ 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).', 'Item 2 in the array did not validate: Ensure this value has at most 2 characters (it has 4).', ]) Loading
django/contrib/postgres/fields/array.py +14 −11 Original line number Diff line number Diff line Loading @@ -7,8 +7,9 @@ from django.core import checks, exceptions from django.db.models import Field, IntegerField, Transform from django.db.models.lookups import Exact, In from django.utils import six from django.utils.translation import string_concat, ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _ from ..utils import prefix_validation_error from .utils import AttributeSetter __all__ = ['ArrayField'] Loading Loading @@ -133,14 +134,15 @@ class ArrayField(Field): def validate(self, value, model_instance): super(ArrayField, self).validate(value, model_instance) for i, part in enumerate(value): for index, part in enumerate(value): try: self.base_field.validate(part, model_instance) except exceptions.ValidationError as e: raise exceptions.ValidationError( string_concat(self.error_messages['item_invalid'], e.message), except exceptions.ValidationError as error: raise prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, ) if isinstance(self.base_field, ArrayField): if len({len(i) for i in value}) > 1: Loading @@ -151,14 +153,15 @@ class ArrayField(Field): def run_validators(self, value): super(ArrayField, self).run_validators(value) for i, part in enumerate(value): for index, part in enumerate(value): try: self.base_field.run_validators(part) except exceptions.ValidationError as e: raise exceptions.ValidationError( string_concat(self.error_messages['item_invalid'], ' '.join(e.messages)), except exceptions.ValidationError as error: raise prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, ) def formfield(self, **kwargs): Loading
django/contrib/postgres/forms/array.py +37 −32 Original line number Diff line number Diff line import copy from itertools import chain from django import forms from django.contrib.postgres.validators import ( Loading @@ -7,7 +8,9 @@ from django.contrib.postgres.validators import ( from django.core.exceptions import ValidationError from django.utils import six from django.utils.safestring import mark_safe from django.utils.translation import string_concat, ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _ from ..utils import prefix_validation_error class SimpleArrayField(forms.CharField): Loading Loading @@ -38,15 +41,15 @@ class SimpleArrayField(forms.CharField): items = [] errors = [] values = [] for i, item in enumerate(items): for index, item in enumerate(items): try: values.append(self.base_field.to_python(item)) except ValidationError as e: for error in e.error_list: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], error.message), except ValidationError as error: errors.append(prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) if errors: raise ValidationError(errors) Loading @@ -55,15 +58,15 @@ class SimpleArrayField(forms.CharField): def validate(self, value): super(SimpleArrayField, self).validate(value) errors = [] for i, item in enumerate(value): for index, item in enumerate(value): try: self.base_field.validate(item) except ValidationError as e: for error in e.error_list: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], error.message), except ValidationError as error: errors.append(prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) if errors: raise ValidationError(errors) Loading @@ -71,15 +74,15 @@ class SimpleArrayField(forms.CharField): def run_validators(self, value): super(SimpleArrayField, self).run_validators(value) errors = [] for i, item in enumerate(value): for index, item in enumerate(value): try: self.base_field.run_validators(item) except ValidationError as e: for error in e.error_list: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], error.message), except ValidationError as error: errors.append(prefix_validation_error( error, prefix=self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) if errors: raise ValidationError(errors) Loading Loading @@ -159,18 +162,20 @@ class SplitArrayField(forms.Field): if not any(value) and self.required: raise ValidationError(self.error_messages['required']) max_size = max(self.size, len(value)) for i in range(max_size): item = value[i] for index in range(max_size): item = value[index] try: cleaned_data.append(self.base_field.clean(item)) errors.append(None) except ValidationError as error: errors.append(ValidationError( string_concat(self.error_messages['item_invalid'], ' '.join(error.messages)), errors.append(prefix_validation_error( error, self.error_messages['item_invalid'], code='item_invalid', params={'nth': i}, params={'nth': index}, )) cleaned_data.append(None) else: errors.append(None) if self.remove_trailing_nulls: null_index = None for i, value in reversed(list(enumerate(cleaned_data))): Loading @@ -183,5 +188,5 @@ class SplitArrayField(forms.Field): errors = errors[:null_index] errors = list(filter(None, errors)) if errors: raise ValidationError(errors) raise ValidationError(list(chain.from_iterable(errors))) return cleaned_data
django/contrib/postgres/utils.py 0 → 100644 +30 −0 Original line number Diff line number Diff line from __future__ import unicode_literals from django.core.exceptions import ValidationError from django.utils.functional import SimpleLazyObject from django.utils.translation import string_concat def prefix_validation_error(error, prefix, code, params): """ Prefix a validation error message while maintaining the existing validation data structure. """ if error.error_list == [error]: error_params = error.params or {} return ValidationError( # We can't simply concatenate messages since they might require # their associated parameters to be expressed correctly which # is not something `string_concat` does. For example, proxied # ungettext calls require a count parameter and are converted # to an empty string if they are missing it. message=string_concat( SimpleLazyObject(lambda: prefix % params), SimpleLazyObject(lambda: error.message % error_params), ), code=code, params=dict(error_params, **params), ) return ValidationError([ prefix_validation_error(e, prefix, code, params) for e in error.error_list ])
tests/postgres_tests/test_array.py +48 −2 Original line number Diff line number Diff line Loading @@ -507,16 +507,32 @@ class TestValidation(PostgreSQLTestCase): self.assertEqual(cm.exception.code, 'nested_array_mismatch') self.assertEqual(cm.exception.messages[0], 'Nested arrays must have the same length.') def test_with_base_field_error_params(self): field = ArrayField(models.CharField(max_length=2)) with self.assertRaises(exceptions.ValidationError) as cm: field.clean(['abc'], None) self.assertEqual(len(cm.exception.error_list), 1) exception = cm.exception.error_list[0] self.assertEqual( exception.message, 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).' ) self.assertEqual(exception.code, 'item_invalid') self.assertEqual(exception.params, {'nth': 0, 'value': 'abc', 'limit_value': 2, 'show_value': 3}) def test_with_validators(self): field = ArrayField(models.IntegerField(validators=[validators.MinValueValidator(1)])) field.clean([1, 2], None) with self.assertRaises(exceptions.ValidationError) as cm: field.clean([0], None) self.assertEqual(cm.exception.code, 'item_invalid') self.assertEqual(len(cm.exception.error_list), 1) exception = cm.exception.error_list[0] self.assertEqual( cm.exception.messages[0], exception.message, 'Item 0 in the array did not validate: Ensure this value is greater than or equal to 1.' ) self.assertEqual(exception.code, 'item_invalid') self.assertEqual(exception.params, {'nth': 0, 'value': 0, 'limit_value': 1, 'show_value': 0}) class TestSimpleFormField(PostgreSQLTestCase): Loading @@ -538,6 +554,27 @@ class TestSimpleFormField(PostgreSQLTestCase): field.clean('a,b,') self.assertEqual(cm.exception.messages[0], 'Item 2 in the array did not validate: This field is required.') def test_validate_fail_base_field_error_params(self): field = SimpleArrayField(forms.CharField(max_length=2)) with self.assertRaises(exceptions.ValidationError) as cm: field.clean('abc,c,defg') errors = cm.exception.error_list self.assertEqual(len(errors), 2) first_error = errors[0] self.assertEqual( first_error.message, 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).' ) self.assertEqual(first_error.code, 'item_invalid') self.assertEqual(first_error.params, {'nth': 0, 'value': 'abc', 'limit_value': 2, 'show_value': 3}) second_error = errors[1] self.assertEqual( second_error.message, 'Item 2 in the array did not validate: Ensure this value has at most 2 characters (it has 4).' ) self.assertEqual(second_error.code, 'item_invalid') self.assertEqual(second_error.params, {'nth': 2, 'value': 'defg', 'limit_value': 2, 'show_value': 4}) def test_validators_fail(self): field = SimpleArrayField(forms.RegexField('[a-e]{2}')) with self.assertRaises(exceptions.ValidationError) as cm: Loading Loading @@ -648,3 +685,12 @@ class TestSplitFormField(PostgreSQLTestCase): </td> </tr> ''') def test_invalid_char_length(self): field = SplitArrayField(forms.CharField(max_length=2), size=3) with self.assertRaises(exceptions.ValidationError) as cm: field.clean(['abc', 'c', 'defg']) self.assertEqual(cm.exception.messages, [ 'Item 0 in the array did not validate: Ensure this value has at most 2 characters (it has 3).', 'Item 2 in the array did not validate: Ensure this value has at most 2 characters (it has 4).', ])