Ticket #9493: formset-unique.7.diff
File formset-unique.7.diff, 8.3 KB (added by , 16 years ago) |
---|
-
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index 4b697a8..de47a2a 100644
a b class BaseModelFormSet(BaseFormSet): 412 412 form.save_m2m() 413 413 self.save_m2m = save_m2m 414 414 return self.save_existing_objects(commit) + self.save_new_objects(commit) 415 416 def clean(self): 417 self.validate_unique() 418 419 def validate_unique(self): 420 from django.db.models.fields import FieldDoesNotExist, Field as ModelField 421 unique_checks = [] 422 first_form = self.forms[0] 423 for name, field in first_form.fields.iteritems(): 424 try: 425 f = first_form.instance._meta.get_field_by_name(name)[0] 426 except FieldDoesNotExist: 427 continue 428 if not isinstance(f, ModelField): 429 # This is an extra field that happens to have a name that matches, 430 # for example, a related object accessor for this model. So 431 # get_field_by_name found it, but it is not a Field so do not proceed 432 # to use it as if it were. 433 continue 434 if f.unique: 435 unique_checks.append((name,)) 436 unique_together = [] 437 for unique_together_check in first_form.instance._meta.unique_together: 438 fields_on_form = [f for f in unique_together_check if f in first_form.fields] 439 if len(fields_on_form) == len(unique_together_check): 440 unique_together.append(unique_together_check) 441 unique_checks.extend(unique_together) 442 errors = [] 443 for unique_check in unique_checks: 444 seen_data = set() 445 for form in self.forms: 446 if not hasattr(form, "cleaned_data"): 447 continue 448 if [f for f in unique_check if f in form.cleaned_data and form.cleaned_data[f] is not None]: 449 row_data = tuple([form.cleaned_data[field] for field in unique_check]) 450 if row_data in seen_data: 451 errors.append(self.get_unique_error_message(unique_check)) 452 break 453 else: 454 seen_data.add(row_data) 455 if errors: 456 raise ValidationError(errors) 457 458 def get_unique_error_message(self, unique_check): 459 if len(unique_check) == 1: 460 return _("You have entered duplicate data for %(field)s. It " 461 "should be unique.") % { 462 "field": unique_check[0], 463 } 464 else: 465 return _("You have entered duplicate data for %(field)s. They " 466 "should be unique together.") % { 467 "field": get_text_list(unique_check, _("and")), 468 } 415 469 416 470 def save_existing_objects(self, commit=True): 417 471 self.changed_objects = [] … … class BaseInlineFormSet(BaseModelFormSet): 522 576 # creating new instances 523 577 form.data[form.add_prefix(self._pk_field.name)] = None 524 578 return form 579 580 def get_unique_error_message(self, unique_check): 581 unique_check = [f for f in unique_check if f != self.fk.name] 582 return super(BaseInlineFormSet, self).get_unique_error_message(unique_check) 525 583 526 584 #@classmethod 527 585 def get_default_prefix(cls): … … class BaseInlineFormSet(BaseModelFormSet): 546 604 form.fields[self._pk_field.name] = InlineForeignKeyField(self.instance, pk_field=True) 547 605 else: 548 606 form.fields[self.fk.name] = InlineForeignKeyField(self.instance, label=form.fields[self.fk.name].label) 607 608 def get_unique_error_message(self, unique_check): 609 unique_check = [field for field in unique_check if field != self.fk.name] 610 return super(BaseInlineFormSet, self).get_unique_error_message(unique_check) 549 611 550 612 def _get_foreign_key(parent_model, model, fk_name=None): 551 613 """ -
docs/topics/forms/modelforms.txt
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 1e87957..4462f79 100644
a b exclude:: 487 487 488 488 .. _saving-objects-in-the-formset: 489 489 490 Overriding clean() method 491 ------------------------- 492 493 You can override the ``clean()`` method to provide custom validation to 494 the whole formset at once. By default, the ``clean()`` method will validate 495 that none of the data in the formsets violate the unique constraints on your 496 model (both field ``unique`` and model ``unique_together``). To maintain this 497 default behavior be sure you call the parent's ``clean()`` method:: 498 499 class MyModelFormSet(BaseModelFormSet): 500 def clean(self): 501 super(MyModelFormSet, self).clean() 502 # example custom validation across forms in the formset: 503 for form in self.forms: 504 # your custom formset validation 505 490 506 Saving objects in the formset 491 507 ----------------------------- 492 508 … … than that of a "normal" formset. The only difference is that we call 571 587 ``formset.save()`` to save the data into the database. (This was described 572 588 above, in :ref:`saving-objects-in-the-formset`.) 573 589 590 591 Overiding ``clean()`` on a ``model_formset`` 592 -------------------------------------------- 593 594 Just like with ``ModelForms``, by default the ``clean()`` method of a 595 ``model_formset`` will validate that none of the items in the formset validate 596 the unique constraints on your model(either unique or unique_together). If you 597 want to overide the ``clean()`` method on a ``model_formset`` and maintain this 598 validation, you must call the parent classes ``clean`` method. 599 600 574 601 Using a custom queryset 575 602 ~~~~~~~~~~~~~~~~~~~~~~~ 576 603 -
tests/modeltests/model_formsets/models.py
diff --git a/tests/modeltests/model_formsets/models.py b/tests/modeltests/model_formsets/models.py index d8cbe34..6906e59 100644
a b class BetterAuthor(Author): 22 22 class Book(models.Model): 23 23 author = models.ForeignKey(Author) 24 24 title = models.CharField(max_length=100) 25 26 class Meta: 27 unique_together = ( 28 ('author', 'title'), 29 ) 30 ordering = ['id'] 25 31 26 32 def __unicode__(self): 27 33 return self.title … … True 934 940 >>> formset.get_queryset() 935 941 [<Player: Bobby>] 936 942 943 # Prevent duplicates from within the same formset 944 >>> FormSet = modelformset_factory(Product, extra=2) 945 >>> data = { 946 ... 'form-TOTAL_FORMS': 2, 947 ... 'form-INITIAL_FORMS': 0, 948 ... 'form-0-slug': 'red_car', 949 ... 'form-1-slug': 'red_car', 950 ... } 951 >>> formset = FormSet(data) 952 >>> formset.is_valid() 953 False 954 >>> formset._non_form_errors 955 [u'You have entered duplicate data for slug. It should be unique.'] 956 957 >>> FormSet = modelformset_factory(Price, extra=2) 958 >>> data = { 959 ... 'form-TOTAL_FORMS': 2, 960 ... 'form-INITIAL_FORMS': 0, 961 ... 'form-0-price': '25', 962 ... 'form-0-quantity': '7', 963 ... 'form-1-price': '25', 964 ... 'form-1-quantity': '7', 965 ... } 966 >>> formset = FormSet(data) 967 >>> formset.is_valid() 968 False 969 >>> formset._non_form_errors 970 [u'You have entered duplicate data for price and quantity. They should be unique together.'] 971 972 # only the price field is specified, this should skip any unique checks since the unique_together is not fulfilled. 973 # this will fail with a KeyError if broken. 974 >>> FormSet = modelformset_factory(Price, fields=("price",), extra=2) 975 >>> data = { 976 ... 'form-TOTAL_FORMS': '2', 977 ... 'form-INITIAL_FORMS': '0', 978 ... 'form-0-price': '24', 979 ... 'form-1-price': '24', 980 ... } 981 >>> formset = FormSet(data) 982 >>> formset.is_valid() 983 True 984 985 >>> FormSet = inlineformset_factory(Author, Book, extra=0) 986 >>> author = Author.objects.order_by('id')[0] 987 >>> book_ids = author.book_set.values_list('id', flat=True) 988 >>> data = { 989 ... 'book_set-TOTAL_FORMS': '2', 990 ... 'book_set-INITIAL_FORMS': '2', 991 ... 992 ... 'book_set-0-title': 'The 2008 Election', 993 ... 'book_set-0-author': str(author.id), 994 ... 'book_set-0-id': str(book_ids[0]), 995 ... 996 ... 'book_set-1-title': 'The 2008 Election', 997 ... 'book_set-1-author': str(author.id), 998 ... 'book_set-1-id': str(book_ids[1]), 999 ... } 1000 >>> formset = FormSet(data=data, instance=author) 1001 >>> formset.is_valid() 1002 False 1003 >>> formset._non_form_errors 1004 [u'You have entered duplicate data for title. It should be unique.'] 937 1005 """}