Fixed #25841 -- Handled base array fields validation errors with params.
Thanks to Trac alias benzid-wael for the report.
This commit is contained in:
parent
86eccdc8b6
commit
3738e4ac46
|
@ -7,8 +7,9 @@ from django.core import checks, exceptions
|
||||||
from django.db.models import Field, IntegerField, Transform
|
from django.db.models import Field, IntegerField, Transform
|
||||||
from django.db.models.lookups import Exact, In
|
from django.db.models.lookups import Exact, In
|
||||||
from django.utils import six
|
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
|
from .utils import AttributeSetter
|
||||||
|
|
||||||
__all__ = ['ArrayField']
|
__all__ = ['ArrayField']
|
||||||
|
@ -133,14 +134,15 @@ class ArrayField(Field):
|
||||||
|
|
||||||
def validate(self, value, model_instance):
|
def validate(self, value, model_instance):
|
||||||
super(ArrayField, self).validate(value, model_instance)
|
super(ArrayField, self).validate(value, model_instance)
|
||||||
for i, part in enumerate(value):
|
for index, part in enumerate(value):
|
||||||
try:
|
try:
|
||||||
self.base_field.validate(part, model_instance)
|
self.base_field.validate(part, model_instance)
|
||||||
except exceptions.ValidationError as e:
|
except exceptions.ValidationError as error:
|
||||||
raise exceptions.ValidationError(
|
raise prefix_validation_error(
|
||||||
string_concat(self.error_messages['item_invalid'], e.message),
|
error,
|
||||||
|
prefix=self.error_messages['item_invalid'],
|
||||||
code='item_invalid',
|
code='item_invalid',
|
||||||
params={'nth': i},
|
params={'nth': index},
|
||||||
)
|
)
|
||||||
if isinstance(self.base_field, ArrayField):
|
if isinstance(self.base_field, ArrayField):
|
||||||
if len({len(i) for i in value}) > 1:
|
if len({len(i) for i in value}) > 1:
|
||||||
|
@ -151,14 +153,15 @@ class ArrayField(Field):
|
||||||
|
|
||||||
def run_validators(self, value):
|
def run_validators(self, value):
|
||||||
super(ArrayField, self).run_validators(value)
|
super(ArrayField, self).run_validators(value)
|
||||||
for i, part in enumerate(value):
|
for index, part in enumerate(value):
|
||||||
try:
|
try:
|
||||||
self.base_field.run_validators(part)
|
self.base_field.run_validators(part)
|
||||||
except exceptions.ValidationError as e:
|
except exceptions.ValidationError as error:
|
||||||
raise exceptions.ValidationError(
|
raise prefix_validation_error(
|
||||||
string_concat(self.error_messages['item_invalid'], ' '.join(e.messages)),
|
error,
|
||||||
|
prefix=self.error_messages['item_invalid'],
|
||||||
code='item_invalid',
|
code='item_invalid',
|
||||||
params={'nth': i},
|
params={'nth': index},
|
||||||
)
|
)
|
||||||
|
|
||||||
def formfield(self, **kwargs):
|
def formfield(self, **kwargs):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import copy
|
import copy
|
||||||
|
from itertools import chain
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.postgres.validators import (
|
from django.contrib.postgres.validators import (
|
||||||
|
@ -7,7 +8,9 @@ from django.contrib.postgres.validators import (
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.safestring import mark_safe
|
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):
|
class SimpleArrayField(forms.CharField):
|
||||||
|
@ -38,16 +41,16 @@ class SimpleArrayField(forms.CharField):
|
||||||
items = []
|
items = []
|
||||||
errors = []
|
errors = []
|
||||||
values = []
|
values = []
|
||||||
for i, item in enumerate(items):
|
for index, item in enumerate(items):
|
||||||
try:
|
try:
|
||||||
values.append(self.base_field.to_python(item))
|
values.append(self.base_field.to_python(item))
|
||||||
except ValidationError as e:
|
except ValidationError as error:
|
||||||
for error in e.error_list:
|
errors.append(prefix_validation_error(
|
||||||
errors.append(ValidationError(
|
error,
|
||||||
string_concat(self.error_messages['item_invalid'], error.message),
|
prefix=self.error_messages['item_invalid'],
|
||||||
code='item_invalid',
|
code='item_invalid',
|
||||||
params={'nth': i},
|
params={'nth': index},
|
||||||
))
|
))
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
return values
|
return values
|
||||||
|
@ -55,32 +58,32 @@ class SimpleArrayField(forms.CharField):
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
super(SimpleArrayField, self).validate(value)
|
super(SimpleArrayField, self).validate(value)
|
||||||
errors = []
|
errors = []
|
||||||
for i, item in enumerate(value):
|
for index, item in enumerate(value):
|
||||||
try:
|
try:
|
||||||
self.base_field.validate(item)
|
self.base_field.validate(item)
|
||||||
except ValidationError as e:
|
except ValidationError as error:
|
||||||
for error in e.error_list:
|
errors.append(prefix_validation_error(
|
||||||
errors.append(ValidationError(
|
error,
|
||||||
string_concat(self.error_messages['item_invalid'], error.message),
|
prefix=self.error_messages['item_invalid'],
|
||||||
code='item_invalid',
|
code='item_invalid',
|
||||||
params={'nth': i},
|
params={'nth': index},
|
||||||
))
|
))
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
def run_validators(self, value):
|
def run_validators(self, value):
|
||||||
super(SimpleArrayField, self).run_validators(value)
|
super(SimpleArrayField, self).run_validators(value)
|
||||||
errors = []
|
errors = []
|
||||||
for i, item in enumerate(value):
|
for index, item in enumerate(value):
|
||||||
try:
|
try:
|
||||||
self.base_field.run_validators(item)
|
self.base_field.run_validators(item)
|
||||||
except ValidationError as e:
|
except ValidationError as error:
|
||||||
for error in e.error_list:
|
errors.append(prefix_validation_error(
|
||||||
errors.append(ValidationError(
|
error,
|
||||||
string_concat(self.error_messages['item_invalid'], error.message),
|
prefix=self.error_messages['item_invalid'],
|
||||||
code='item_invalid',
|
code='item_invalid',
|
||||||
params={'nth': i},
|
params={'nth': index},
|
||||||
))
|
))
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(errors)
|
||||||
|
|
||||||
|
@ -159,18 +162,20 @@ class SplitArrayField(forms.Field):
|
||||||
if not any(value) and self.required:
|
if not any(value) and self.required:
|
||||||
raise ValidationError(self.error_messages['required'])
|
raise ValidationError(self.error_messages['required'])
|
||||||
max_size = max(self.size, len(value))
|
max_size = max(self.size, len(value))
|
||||||
for i in range(max_size):
|
for index in range(max_size):
|
||||||
item = value[i]
|
item = value[index]
|
||||||
try:
|
try:
|
||||||
cleaned_data.append(self.base_field.clean(item))
|
cleaned_data.append(self.base_field.clean(item))
|
||||||
errors.append(None)
|
|
||||||
except ValidationError as error:
|
except ValidationError as error:
|
||||||
errors.append(ValidationError(
|
errors.append(prefix_validation_error(
|
||||||
string_concat(self.error_messages['item_invalid'], ' '.join(error.messages)),
|
error,
|
||||||
|
self.error_messages['item_invalid'],
|
||||||
code='item_invalid',
|
code='item_invalid',
|
||||||
params={'nth': i},
|
params={'nth': index},
|
||||||
))
|
))
|
||||||
cleaned_data.append(None)
|
cleaned_data.append(None)
|
||||||
|
else:
|
||||||
|
errors.append(None)
|
||||||
if self.remove_trailing_nulls:
|
if self.remove_trailing_nulls:
|
||||||
null_index = None
|
null_index = None
|
||||||
for i, value in reversed(list(enumerate(cleaned_data))):
|
for i, value in reversed(list(enumerate(cleaned_data))):
|
||||||
|
@ -183,5 +188,5 @@ class SplitArrayField(forms.Field):
|
||||||
errors = errors[:null_index]
|
errors = errors[:null_index]
|
||||||
errors = list(filter(None, errors))
|
errors = list(filter(None, errors))
|
||||||
if errors:
|
if errors:
|
||||||
raise ValidationError(errors)
|
raise ValidationError(list(chain.from_iterable(errors)))
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
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
|
||||||
|
])
|
|
@ -507,16 +507,32 @@ class TestValidation(PostgreSQLTestCase):
|
||||||
self.assertEqual(cm.exception.code, 'nested_array_mismatch')
|
self.assertEqual(cm.exception.code, 'nested_array_mismatch')
|
||||||
self.assertEqual(cm.exception.messages[0], 'Nested arrays must have the same length.')
|
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):
|
def test_with_validators(self):
|
||||||
field = ArrayField(models.IntegerField(validators=[validators.MinValueValidator(1)]))
|
field = ArrayField(models.IntegerField(validators=[validators.MinValueValidator(1)]))
|
||||||
field.clean([1, 2], None)
|
field.clean([1, 2], None)
|
||||||
with self.assertRaises(exceptions.ValidationError) as cm:
|
with self.assertRaises(exceptions.ValidationError) as cm:
|
||||||
field.clean([0], None)
|
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(
|
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.'
|
'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):
|
class TestSimpleFormField(PostgreSQLTestCase):
|
||||||
|
@ -538,6 +554,27 @@ class TestSimpleFormField(PostgreSQLTestCase):
|
||||||
field.clean('a,b,')
|
field.clean('a,b,')
|
||||||
self.assertEqual(cm.exception.messages[0], 'Item 2 in the array did not validate: This field is required.')
|
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):
|
def test_validators_fail(self):
|
||||||
field = SimpleArrayField(forms.RegexField('[a-e]{2}'))
|
field = SimpleArrayField(forms.RegexField('[a-e]{2}'))
|
||||||
with self.assertRaises(exceptions.ValidationError) as cm:
|
with self.assertRaises(exceptions.ValidationError) as cm:
|
||||||
|
@ -648,3 +685,12 @@ class TestSplitFormField(PostgreSQLTestCase):
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</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…
Reference in New Issue