Loading django/core/exceptions.py +52 −38 Original line number Diff line number Diff line Loading @@ -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 django/db/models/base.py +1 −1 Original line number Diff line number Diff line Loading @@ -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: Loading django/forms/forms.py +48 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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 Loading django/forms/models.py +18 −25 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading @@ -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) Loading Loading @@ -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) Loading docs/ref/forms/api.txt +20 −0 Original line number Diff line number Diff line Loading @@ -117,6 +117,26 @@ The validation routines will only get called once, regardless of how many times you access :attr:`~Form.errors` or call :meth:`~Form.is_valid`. This means that if validation has side effects, those side effects will only be triggered once. .. method:: Form.add_error(field, error) .. versionadded:: 1.7 This method allows adding errors to specific fields from within the ``Form.clean()`` method, or from outside the form altogether; for instance from a view. This is a better alternative to fiddling directly with ``Form._errors`` as described in :ref:`modifying-field-errors`. The ``field`` argument is the name of the field to which the errors should be added. If its value is ``None`` the error will be treated as a non-field error as returned by ``Form.non_field_errors()``. The ``error`` argument can be a simple string, or preferably an instance of ``ValidationError``. See :ref:`raising-validation-error` for best practices when defining form errors. Note that ``Form.add_error()`` automatically removes the relevant field from ``cleaned_data``. Behavior of unbound forms ~~~~~~~~~~~~~~~~~~~~~~~~~ Loading Loading
django/core/exceptions.py +52 −38 Original line number Diff line number Diff line Loading @@ -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
django/db/models/base.py +1 −1 Original line number Diff line number Diff line Loading @@ -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: Loading
django/forms/forms.py +48 −4 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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() Loading @@ -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 Loading
django/forms/models.py +18 −25 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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. Loading @@ -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) Loading Loading @@ -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) Loading
docs/ref/forms/api.txt +20 −0 Original line number Diff line number Diff line Loading @@ -117,6 +117,26 @@ The validation routines will only get called once, regardless of how many times you access :attr:`~Form.errors` or call :meth:`~Form.is_valid`. This means that if validation has side effects, those side effects will only be triggered once. .. method:: Form.add_error(field, error) .. versionadded:: 1.7 This method allows adding errors to specific fields from within the ``Form.clean()`` method, or from outside the form altogether; for instance from a view. This is a better alternative to fiddling directly with ``Form._errors`` as described in :ref:`modifying-field-errors`. The ``field`` argument is the name of the field to which the errors should be added. If its value is ``None`` the error will be treated as a non-field error as returned by ``Form.non_field_errors()``. The ``error`` argument can be a simple string, or preferably an instance of ``ValidationError``. See :ref:`raising-validation-error` for best practices when defining form errors. Note that ``Form.add_error()`` automatically removes the relevant field from ``cleaned_data``. Behavior of unbound forms ~~~~~~~~~~~~~~~~~~~~~~~~~ Loading