diff --git a/django/core/exceptions.py b/django/core/exceptions.py
index fee7db4..a91e61a 100644
a
|
b
|
class FieldError(Exception):
|
33 | 33 | pass |
34 | 34 | |
35 | 35 | NON_FIELD_ERRORS = '__all__' |
36 | | class BaseValidationError(Exception): |
| 36 | class ValidationError(Exception): |
37 | 37 | """An error while validating data.""" |
38 | 38 | def __init__(self, message, code=None, params=None): |
39 | 39 | import operator |
… |
… |
class BaseValidationError(Exception):
|
64 | 64 | return repr(self.message_dict) |
65 | 65 | return repr(self.messages) |
66 | 66 | |
67 | | class ValidationError(BaseValidationError): |
68 | | pass |
69 | | |
70 | | class UnresolvableValidationError(BaseValidationError): |
71 | | """Validation error that cannot be resolved by the user.""" |
72 | | pass |
73 | | |
diff --git a/django/db/models/base.py b/django/db/models/base.py
index 06db7cc..64b4993 100644
a
|
b
|
class Model(object):
|
649 | 649 | not be associated with a particular field; it will have a special-case |
650 | 650 | association with the field defined by NON_FIELD_ERRORS. |
651 | 651 | """ |
652 | | self.validate_unique() |
| 652 | pass |
653 | 653 | |
654 | | def validate_unique(self): |
655 | | unique_checks, date_checks = self._get_unique_checks() |
| 654 | def validate_unique(self, exclude=[]): |
| 655 | unique_checks, date_checks = self._get_unique_checks(exclude) |
656 | 656 | |
657 | 657 | errors = self._perform_unique_checks(unique_checks) |
658 | 658 | date_errors = self._perform_date_checks(date_checks) |
… |
… |
class Model(object):
|
663 | 663 | if errors: |
664 | 664 | raise ValidationError(errors) |
665 | 665 | |
666 | | def _get_unique_checks(self): |
| 666 | def _get_unique_checks(self, exclude=[]): |
667 | 667 | from django.db.models.fields import FieldDoesNotExist, Field as ModelField |
668 | 668 | |
669 | | unique_checks = list(self._meta.unique_together) |
| 669 | unique_checks = [] |
| 670 | # include all unique_together checks... |
| 671 | for check in self._meta.unique_together: |
| 672 | for name in check: |
| 673 | # ... except those that contain an excluded field |
| 674 | if name in exclude: |
| 675 | break |
| 676 | else: |
| 677 | unique_checks.append(check) |
| 678 | |
670 | 679 | # these are checks for the unique_for_<date/year/month> |
671 | 680 | date_checks = [] |
672 | 681 | |
… |
… |
class Model(object):
|
674 | 683 | # the list of checks. Again, skip empty fields and any that did not validate. |
675 | 684 | for f in self._meta.fields: |
676 | 685 | name = f.name |
| 686 | # do not process excluded fields |
| 687 | if name in exclude: |
| 688 | continue |
677 | 689 | if f.unique: |
678 | 690 | unique_checks.append((name,)) |
679 | 691 | if f.unique_for_date: |
… |
… |
class Model(object):
|
795 | 807 | except ValidationError, e: |
796 | 808 | errors[f.name] = e.messages |
797 | 809 | |
798 | | # Form.clean() is run even if other validation fails, so do the |
799 | | # same with Model.validate() for consistency. |
800 | 810 | try: |
801 | | self.validate() |
| 811 | self.validate_unique(exclude=exclude+errors.keys()) |
802 | 812 | except ValidationError, e: |
803 | 813 | if hasattr(e, 'message_dict'): |
804 | 814 | if errors: |
… |
… |
class Model(object):
|
809 | 819 | else: |
810 | 820 | errors[NON_FIELD_ERRORS] = e.messages |
811 | 821 | |
| 822 | # Form.clean() is run even if other validation fails, so do the |
| 823 | # same with Model.validate() for consistency. |
| 824 | |
| 825 | |
| 826 | # However, do not run Model.validate() on incomplete forms when creating new instance |
| 827 | if not (exclude and getattr(self, '_adding', True)): |
| 828 | try: |
| 829 | self.validate() |
| 830 | except ValidationError, e: |
| 831 | if hasattr(e, 'message_dict'): |
| 832 | if errors: |
| 833 | for k, v in e.message_dict.items(): |
| 834 | errors.set_default(k, []).extend(v) |
| 835 | else: |
| 836 | errors = e.message_dict |
| 837 | else: |
| 838 | errors[NON_FIELD_ERRORS] = e.messages |
| 839 | |
812 | 840 | if errors: |
813 | 841 | raise ValidationError(errors) |
814 | 842 | |
diff --git a/django/forms/models.py b/django/forms/models.py
index ff20c93..6b2bca2 100644
a
|
b
|
from django.utils.datastructures import SortedDict
|
9 | 9 | from django.utils.text import get_text_list, capfirst |
10 | 10 | from django.utils.translation import ugettext_lazy as _, ugettext |
11 | 11 | |
12 | | from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, UnresolvableValidationError |
| 12 | from django.core.exceptions import ValidationError, NON_FIELD_ERRORS |
13 | 13 | from django.core.validators import EMPTY_VALUES |
14 | 14 | from util import ErrorList |
15 | 15 | from forms import BaseForm, get_declared_fields |
… |
… |
class BaseModelForm(BaseForm):
|
249 | 249 | opts = self._meta |
250 | 250 | self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude) |
251 | 251 | try: |
252 | | self.instance.full_validate(exclude=self._errors.keys()) |
| 252 | self.instance.full_validate(exclude=self._errors.keys() + list(opts.exclude or [])) |
253 | 253 | except ValidationError, e: |
254 | 254 | for k, v in e.message_dict.items(): |
255 | 255 | if k != NON_FIELD_ERRORS: |
… |
… |
class BaseModelForm(BaseForm):
|
262 | 262 | if NON_FIELD_ERRORS in e.message_dict: |
263 | 263 | raise ValidationError(e.message_dict[NON_FIELD_ERRORS]) |
264 | 264 | |
265 | | # If model validation threw errors for fields that aren't on the |
266 | | # form, the the errors cannot be corrected by the user. Displaying |
267 | | # those errors would be pointless, so raise another type of |
268 | | # exception that *won't* be caught and displayed by the form. |
269 | | if set(e.message_dict.keys()) - set(self.fields.keys() + [NON_FIELD_ERRORS]): |
270 | | raise UnresolvableValidationError(e.message_dict) |
271 | | |
272 | | |
273 | 265 | return self.cleaned_data |
274 | 266 | |
275 | 267 | def save(self, commit=True): |
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index ba59f9a..4ed8da1 100644
a
|
b
|
False
|
1425 | 1425 | >>> form._errors |
1426 | 1426 | {'__all__': [u'Price with this Price and Quantity already exists.']} |
1427 | 1427 | |
1428 | | # This form is never valid because quantity is blank=False. |
1429 | 1428 | >>> class PriceForm(ModelForm): |
1430 | 1429 | ... class Meta: |
1431 | 1430 | ... model = Price |
1432 | 1431 | ... exclude = ('quantity',) |
1433 | 1432 | >>> form = PriceForm({'price': '6.00'}) |
1434 | 1433 | >>> form.is_valid() |
1435 | | Traceback (most recent call last): |
1436 | | ... |
1437 | | UnresolvableValidationError: {'quantity': [u'This field cannot be null.']} |
| 1434 | True |
1438 | 1435 | |
1439 | 1436 | # Unique & unique together with null values |
1440 | 1437 | >>> class BookForm(ModelForm): |