Skip to content

Commit

Permalink
Fixed #25987 -- Made inline formset validation respect unique_togethe…
Browse files Browse the repository at this point in the history
…r with an unsaved parent object.

Thanks Anton Kuzmichev for the report and Tim for the review.
  • Loading branch information
charettes committed Mar 27, 2016
1 parent 2c125bd commit 1a403aa
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 3 deletions.
12 changes: 9 additions & 3 deletions django/forms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ class BaseModelFormSet(BaseFormSet):
"""
model = None

# Set of fields that must be unique among forms of this set.
unique_fields = set()

def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
queryset=None, **kwargs):
self.queryset = queryset
Expand Down Expand Up @@ -677,9 +680,11 @@ def validate_unique(self):
for uclass, unique_check in all_unique_checks:
seen_data = set()
for form in valid_forms:
# get data for each field of each of unique_check
row_data = (form.cleaned_data[field]
for field in unique_check if field in form.cleaned_data)
# Get the data for the set of fields that must be unique among the forms.
row_data = (
field if field in self.unique_fields else form.cleaned_data[field]
for field in unique_check if field in form.cleaned_data
)
# Reduce Model instances to their primary key values
row_data = tuple(d._get_pk_val() if hasattr(d, '_get_pk_val') else d
for d in row_data)
Expand Down Expand Up @@ -878,6 +883,7 @@ def __init__(self, data=None, files=None, instance=None,
qs = queryset.filter(**{self.fk.name: self.instance})
else:
qs = queryset.none()
self.unique_fields = {self.fk.name}
super(BaseInlineFormSet, self).__init__(data, files, prefix=prefix,
queryset=qs, **kwargs)

Expand Down
3 changes: 3 additions & 0 deletions tests/inline_formsets/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,8 @@ class Poem(models.Model):
poet = models.ForeignKey(Poet, models.CASCADE)
name = models.CharField(max_length=100)

class Meta:
unique_together = ('poet', 'name')

def __str__(self):
return self.name
14 changes: 14 additions & 0 deletions tests/inline_formsets/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,17 @@ def test_zero_primary_key(self):
PoemFormSet = inlineformset_factory(Poet, Poem, fields="__all__", extra=0)
formset = PoemFormSet(None, instance=poet)
self.assertEqual(len(formset.forms), 1)

def test_unsaved_fk_validate_unique(self):
poet = Poet(name='unsaved')
PoemFormSet = inlineformset_factory(Poet, Poem, fields=['name'])
data = {
'poem_set-TOTAL_FORMS': '2',
'poem_set-INITIAL_FORMS': '0',
'poem_set-MAX_NUM_FORMS': '2',
'poem_set-0-name': 'Poem',
'poem_set-1-name': 'Poem',
}
formset = PoemFormSet(data, instance=poet)
self.assertFalse(formset.is_valid())
self.assertEqual(formset.non_form_errors(), ['Please correct the duplicate data for name.'])

0 comments on commit 1a403aa

Please sign in to comment.