mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Fixed #20199 -- Allow ModelForm fields to override error_messages from model fields
This commit is contained in:
@@ -125,7 +125,7 @@ class Field(object):
|
||||
|
||||
def validate(self, value):
|
||||
if value in self.empty_values and self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
|
||||
def run_validators(self, value):
|
||||
if value in self.empty_values:
|
||||
@@ -246,7 +246,7 @@ class IntegerField(Field):
|
||||
try:
|
||||
value = int(str(value))
|
||||
except (ValueError, TypeError):
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
return value
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
@@ -277,7 +277,7 @@ class FloatField(IntegerField):
|
||||
try:
|
||||
value = float(value)
|
||||
except (ValueError, TypeError):
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
return value
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
@@ -323,7 +323,7 @@ class DecimalField(IntegerField):
|
||||
try:
|
||||
value = Decimal(value)
|
||||
except DecimalException:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
return value
|
||||
|
||||
def validate(self, value):
|
||||
@@ -334,7 +334,7 @@ class DecimalField(IntegerField):
|
||||
# since it is never equal to itself. However, NaN is the only value that
|
||||
# isn't equal to itself, so we can use this to identify NaN
|
||||
if value != value or value == Decimal("Inf") or value == Decimal("-Inf"):
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
sign, digittuple, exponent = value.as_tuple()
|
||||
decimals = abs(exponent)
|
||||
# digittuple doesn't include any leading zeros.
|
||||
@@ -348,15 +348,24 @@ class DecimalField(IntegerField):
|
||||
whole_digits = digits - decimals
|
||||
|
||||
if self.max_digits is not None and digits > self.max_digits:
|
||||
raise ValidationError(self.error_messages['max_digits'] % {
|
||||
'max': self.max_digits})
|
||||
raise ValidationError(
|
||||
self.error_messages['max_digits'],
|
||||
code='max_digits',
|
||||
params={'max': self.max_digits},
|
||||
)
|
||||
if self.decimal_places is not None and decimals > self.decimal_places:
|
||||
raise ValidationError(self.error_messages['max_decimal_places'] % {
|
||||
'max': self.decimal_places})
|
||||
raise ValidationError(
|
||||
self.error_messages['max_decimal_places'],
|
||||
code='max_decimal_places',
|
||||
params={'max': self.decimal_places},
|
||||
)
|
||||
if (self.max_digits is not None and self.decimal_places is not None
|
||||
and whole_digits > (self.max_digits - self.decimal_places)):
|
||||
raise ValidationError(self.error_messages['max_whole_digits'] % {
|
||||
'max': (self.max_digits - self.decimal_places)})
|
||||
raise ValidationError(
|
||||
self.error_messages['max_whole_digits'],
|
||||
code='max_whole_digits',
|
||||
params={'max': (self.max_digits - self.decimal_places)},
|
||||
)
|
||||
return value
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
@@ -391,7 +400,7 @@ class BaseTemporalField(Field):
|
||||
return self.strptime(value, format)
|
||||
except (ValueError, TypeError):
|
||||
continue
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
|
||||
def strptime(self, value, format):
|
||||
raise NotImplementedError('Subclasses must define this method.')
|
||||
@@ -471,7 +480,7 @@ class DateTimeField(BaseTemporalField):
|
||||
# Input comes from a SplitDateTimeWidget, for example. So, it's two
|
||||
# components: date and time.
|
||||
if len(value) != 2:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
if value[0] in self.empty_values and value[1] in self.empty_values:
|
||||
return None
|
||||
value = '%s %s' % tuple(value)
|
||||
@@ -548,22 +557,22 @@ class FileField(Field):
|
||||
file_name = data.name
|
||||
file_size = data.size
|
||||
except AttributeError:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
|
||||
if self.max_length is not None and len(file_name) > self.max_length:
|
||||
error_values = {'max': self.max_length, 'length': len(file_name)}
|
||||
raise ValidationError(self.error_messages['max_length'] % error_values)
|
||||
params = {'max': self.max_length, 'length': len(file_name)}
|
||||
raise ValidationError(self.error_messages['max_length'], code='max_length', params=params)
|
||||
if not file_name:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
if not self.allow_empty_file and not file_size:
|
||||
raise ValidationError(self.error_messages['empty'])
|
||||
raise ValidationError(self.error_messages['empty'], code='empty')
|
||||
|
||||
return data
|
||||
|
||||
def clean(self, data, initial=None):
|
||||
# If the widget got contradictory inputs, we raise a validation error
|
||||
if data is FILE_INPUT_CONTRADICTION:
|
||||
raise ValidationError(self.error_messages['contradiction'])
|
||||
raise ValidationError(self.error_messages['contradiction'], code='contradiction')
|
||||
# False means the field value should be cleared; further validation is
|
||||
# not needed.
|
||||
if data is False:
|
||||
@@ -623,7 +632,10 @@ class ImageField(FileField):
|
||||
Image.open(file).verify()
|
||||
except Exception:
|
||||
# Pillow (or PIL) doesn't recognize it as an image.
|
||||
six.reraise(ValidationError, ValidationError(self.error_messages['invalid_image']), sys.exc_info()[2])
|
||||
six.reraise(ValidationError, ValidationError(
|
||||
self.error_messages['invalid_image'],
|
||||
code='invalid_image',
|
||||
), sys.exc_info()[2])
|
||||
if hasattr(f, 'seek') and callable(f.seek):
|
||||
f.seek(0)
|
||||
return f
|
||||
@@ -648,7 +660,7 @@ class URLField(CharField):
|
||||
except ValueError:
|
||||
# urlparse.urlsplit can raise a ValueError with some
|
||||
# misformatted URLs.
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
|
||||
value = super(URLField, self).to_python(value)
|
||||
if value:
|
||||
@@ -692,7 +704,7 @@ class BooleanField(Field):
|
||||
|
||||
def validate(self, value):
|
||||
if not value and self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
# Sometimes data or initial could be None or '' which should be the
|
||||
@@ -776,7 +788,11 @@ class ChoiceField(Field):
|
||||
"""
|
||||
super(ChoiceField, self).validate(value)
|
||||
if value and not self.valid_value(value):
|
||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_choice'],
|
||||
code='invalid_choice',
|
||||
params={'value': value},
|
||||
)
|
||||
|
||||
def valid_value(self, value):
|
||||
"Check to see if the provided value is a valid choice"
|
||||
@@ -810,7 +826,11 @@ class TypedChoiceField(ChoiceField):
|
||||
try:
|
||||
value = self.coerce(value)
|
||||
except (ValueError, TypeError, ValidationError):
|
||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_choice'],
|
||||
code='invalid_choice',
|
||||
params={'value': value},
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
@@ -826,7 +846,7 @@ class MultipleChoiceField(ChoiceField):
|
||||
if not value:
|
||||
return []
|
||||
elif not isinstance(value, (list, tuple)):
|
||||
raise ValidationError(self.error_messages['invalid_list'])
|
||||
raise ValidationError(self.error_messages['invalid_list'], code='invalid_list')
|
||||
return [smart_text(val) for val in value]
|
||||
|
||||
def validate(self, value):
|
||||
@@ -834,11 +854,15 @@ class MultipleChoiceField(ChoiceField):
|
||||
Validates that the input is a list or tuple.
|
||||
"""
|
||||
if self.required and not value:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
# Validate that each value in the value list is in self.choices.
|
||||
for val in value:
|
||||
if not self.valid_value(val):
|
||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_choice'],
|
||||
code='invalid_choice',
|
||||
params={'value': val},
|
||||
)
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
if initial is None:
|
||||
@@ -871,14 +895,18 @@ class TypedMultipleChoiceField(MultipleChoiceField):
|
||||
try:
|
||||
new_value.append(self.coerce(choice))
|
||||
except (ValueError, TypeError, ValidationError):
|
||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': choice})
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_choice'],
|
||||
code='invalid_choice',
|
||||
params={'value': choice},
|
||||
)
|
||||
return new_value
|
||||
|
||||
def validate(self, value):
|
||||
if value != self.empty_value:
|
||||
super(TypedMultipleChoiceField, self).validate(value)
|
||||
elif self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
|
||||
|
||||
class ComboField(Field):
|
||||
@@ -952,18 +980,18 @@ class MultiValueField(Field):
|
||||
if not value or isinstance(value, (list, tuple)):
|
||||
if not value or not [v for v in value if v not in self.empty_values]:
|
||||
if self.required:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
else:
|
||||
return self.compress([])
|
||||
else:
|
||||
raise ValidationError(self.error_messages['invalid'])
|
||||
raise ValidationError(self.error_messages['invalid'], code='invalid')
|
||||
for i, field in enumerate(self.fields):
|
||||
try:
|
||||
field_value = value[i]
|
||||
except IndexError:
|
||||
field_value = None
|
||||
if self.required and field_value in self.empty_values:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
try:
|
||||
clean_data.append(field.clean(field_value))
|
||||
except ValidationError as e:
|
||||
@@ -1078,9 +1106,9 @@ class SplitDateTimeField(MultiValueField):
|
||||
# Raise a validation error if time or date is empty
|
||||
# (possible if SplitDateTimeField has required=False).
|
||||
if data_list[0] in self.empty_values:
|
||||
raise ValidationError(self.error_messages['invalid_date'])
|
||||
raise ValidationError(self.error_messages['invalid_date'], code='invalid_date')
|
||||
if data_list[1] in self.empty_values:
|
||||
raise ValidationError(self.error_messages['invalid_time'])
|
||||
raise ValidationError(self.error_messages['invalid_time'], code='invalid_time')
|
||||
result = datetime.datetime.combine(*data_list)
|
||||
return from_current_timezone(result)
|
||||
return None
|
||||
|
||||
@@ -85,7 +85,10 @@ class BaseFormSet(object):
|
||||
if self.is_bound:
|
||||
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
|
||||
if not form.is_valid():
|
||||
raise ValidationError('ManagementForm data is missing or has been tampered with')
|
||||
raise ValidationError(
|
||||
_('ManagementForm data is missing or has been tampered with'),
|
||||
code='missing_management_form',
|
||||
)
|
||||
else:
|
||||
form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
|
||||
TOTAL_FORM_COUNT: self.total_form_count(),
|
||||
@@ -315,7 +318,9 @@ class BaseFormSet(object):
|
||||
self.management_form.cleaned_data[TOTAL_FORM_COUNT] > self.absolute_max:
|
||||
raise ValidationError(ungettext(
|
||||
"Please submit %d or fewer forms.",
|
||||
"Please submit %d or fewer forms.", self.max_num) % self.max_num)
|
||||
"Please submit %d or fewer forms.", self.max_num) % self.max_num,
|
||||
code='too_many_forms',
|
||||
)
|
||||
# Give self.clean() a chance to do cross-form validation.
|
||||
self.clean()
|
||||
except ValidationError as e:
|
||||
|
||||
@@ -314,7 +314,17 @@ class BaseModelForm(BaseForm):
|
||||
super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
|
||||
error_class, label_suffix, empty_permitted)
|
||||
|
||||
def _update_errors(self, message_dict):
|
||||
def _update_errors(self, errors):
|
||||
for field, messages in errors.error_dict.items():
|
||||
if field not in self.fields:
|
||||
continue
|
||||
field = self.fields[field]
|
||||
for message in messages:
|
||||
if isinstance(message, ValidationError):
|
||||
if message.code in field.error_messages:
|
||||
message.message = field.error_messages[message.code]
|
||||
|
||||
message_dict = errors.message_dict
|
||||
for k, v in message_dict.items():
|
||||
if k != NON_FIELD_ERRORS:
|
||||
self._errors.setdefault(k, self.error_class()).extend(v)
|
||||
@@ -1000,7 +1010,7 @@ class InlineForeignKeyField(Field):
|
||||
else:
|
||||
orig = self.parent_instance.pk
|
||||
if force_text(value) != force_text(orig):
|
||||
raise ValidationError(self.error_messages['invalid_choice'])
|
||||
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
|
||||
return self.parent_instance
|
||||
|
||||
def _has_changed(self, initial, data):
|
||||
@@ -1115,7 +1125,7 @@ class ModelChoiceField(ChoiceField):
|
||||
key = self.to_field_name or 'pk'
|
||||
value = self.queryset.get(**{key: value})
|
||||
except (ValueError, self.queryset.model.DoesNotExist):
|
||||
raise ValidationError(self.error_messages['invalid_choice'])
|
||||
raise ValidationError(self.error_messages['invalid_choice'], code='invalid_choice')
|
||||
return value
|
||||
|
||||
def validate(self, value):
|
||||
@@ -1150,22 +1160,30 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
||||
|
||||
def clean(self, value):
|
||||
if self.required and not value:
|
||||
raise ValidationError(self.error_messages['required'])
|
||||
raise ValidationError(self.error_messages['required'], code='required')
|
||||
elif not self.required and not value:
|
||||
return self.queryset.none()
|
||||
if not isinstance(value, (list, tuple)):
|
||||
raise ValidationError(self.error_messages['list'])
|
||||
raise ValidationError(self.error_messages['list'], code='list')
|
||||
key = self.to_field_name or 'pk'
|
||||
for pk in value:
|
||||
try:
|
||||
self.queryset.filter(**{key: pk})
|
||||
except ValueError:
|
||||
raise ValidationError(self.error_messages['invalid_pk_value'] % {'pk': pk})
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_pk_value'],
|
||||
code='invalid_pk_value',
|
||||
params={'pk': pk},
|
||||
)
|
||||
qs = self.queryset.filter(**{'%s__in' % key: value})
|
||||
pks = set([force_text(getattr(o, key)) for o in qs])
|
||||
for val in value:
|
||||
if force_text(val) not in pks:
|
||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
|
||||
raise ValidationError(
|
||||
self.error_messages['invalid_choice'],
|
||||
code='invalid_choice',
|
||||
params={'value': val},
|
||||
)
|
||||
# Since this overrides the inherited ModelChoiceField.clean
|
||||
# we run custom validators here
|
||||
self.run_validators(value)
|
||||
|
||||
@@ -80,12 +80,17 @@ def from_current_timezone(value):
|
||||
try:
|
||||
return timezone.make_aware(value, current_timezone)
|
||||
except Exception:
|
||||
msg = _(
|
||||
message = _(
|
||||
'%(datetime)s couldn\'t be interpreted '
|
||||
'in time zone %(current_timezone)s; it '
|
||||
'may be ambiguous or it may not exist.') % {'datetime': value, 'current_timezone':
|
||||
current_timezone}
|
||||
six.reraise(ValidationError, ValidationError(msg), sys.exc_info()[2])
|
||||
'may be ambiguous or it may not exist.'
|
||||
)
|
||||
params = {'datetime': value, 'current_timezone': current_timezone}
|
||||
six.reraise(ValidationError, ValidationError(
|
||||
message,
|
||||
code='ambiguous_timezone',
|
||||
params=params,
|
||||
), sys.exc_info()[2])
|
||||
return value
|
||||
|
||||
def to_current_timezone(value):
|
||||
|
||||
Reference in New Issue
Block a user