diff --git a/django/core/exceptions.py b/django/core/exceptions.py index 2c79736e33..5975164d07 100644 --- a/django/core/exceptions.py +++ b/django/core/exceptions.py @@ -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 - message = force_text(message) - self.messages = [message] + 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) + 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 diff --git a/django/db/models/base.py b/django/db/models/base.py index b8b92217e0..1238bfb4ce 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -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,13 +932,14 @@ class Model(six.with_metaclass(ModelBase)): errors = e.update_error_dict(errors) # Run unique checks, but only for fields that passed validation. - for name in errors.keys(): - if name != NON_FIELD_ERRORS and name not in exclude: - exclude.append(name) - try: - self.validate_unique(exclude=exclude) - except ValidationError as e: - errors = e.update_error_dict(errors) + if validate_unique: + for name in errors.keys(): + if name != NON_FIELD_ERRORS and name not in exclude: + exclude.append(name) + try: + self.validate_unique(exclude=exclude) + except ValidationError as e: + errors = e.update_error_dict(errors) if errors: raise ValidationError(errors) @@ -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) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index cf81a27997..25ceb3f868 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -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) diff --git a/django/forms/fields.py b/django/forms/fields.py index 420690704f..219c82100e 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -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) diff --git a/django/forms/models.py b/django/forms/models.py index b0db4b62ac..c4070b97fe 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -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): """ diff --git a/tests/validators/tests.py b/tests/validators/tests.py index 6b46c53cc3..49389ef663 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -213,7 +213,7 @@ class TestSimpleValidators(TestCase): self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'First Problem', %(_)s'Second Problem'])")) def test_message_dict(self): - v = ValidationError({'first': 'First Problem'}) + v = ValidationError({'first': ['First Problem']}) self.assertEqual(str(v), str_prefix("{%(_)s'first': %(_)s'First Problem'}")) self.assertEqual(repr(v), str_prefix("ValidationError({%(_)s'first': %(_)s'First Problem'})"))