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:
		| @@ -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 | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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) | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user