diff --git a/django/forms/models.py b/django/forms/models.py index cf465ad30c..d5d9fd1944 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -526,10 +526,9 @@ class BaseModelFormSet(BaseFormSet): # it's already invalid if not hasattr(form, "cleaned_data"): continue - # get each of the fields for which we have data on this form - if [f for f in unique_check if f in form.cleaned_data and form.cleaned_data[f] is not None]: - # get the data itself - row_data = tuple([form.cleaned_data[field] for field in unique_check]) + # get data for each field of each of unique_check + row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data]) + if row_data and not None in row_data: # if we've aready seen it then we have a uniqueness failure if row_data in seen_data: # poke error messages into the right places and mark diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index d5825313ec..1665f62416 100644 --- a/tests/modeltests/model_formsets/models.py +++ b/tests/modeltests/model_formsets/models.py @@ -35,6 +35,23 @@ class BookWithCustomPK(models.Model): def __unicode__(self): return u'%s: %s' % (self.my_pk, self.title) +class Editor(models.Model): + name = models.CharField(max_length=100) + +class BookWithOptionalAltEditor(models.Model): + author = models.ForeignKey(Author) + # Optional secondary author + alt_editor = models.ForeignKey(Editor, blank=True, null=True) + title = models.CharField(max_length=100) + + class Meta: + unique_together = ( + ('author', 'title', 'alt_editor'), + ) + + def __unicode__(self): + return self.title + class AlternateBook(Book): notes = models.CharField(max_length=100) @@ -648,6 +665,26 @@ True >>> formset.save() [] +Test inline formsets where the inline-edited object has a unique_together constraint with a nullable member + +>>> AuthorBooksFormSet4 = inlineformset_factory(Author, BookWithOptionalAltEditor, can_delete=False, extra=2) + +>>> data = { +... 'bookwithoptionalalteditor_set-TOTAL_FORMS': '2', # the number of forms rendered +... 'bookwithoptionalalteditor_set-INITIAL_FORMS': '0', # the number of forms with initial data +... 'bookwithoptionalalteditor_set-MAX_NUM_FORMS': '', # the max number of forms +... 'bookwithoptionalalteditor_set-0-author': '1', +... 'bookwithoptionalalteditor_set-0-title': 'Les Fleurs du Mal', +... 'bookwithoptionalalteditor_set-1-author': '1', +... 'bookwithoptionalalteditor_set-1-title': 'Les Fleurs du Mal', +... } +>>> formset = AuthorBooksFormSet4(data, instance=author) +>>> formset.is_valid() +True + +>>> formset.save() +[, ] + # ModelForm with a custom save method in an inline formset ###################