Ticket #12596: 12596.diff

File 12596.diff, 5.9 KB (added by jkocherhans, 5 years ago)

This takes carljm's test, but makes ModelForm?.clean() call validate_unique() like it used to. Haven't decided if this is the right approach.

  • django/forms/forms.py

    diff --git a/django/forms/forms.py b/django/forms/forms.py
    index c53a901..73599d0 100644
    a b class BaseForm(StrAndUnicode): 
    263263        # changed from the initial data, short circuit any validation.
    264264        if self.empty_permitted and not self.has_changed():
    265265            return
     266
     267        self._clean_fields()
     268        self._clean_form()
     269
     270        if self._errors:
     271            delattr(self, 'cleaned_data')
     272
     273    def _clean_fields(self):
    266274        for name, field in self.fields.items():
    267275            # value_from_datadict() gets the data from the data dictionaries.
    268276            # Each widget type knows how to retrieve its own data, because some
    class BaseForm(StrAndUnicode): 
    282290                self._errors[name] = self.error_class(e.messages)
    283291                if name in self.cleaned_data:
    284292                    del self.cleaned_data[name]
     293
     294    def _clean_form(self):
    285295        try:
    286296            self.cleaned_data = self.clean()
    287297        except ValidationError, e:
    288298            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
    289         if self._errors:
    290             delattr(self, 'cleaned_data')
    291299
    292300    def clean(self):
    293301        """
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index cce2319..3ac1854 100644
    a b class BaseModelForm(BaseForm): 
    250250        super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
    251251                                            error_class, label_suffix, empty_permitted)
    252252
    253 
    254253    def _get_validation_exclusions(self):
    255254        """
    256255        For backwards-compatibility, several types of fields need to be
    class BaseModelForm(BaseForm): 
    280279                    exclude.append(f.name)
    281280        return exclude
    282281
    283     def clean(self):
     282    def _clean_fields(self):
     283        """
     284        Cleans the form fields, constructs the instance, then cleans the model
     285        fields.
     286        """
     287        super(BaseModelForm, self)._clean_fields()
    284288        opts = self._meta
    285289        self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
    286290        exclude = self._get_validation_exclusions()
    287291        try:
    288             self.instance.full_clean(exclude=exclude)
     292            self.instance.clean_fields(exclude=exclude)
    289293        except ValidationError, e:
    290             for k, v in e.message_dict.items():
    291                 if k != NON_FIELD_ERRORS:
    292                     self._errors.setdefault(k, ErrorList()).extend(v)
    293                     # Remove the data from the cleaned_data dict since it was invalid
    294                     if k in self.cleaned_data:
    295                         del self.cleaned_data[k]
    296             if NON_FIELD_ERRORS in e.message_dict:
    297                 raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
     294            self._populate_errors(e.message_dict)
     295
     296    def _clean_form(self):
     297        """
     298        Runs the instance's clean method, then the form's. This is becuase the
     299        form will run validate_unique() by default, and we should run the
     300        model's clean method first.
     301        """
     302        exclude = self._get_validation_exclusions()
     303        try:
     304            self.instance.clean()
     305        except ValidationError, e:
     306            self._populate_errors(e.message_dict)
     307        super(BaseModelForm, self)._clean_form()
     308
     309    def _populate_errors(self, message_dict):
     310        """
     311        Updates self._errors with the errors from a ValidationError's
     312        message_dict attribute.
     313        """
     314        for k, v in message_dict.items():
     315            if k != NON_FIELD_ERRORS:
     316                self._errors.setdefault(k, ErrorList()).extend(v)
     317                # Remove the data from the cleaned_data dict since it was invalid
     318                if k in self.cleaned_data:
     319                    del self.cleaned_data[k]
     320        if NON_FIELD_ERRORS in message_dict:
     321            messages = message_dict[NON_FIELD_ERRORS]
     322            self._errors.setdefault(NON_FIELD_ERRORS, ErrorList()).extend(messages)
     323
     324    def clean(self):
     325        self.validate_unique()
    298326        return self.cleaned_data
    299327
     328    def validate_unique(self):
     329        exclude = self._get_validation_exclusions()
     330        for name in self._errors.keys():
     331            if name != NON_FIELD_ERRORS and name not in exclude:
     332                exclude.append(name)
     333        try:
     334            self.instance.validate_unique(exclude=exclude)
     335        except ValidationError, e:
     336            self._populate_errors(e.message_dict)
     337
    300338    def save(self, commit=True):
    301339        """
    302340        Saves this ``form``'s cleaned_data into model instance
  • tests/regressiontests/model_forms_regress/tests.py

    diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py
    index f8b6511..57d5655 100644
    a b class UniqueTogetherTests(TestCase): 
    5050        form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
    5151        self.failUnless(form.is_valid())
    5252
     53class TripleFormWithCleanOverride(forms.ModelForm):
     54    class Meta:
     55        model = Triple
     56
     57    def clean(self):
     58        if not self.cleaned_data['left'] == self.cleaned_data['right']:
     59            raise forms.ValidationError('Left and right should be equal')
     60        return self.cleaned_data
     61
     62class OverrideCleanTests(TestCase):
     63    def test_override_clean(self):
     64        """
     65        Regression for #12596: Calling super from ModelForm.clean() should be
     66        optional.
     67        """
     68        form = TripleFormWithCleanOverride({'left': 1, 'middle': 2, 'right': 1})
     69        self.failUnless(form.is_valid())
     70        # form.instance.left will be None if the instance was not constructed
     71        # by form.full_clean().
     72        self.assertEquals(form.instance.left, 1)
     73
    5374class FPForm(forms.ModelForm):
    5475    class Meta:
    5576        model = FilePathModel
Back to Top