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): |