diff --git a/django/forms/forms.py b/django/forms/forms.py
index c53a901..73599d0 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -263,6 +263,14 @@ class BaseForm(StrAndUnicode):
         # changed from the initial data, short circuit any validation.
         if self.empty_permitted and not self.has_changed():
             return
+
+        self._clean_fields()
+        self._clean_form()
+
+        if self._errors:
+            delattr(self, 'cleaned_data')
+
+    def _clean_fields(self):
         for name, field in self.fields.items():
             # value_from_datadict() gets the data from the data dictionaries.
             # Each widget type knows how to retrieve its own data, because some
@@ -282,12 +290,12 @@ class BaseForm(StrAndUnicode):
                 self._errors[name] = self.error_class(e.messages)
                 if name in self.cleaned_data:
                     del self.cleaned_data[name]
+
+    def _clean_form(self):
         try:
             self.cleaned_data = self.clean()
         except ValidationError, e:
             self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
-        if self._errors:
-            delattr(self, 'cleaned_data')
 
     def clean(self):
         """
diff --git a/django/forms/models.py b/django/forms/models.py
index cce2319..3ac1854 100644
--- a/django/forms/models.py
+++ b/django/forms/models.py
@@ -250,7 +250,6 @@ class BaseModelForm(BaseForm):
         super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
                                             error_class, label_suffix, empty_permitted)
 
-
     def _get_validation_exclusions(self):
         """
         For backwards-compatibility, several types of fields need to be
@@ -280,23 +279,62 @@ class BaseModelForm(BaseForm):
                     exclude.append(f.name)
         return exclude
 
-    def clean(self):
+    def _clean_fields(self):
+        """
+        Cleans the form fields, constructs the instance, then cleans the model
+        fields.
+        """
+        super(BaseModelForm, self)._clean_fields()
         opts = self._meta
         self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
         exclude = self._get_validation_exclusions()
         try:
-            self.instance.full_clean(exclude=exclude)
+            self.instance.clean_fields(exclude=exclude)
         except ValidationError, e:
-            for k, v in e.message_dict.items():
-                if k != NON_FIELD_ERRORS:
-                    self._errors.setdefault(k, ErrorList()).extend(v)
-                    # Remove the data from the cleaned_data dict since it was invalid
-                    if k in self.cleaned_data:
-                        del self.cleaned_data[k]
-            if NON_FIELD_ERRORS in e.message_dict:
-                raise ValidationError(e.message_dict[NON_FIELD_ERRORS])
+            self._populate_errors(e.message_dict)
+
+    def _clean_form(self):
+        """
+        Runs the instance's clean method, then the form's. This is becuase the
+        form will run validate_unique() by default, and we should run the
+        model's clean method first.
+        """
+        exclude = self._get_validation_exclusions()
+        try:
+            self.instance.clean()
+        except ValidationError, e:
+            self._populate_errors(e.message_dict)
+        super(BaseModelForm, self)._clean_form()
+
+    def _populate_errors(self, message_dict):
+        """
+        Updates self._errors with the errors from a ValidationError's
+        message_dict attribute.
+        """
+        for k, v in message_dict.items():
+            if k != NON_FIELD_ERRORS:
+                self._errors.setdefault(k, ErrorList()).extend(v)
+                # Remove the data from the cleaned_data dict since it was invalid
+                if k in self.cleaned_data:
+                    del self.cleaned_data[k]
+        if NON_FIELD_ERRORS in message_dict:
+            messages = message_dict[NON_FIELD_ERRORS]
+            self._errors.setdefault(NON_FIELD_ERRORS, ErrorList()).extend(messages)
+
+    def clean(self):
+        self.validate_unique()
         return self.cleaned_data
 
+    def validate_unique(self):
+        exclude = self._get_validation_exclusions()
+        for name in self._errors.keys():
+            if name != NON_FIELD_ERRORS and name not in exclude:
+                exclude.append(name)
+        try:
+            self.instance.validate_unique(exclude=exclude)
+        except ValidationError, e:
+            self._populate_errors(e.message_dict)
+
     def save(self, commit=True):
         """
         Saves this ``form``'s cleaned_data into model instance
diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py
index f8b6511..57d5655 100644
--- a/tests/regressiontests/model_forms_regress/tests.py
+++ b/tests/regressiontests/model_forms_regress/tests.py
@@ -50,6 +50,27 @@ class UniqueTogetherTests(TestCase):
         form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
         self.failUnless(form.is_valid())
 
+class TripleFormWithCleanOverride(forms.ModelForm):
+    class Meta:
+        model = Triple
+
+    def clean(self):
+        if not self.cleaned_data['left'] == self.cleaned_data['right']:
+            raise forms.ValidationError('Left and right should be equal')
+        return self.cleaned_data
+
+class OverrideCleanTests(TestCase):
+    def test_override_clean(self):
+        """
+        Regression for #12596: Calling super from ModelForm.clean() should be
+        optional.
+        """
+        form = TripleFormWithCleanOverride({'left': 1, 'middle': 2, 'right': 1})
+        self.failUnless(form.is_valid())
+        # form.instance.left will be None if the instance was not constructed
+        # by form.full_clean().
+        self.assertEquals(form.instance.left, 1)
+
 class FPForm(forms.ModelForm):
     class Meta:
         model = FilePathModel
