Kaydet (Commit) 3738e4ac authored tarafından Simon Charette's avatar Simon Charette

Fixed #25841 -- Handled base array fields validation errors with params.

Thanks to Trac alias benzid-wael for the report.
üst 86eccdc8
...@@ -7,8 +7,9 @@ from django.core import checks, exceptions ...@@ -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): ...@@ -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): ...@@ -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):
......
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 ( ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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): ...@@ -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
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): ...@@ -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): ...@@ -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): ...@@ -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).',
])
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment