1
0
mirror of https://github.com/django/django.git synced 2025-10-31 01:25:32 +00:00

Fixed #20867 -- Added the Form.add_error() method.

Refs #20199 #16986.

Thanks @akaariai, @bmispelon, @mjtamlyn, @timgraham for the reviews.
This commit is contained in:
Loic Bistuer
2013-11-12 00:56:01 +07:00
parent 7e2d61a972
commit f563c339ca
8 changed files with 214 additions and 74 deletions

View File

@@ -77,64 +77,78 @@ class ValidationError(Exception):
"""An error while validating data."""
def __init__(self, message, code=None, params=None):
"""
ValidationError can be passed any object that can be printed (usually
a string), a list of objects or a dictionary.
The `message` argument can be a single error, a list of errors, or a
dictionary that maps field names to lists of errors. What we define as
an "error" can be either a simple string or an instance of
ValidationError with its message attribute set, and what we define as
list or dictionary can be an actual `list` or `dict` or an instance
of ValidationError with its `error_list` or `error_dict` attribute set.
"""
if isinstance(message, ValidationError):
if hasattr(message, 'error_dict'):
message = message.error_dict
elif not hasattr(message, 'message'):
message = message.error_list
else:
message, code, params = message.message, message.code, message.params
if isinstance(message, dict):
self.error_dict = message
self.error_dict = {}
for field, messages in message.items():
if not isinstance(messages, ValidationError):
messages = ValidationError(messages)
self.error_dict[field] = messages.error_list
elif isinstance(message, list):
self.error_list = message
self.error_list = []
for message in message:
# Normalize plain strings to instances of ValidationError.
if not isinstance(message, ValidationError):
message = ValidationError(message)
self.error_list.extend(message.error_list)
else:
self.message = message
self.code = code
self.params = params
self.message = message
self.error_list = [self]
@property
def message_dict(self):
message_dict = {}
for field, messages in self.error_dict.items():
message_dict[field] = []
for message in messages:
if isinstance(message, ValidationError):
message_dict[field].extend(message.messages)
else:
message_dict[field].append(force_text(message))
return message_dict
return dict(self)
@property
def messages(self):
if hasattr(self, 'error_dict'):
message_list = reduce(operator.add, self.error_dict.values())
else:
message_list = self.error_list
messages = []
for message in message_list:
if isinstance(message, ValidationError):
params = message.params
message = message.message
if params:
message %= params
message = force_text(message)
messages.append(message)
return messages
def __str__(self):
if hasattr(self, 'error_dict'):
return repr(self.message_dict)
return repr(self.messages)
def __repr__(self):
return 'ValidationError(%s)' % self
return reduce(operator.add, dict(self).values())
return list(self)
def update_error_dict(self, error_dict):
if hasattr(self, 'error_dict'):
if error_dict:
for k, v in self.error_dict.items():
error_dict.setdefault(k, []).extend(v)
for field, errors in self.error_dict.items():
error_dict.setdefault(field, []).extend(errors)
else:
error_dict = self.error_dict
else:
error_dict[NON_FIELD_ERRORS] = self.error_list
return error_dict
def __iter__(self):
if hasattr(self, 'error_dict'):
for field, errors in self.error_dict.items():
yield field, list(ValidationError(errors))
else:
for error in self.error_list:
message = error.message
if error.params:
message %= error.params
yield force_text(message)
def __str__(self):
if hasattr(self, 'error_dict'):
return repr(dict(self))
return repr(list(self))
def __repr__(self):
return 'ValidationError(%s)' % self

View File

@@ -987,7 +987,7 @@ class Model(six.with_metaclass(ModelBase)):
def clean_fields(self, exclude=None):
"""
Cleans all fields and raises a ValidationError containing message_dict
Cleans all fields and raises a ValidationError containing a dict
of all validation errors if any occur.
"""
if exclude is None:

View File

@@ -290,6 +290,51 @@ class BaseForm(object):
prefix = self.add_prefix(fieldname)
return field.widget.value_from_datadict(self.data, self.files, prefix)
def add_error(self, field, error):
"""
Update the content of `self._errors`.
The `field` argument is the name of the field to which the errors
should be added. If its value is None the errors will be treated as
NON_FIELD_ERRORS.
The `error` argument can be a single error, a list of errors, or a
dictionary that maps field names to lists of errors. What we define as
an "error" can be either a simple string or an instance of
ValidationError with its message attribute set and what we define as
list or dictionary can be an actual `list` or `dict` or an instance
of ValidationError with its `error_list` or `error_dict` attribute set.
If `error` is a dictionary, the `field` argument *must* be None and
errors will be added to the fields that correspond to the keys of the
dictionary.
"""
if not isinstance(error, ValidationError):
# Normalize to ValidationError and let its constructor
# do the hard work of making sense of the input.
error = ValidationError(error)
if hasattr(error, 'error_dict'):
if field is not None:
raise TypeError(
"The argument `field` must be `None` when the `error` "
"argument contains errors for multiple fields."
)
else:
error = dict(error)
else:
error = {field or NON_FIELD_ERRORS: list(error)}
for field, error_list in error.items():
if field not in self.errors:
if field != NON_FIELD_ERRORS and field not in self.fields:
raise ValueError(
"'%s' has no field named '%s'." % (self.__class__.__name__, field))
self._errors[field] = self.error_class()
self._errors[field].extend(error_list)
if field in self.cleaned_data:
del self.cleaned_data[field]
def full_clean(self):
"""
Cleans all of self.data and populates self._errors and
@@ -303,6 +348,7 @@ class BaseForm(object):
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
@@ -324,15 +370,13 @@ class BaseForm(object):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data:
del self.cleaned_data[name]
self.add_error(name, e)
def _clean_form(self):
try:
cleaned_data = self.clean()
except ValidationError as e:
self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data

View File

@@ -326,27 +326,6 @@ class BaseModelForm(BaseForm):
super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
error_class, label_suffix, empty_permitted)
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)
# Remove the data from the cleaned_data dict since it was invalid
if k in self.cleaned_data:
del self.cleaned_data[k]
if NON_FIELD_ERRORS in message_dict:
messages = message_dict[NON_FIELD_ERRORS]
self._errors.setdefault(NON_FIELD_ERRORS, self.error_class()).extend(messages)
def _get_validation_exclusions(self):
"""
For backwards-compatibility, several types of fields need to be
@@ -393,6 +372,20 @@ class BaseModelForm(BaseForm):
self._validate_unique = True
return self.cleaned_data
def _update_errors(self, errors):
# Override any validation error messages defined at the model level
# with those defined on the form fields.
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) and
message.code in field.error_messages):
message.message = field.error_messages[message.code]
self.add_error(None, errors)
def _post_clean(self):
opts = self._meta
# Update the model instance with self.cleaned_data.
@@ -407,13 +400,12 @@ class BaseModelForm(BaseForm):
# object being referred to may not yet fully exist (#12749).
# However, these fields *must* be included in uniqueness checks,
# so this can't be part of _get_validation_exclusions().
for f_name, field in self.fields.items():
for name, field in self.fields.items():
if isinstance(field, InlineForeignKeyField):
exclude.append(f_name)
exclude.append(name)
try:
self.instance.full_clean(exclude=exclude,
validate_unique=False)
self.instance.full_clean(exclude=exclude, validate_unique=False)
except ValidationError as e:
self._update_errors(e)
@@ -695,6 +687,7 @@ class BaseModelFormSet(BaseFormSet):
del form.cleaned_data[field]
# mark the data as seen
seen_data.add(data)
if errors:
raise ValidationError(errors)