Ticket #9493: formset-unique.9.diff
File formset-unique.9.diff, 7.8 KB (added by , 16 years ago) |
---|
-
django/forms/models.py
diff --git a/django/forms/models.py b/django/forms/models.py index de5f1ab..4cd518f 100644
a b class BaseModelFormSet(BaseFormSet): 414 414 self.save_m2m = save_m2m 415 415 return self.save_existing_objects(commit) + self.save_new_objects(commit) 416 416 417 def clean(self): 418 self.validate_unique() 419 420 def validate_unique(self): 421 from django.db.models.fields import FieldDoesNotExist, Field as ModelField 422 unique_checks = [] 423 first_form = self.forms[0] 424 for name, field in first_form.fields.iteritems(): 425 try: 426 f = first_form.instance._meta.get_field_by_name(name)[0] 427 except FieldDoesNotExist: 428 continue 429 if not isinstance(f, ModelField): 430 # This is an extra field that happens to have a name that matches, 431 # for example, a related object accessor for this model. So 432 # get_field_by_name found it, but it is not a Field so do not proceed 433 # to use it as if it were. 434 continue 435 if f.unique: 436 unique_checks.append((name,)) 437 unique_together = [] 438 for unique_together_check in first_form.instance._meta.unique_together: 439 fields_on_form = [f for f in unique_together_check if f in first_form.fields] 440 if len(fields_on_form) == len(unique_together_check): 441 unique_together.append(unique_together_check) 442 unique_checks.extend(unique_together) 443 errors = [] 444 for unique_check in unique_checks: 445 seen_data = set() 446 for form in self.forms: 447 if not hasattr(form, "cleaned_data"): 448 continue 449 if [f for f in unique_check if f in form.cleaned_data and form.cleaned_data[f] is not None]: 450 row_data = tuple([form.cleaned_data[field] for field in unique_check]) 451 if row_data in seen_data: 452 errors.append(self.get_unique_error_message(unique_check)) 453 break 454 else: 455 seen_data.add(row_data) 456 if errors: 457 raise ValidationError(errors) 458 459 def get_unique_error_message(self, unique_check): 460 if len(unique_check) == 1: 461 return _("You have entered duplicate data for %(field)s. It " 462 "should be unique.") % { 463 "field": unique_check[0], 464 } 465 else: 466 return _("You have entered duplicate data for %(field)s. They " 467 "should be unique together.") % { 468 "field": get_text_list(unique_check, _("and")), 469 } 470 417 471 def save_existing_objects(self, commit=True): 418 472 self.changed_objects = [] 419 473 self.deleted_objects = [] … … class BaseInlineFormSet(BaseModelFormSet): 564 618 label=getattr(form.fields.get(self.fk.name), 'label', capfirst(self.fk.verbose_name)) 565 619 ) 566 620 621 def get_unique_error_message(self, unique_check): 622 unique_check = [field for field in unique_check if field != self.fk.name] 623 return super(BaseInlineFormSet, self).get_unique_error_message(unique_check) 624 567 625 def _get_foreign_key(parent_model, model, fk_name=None): 568 626 """ 569 627 Finds and returns the ForeignKey from model to parent if there is one. -
docs/topics/forms/modelforms.txt
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index be67a38..81d4642 100644
a b exclude:: 515 515 516 516 .. _saving-objects-in-the-formset: 517 517 518 Overriding clean() method 519 ------------------------- 520 521 You can override the ``clean()`` method to provide custom validation to 522 the whole formset at once. By default, the ``clean()`` method will validate 523 that none of the data in the formsets violate the unique constraints on your 524 model (both field ``unique`` and model ``unique_together``). To maintain this 525 default behavior be sure you call the parent's ``clean()`` method:: 526 527 class MyModelFormSet(BaseModelFormSet): 528 def clean(self): 529 super(MyModelFormSet, self).clean() 530 # example custom validation across forms in the formset: 531 for form in self.forms: 532 # your custom formset validation 533 518 534 Saving objects in the formset 519 535 ----------------------------- 520 536 … … than that of a "normal" formset. The only difference is that we call 599 615 ``formset.save()`` to save the data into the database. (This was described 600 616 above, in :ref:`saving-objects-in-the-formset`.) 601 617 618 619 Overiding ``clean()`` on a ``model_formset`` 620 -------------------------------------------- 621 622 Just like with ``ModelForms``, by default the ``clean()`` method of a 623 ``model_formset`` will validate that none of the items in the formset validate 624 the unique constraints on your model(either unique or unique_together). If you 625 want to overide the ``clean()`` method on a ``model_formset`` and maintain this 626 validation, you must call the parent classes ``clean`` method. 627 628 602 629 Using a custom queryset 603 630 ~~~~~~~~~~~~~~~~~~~~~~~ 604 631 -
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 """}