Commit f34cfec0 authored by Loic Bistuer's avatar Loic Bistuer Committed by Tim Graham
Browse files

Refactored ValidationError to allow persisting error params and error codes as...

Refactored ValidationError to allow persisting error params and error codes as the exception bubbles up
parent efe6e160
Loading
Loading
Loading
Loading
+46 −22
Original line number Diff line number Diff line
@@ -3,6 +3,9 @@ Global Django exception and warning classes.
"""
import logging
from functools import reduce
import operator

from django.utils.encoding import force_text


class DjangoRuntimeWarning(RuntimeWarning):
@@ -74,46 +77,67 @@ NON_FIELD_ERRORS = '__all__'
class ValidationError(Exception):
    """An error while validating data."""
    def __init__(self, message, code=None, params=None):
        import operator
        from django.utils.encoding import force_text
        """
        ValidationError can be passed any object that can be printed (usually
        a string), a list of objects or a dictionary.
        """
        if isinstance(message, dict):
            self.message_dict = message
            # Reduce each list of messages into a single list.
            message = reduce(operator.add, message.values())

        if isinstance(message, list):
            self.messages = [force_text(msg) for msg in message]
            self.error_dict = message
        elif isinstance(message, list):
            self.error_list = message
        else:
            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

    @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)
            else:
                message = force_text(message)
            self.messages = [message]
            messages.append(message)
        return messages

    def __str__(self):
        # This is needed because, without a __str__(), printing an exception
        # instance would result in this:
        # AttributeError: ValidationError instance has no attribute 'args'
        # See http://www.python.org/doc/current/tut/node10.html#handling
        if hasattr(self, 'message_dict'):
        if hasattr(self, 'error_dict'):
            return repr(self.message_dict)
        return repr(self.messages)

    def __repr__(self):
        if hasattr(self, 'message_dict'):
            return 'ValidationError(%s)' % repr(self.message_dict)
        return 'ValidationError(%s)' % repr(self.messages)
        return 'ValidationError(%s)' % self

    def update_error_dict(self, error_dict):
        if hasattr(self, 'message_dict'):
        if hasattr(self, 'error_dict'):
            if error_dict:
                for k, v in self.message_dict.items():
                for k, v in self.error_dict.items():
                    error_dict.setdefault(k, []).extend(v)
            else:
                error_dict = self.message_dict
                error_dict = self.error_dict
        else:
            error_dict[NON_FIELD_ERRORS] = self.messages
            error_dict[NON_FIELD_ERRORS] = self.error_list
        return error_dict
+10 −9
Original line number Diff line number Diff line
@@ -910,7 +910,7 @@ class Model(six.with_metaclass(ModelBase)):
                'field_label': six.text_type(field_labels)
            }

    def full_clean(self, exclude=None):
    def full_clean(self, exclude=None, validate_unique=True):
        """
        Calls clean_fields, clean, and validate_unique, on the model,
        and raises a ``ValidationError`` for any errors that occurred.
@@ -932,6 +932,7 @@ class Model(six.with_metaclass(ModelBase)):
            errors = e.update_error_dict(errors)

        # Run unique checks, but only for fields that passed validation.
        if validate_unique:
            for name in errors.keys():
                if name != NON_FIELD_ERRORS and name not in exclude:
                    exclude.append(name)
@@ -963,7 +964,7 @@ class Model(six.with_metaclass(ModelBase)):
            try:
                setattr(self, f.attname, f.clean(raw_value, self))
            except ValidationError as e:
                errors[f.name] = e.messages
                errors[f.name] = e.error_list

        if errors:
            raise ValidationError(errors)
+3 −6
Original line number Diff line number Diff line
@@ -208,12 +208,9 @@ class Field(object):
                v(value)
            except exceptions.ValidationError as e:
                if hasattr(e, 'code') and e.code in self.error_messages:
                    message = self.error_messages[e.code]
                    if e.params:
                        message = message % e.params
                    errors.append(message)
                else:
                    errors.extend(e.messages)
                    e.message = self.error_messages[e.code]
                errors.extend(e.error_list)

        if errors:
            raise exceptions.ValidationError(errors)

+3 −7
Original line number Diff line number Diff line
@@ -136,12 +136,8 @@ class Field(object):
                v(value)
            except ValidationError as e:
                if hasattr(e, 'code') and e.code in self.error_messages:
                    message = self.error_messages[e.code]
                    if e.params:
                        message = message % e.params
                    errors.append(message)
                else:
                    errors.extend(e.messages)
                    e.message = self.error_messages[e.code]
                errors.extend(e.error_list)
        if errors:
            raise ValidationError(errors)

@@ -974,7 +970,7 @@ class MultiValueField(Field):
                # Collect all validation errors in a single list, which we'll
                # raise at the end of clean(), rather than raising a single
                # exception for the first error we encounter.
                errors.extend(e.messages)
                errors.extend(e.error_list)
        if errors:
            raise ValidationError(errors)

+4 −10
Original line number Diff line number Diff line
@@ -389,17 +389,11 @@ class BaseModelForm(BaseForm):
            if isinstance(field, InlineForeignKeyField):
                exclude.append(f_name)

        # Clean the model instance's fields.
        try:
            self.instance.clean_fields(exclude=exclude)
            self.instance.full_clean(exclude=exclude,
                validate_unique=False)
        except ValidationError as e:
            self._update_errors(e.message_dict)

        # Call the model instance's clean method.
        try:
            self.instance.clean()
        except ValidationError as e:
            self._update_errors({NON_FIELD_ERRORS: e.messages})
            self._update_errors(e)

        # Validate uniqueness if needed.
        if self._validate_unique:
@@ -414,7 +408,7 @@ class BaseModelForm(BaseForm):
        try:
            self.instance.validate_unique(exclude=exclude)
        except ValidationError as e:
            self._update_errors(e.message_dict)
            self._update_errors(e)

    def save(self, commit=True):
        """
Loading