Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #9319 -- Fixed a crash when using the same model field in multiple

unique_together constraints.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@9208 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 7e7a370e20b57c0a4906aa82a4763119342823cc 1 parent 559aca7
Malcolm Tredinnick authored October 08, 2008
40  django/forms/models.py
@@ -15,6 +15,11 @@
15 15
 from widgets import media_property
16 16
 from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
17 17
 
  18
+try:
  19
+    set
  20
+except NameError:
  21
+    from sets import Set as set     # Python 2.3 fallback
  22
+
18 23
 __all__ = (
19 24
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
20 25
     'save_instance', 'form_for_fields', 'ModelChoiceField',
@@ -219,9 +224,9 @@ def validate_unique(self):
219 224
             fields_on_form = [field for field in check if field in self.fields]
220 225
             if len(fields_on_form) == len(check):
221 226
                 unique_checks.append(check)
222  
-            
  227
+
223 228
         form_errors = []
224  
-        
  229
+
225 230
         # Gather a list of checks for fields declared as unique and add them to
226 231
         # the list of checks. Again, skip fields not on the form.
227 232
         for name, field in self.fields.items():
@@ -235,30 +240,31 @@ def validate_unique(self):
235 240
             is_null_pk = f.primary_key and self.cleaned_data[name] is None
236 241
             if name in self.cleaned_data and f.unique and not is_null_pk:
237 242
                 unique_checks.append((name,))
238  
-                
  243
+
239 244
         # Don't run unique checks on fields that already have an error.
240 245
         unique_checks = [check for check in unique_checks if not [x in self._errors for x in check if x in self._errors]]
241  
-        
  246
+
  247
+        bad_fields = set()
242 248
         for unique_check in unique_checks:
243 249
             # Try to look up an existing object with the same values as this
244 250
             # object's values for all the unique field.
245  
-            
  251
+
246 252
             lookup_kwargs = {}
247 253
             for field_name in unique_check:
248 254
                 lookup_kwargs[field_name] = self.cleaned_data[field_name]
249  
-            
  255
+
250 256
             qs = self.instance.__class__._default_manager.filter(**lookup_kwargs)
251 257
 
252  
-            # Exclude the current object from the query if we are editing an 
  258
+            # Exclude the current object from the query if we are editing an
253 259
             # instance (as opposed to creating a new one)
254 260
             if self.instance.pk is not None:
255 261
                 qs = qs.exclude(pk=self.instance.pk)
256  
-                
  262
+
257 263
             # This cute trick with extra/values is the most efficient way to
258 264
             # tell if a particular query returns any results.
259 265
             if qs.extra(select={'a': 1}).values('a').order_by():
260 266
                 model_name = capfirst(self.instance._meta.verbose_name)
261  
-                
  267
+
262 268
                 # A unique field
263 269
                 if len(unique_check) == 1:
264 270
                     field_name = unique_check[0]
@@ -278,13 +284,17 @@ def validate_unique(self):
278 284
                         {'model_name': unicode(model_name),
279 285
                          'field_label': unicode(field_labels)}
280 286
                     )
281  
-                
282  
-                # Remove the data from the cleaned_data dict since it was invalid
  287
+
  288
+                # Mark these fields as needing to be removed from cleaned data
  289
+                # later.
283 290
                 for field_name in unique_check:
284  
-                    del self.cleaned_data[field_name]
285  
-        
  291
+                    bad_fields.add(field_name)
  292
+
  293
+        for field_name in bad_fields:
  294
+            del self.cleaned_data[field_name]
286 295
         if form_errors:
287  
-            # Raise the unique together errors since they are considered form-wide.
  296
+            # Raise the unique together errors since they are considered
  297
+            # form-wide.
288 298
             raise ValidationError(form_errors)
289 299
 
290 300
     def save(self, commit=True):
@@ -471,7 +481,7 @@ def save_new(self, form, commit=True):
471 481
         kwargs = {self.fk.get_attname(): self.instance.pk}
472 482
         new_obj = self.model(**kwargs)
473 483
         return save_instance(form, new_obj, exclude=[self._pk_field.name], commit=commit)
474  
-    
  484
+
475 485
     def add_fields(self, form, index):
476 486
         super(BaseInlineFormSet, self).add_fields(form, index)
477 487
         if self._pk_field == self.fk:
0  tests/regressiontests/model_forms_regress/__init__.py
No changes.
32  tests/regressiontests/model_forms_regress/models.py
... ...
@@ -0,0 +1,32 @@
  1
+from django.db import models
  2
+from django import forms
  3
+
  4
+class Triple(models.Model):
  5
+    left = models.IntegerField()
  6
+    middle = models.IntegerField()
  7
+    right = models.IntegerField()
  8
+
  9
+    def __unicode__(self):
  10
+        return u"%d, %d, %d" % (self.left, self.middle, self.right)
  11
+
  12
+    class Meta:
  13
+        unique_together = (('left', 'middle'), ('middle', 'right'))
  14
+
  15
+__test__ = {'API_TESTS': """
  16
+When the same field is involved in multiple unique_together constraints, we
  17
+need to make sure we don't remove the data for it before doing all the
  18
+validation checking (not just failing after the first one).
  19
+
  20
+>>> _ = Triple.objects.create(left=1, middle=2, right=3)
  21
+>>> class TripleForm(forms.ModelForm):
  22
+...     class Meta:
  23
+...         model = Triple
  24
+
  25
+>>> form = TripleForm({'left': '1', 'middle': '2', 'right': '3'})
  26
+>>> form.is_valid()
  27
+False
  28
+>>> form = TripleForm({'left': '1', 'middle': '3', 'right': '1'})
  29
+>>> form.is_valid()
  30
+True
  31
+"""}
  32
+

0 notes on commit 7e7a370

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