Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #12596. Calling super from a ModelForm's clean method is once a…

…gain optional. Failing to call super only skips unique validation as documented. Thanks for the initial patch and tests, carljm.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12269 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 408d4310291cd1287f3dbc05aaeb5d205eba8751 1 parent 856a39e
Joseph Kocherhans authored January 21, 2010
10  django/forms/forms.py
@@ -263,6 +263,12 @@ def full_clean(self):
263 263
         # changed from the initial data, short circuit any validation.
264 264
         if self.empty_permitted and not self.has_changed():
265 265
             return
  266
+        self._clean_fields()
  267
+        self._clean_form()
  268
+        if self._errors:
  269
+            delattr(self, 'cleaned_data')
  270
+
  271
+    def _clean_fields(self):
266 272
         for name, field in self.fields.items():
267 273
             # value_from_datadict() gets the data from the data dictionaries.
268 274
             # Each widget type knows how to retrieve its own data, because some
@@ -282,12 +288,12 @@ def full_clean(self):
282 288
                 self._errors[name] = self.error_class(e.messages)
283 289
                 if name in self.cleaned_data:
284 290
                     del self.cleaned_data[name]
  291
+
  292
+    def _clean_form(self):
285 293
         try:
286 294
             self.cleaned_data = self.clean()
287 295
         except ValidationError, e:
288 296
             self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
289  
-        if self._errors:
290  
-            delattr(self, 'cleaned_data')
291 297
 
292 298
     def clean(self):
293 299
         """
54  django/forms/models.py
@@ -250,6 +250,16 @@ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
250 250
         super(BaseModelForm, self).__init__(data, files, auto_id, prefix, object_data,
251 251
                                             error_class, label_suffix, empty_permitted)
252 252
 
  253
+    def _update_errors(self, message_dict):
  254
+        for k, v in message_dict.items():
  255
+            if k != NON_FIELD_ERRORS:
  256
+                self._errors.setdefault(k, self.error_class()).extend(v)
  257
+                # Remove the data from the cleaned_data dict since it was invalid
  258
+                if k in self.cleaned_data:
  259
+                    del self.cleaned_data[k]
  260
+        if NON_FIELD_ERRORS in message_dict:
  261
+            messages = message_dict[NON_FIELD_ERRORS]
  262
+            self._errors.setdefault(NON_FIELD_ERRORS, self.error_class()).extend(messages)
253 263
 
254 264
     def _get_validation_exclusions(self):
255 265
         """
@@ -281,21 +291,45 @@ def _get_validation_exclusions(self):
281 291
         return exclude
282 292
 
283 293
     def clean(self):
  294
+        self.validate_unique()
  295
+        return self.cleaned_data
  296
+
  297
+    def _clean_fields(self):
  298
+        """
  299
+        Cleans the form fields, constructs the instance, then cleans the model
  300
+        fields.
  301
+        """
  302
+        super(BaseModelForm, self)._clean_fields()
284 303
         opts = self._meta
285 304
         self.instance = construct_instance(self, self.instance, opts.fields, opts.exclude)
286 305
         exclude = self._get_validation_exclusions()
287 306
         try:
288  
-            self.instance.full_clean(exclude=exclude)
  307
+            self.instance.clean_fields(exclude=exclude)
289 308
         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])
298  
-        return self.cleaned_data
  309
+            self._update_errors(e.message_dict)
  310
+
  311
+    def _clean_form(self):
  312
+        """
  313
+        Runs the instance's clean method, then the form's. This is becuase the
  314
+        form will run validate_unique() by default, and we should run the
  315
+        model's clean method first.
  316
+        """
  317
+        try:
  318
+            self.instance.clean()
  319
+        except ValidationError, e:
  320
+            self._update_errors(e.message_dict)
  321
+        super(BaseModelForm, self)._clean_form()
  322
+
  323
+    def validate_unique(self):
  324
+        """
  325
+        Calls the instance's validate_unique() method and updates the form's
  326
+        validation errors if any were raised.
  327
+        """
  328
+        exclude = self._get_validation_exclusions()
  329
+        try:
  330
+            self.instance.validate_unique(exclude=exclude)
  331
+        except ValidationError, e:
  332
+            self._update_errors(e.message_dict)
299 333
 
300 334
     def save(self, commit=True):
301 335
         """
21  tests/regressiontests/model_forms_regress/tests.py
@@ -50,6 +50,27 @@ def test_multiple_field_unique_together(self):
50 50
         form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
51 51
         self.failUnless(form.is_valid())
52 52
 
  53
+class 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
+
  62
+class 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
+
53 74
 class FPForm(forms.ModelForm):
54 75
     class Meta:
55 76
         model = FilePathModel

0 notes on commit 408d431

Please sign in to comment.
Something went wrong with that request. Please try again.