mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			369 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			369 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from django import forms
 | |
| from django.contrib.contenttypes.forms import generic_inlineformset_factory
 | |
| from django.contrib.contenttypes.models import ContentType
 | |
| from django.db import models
 | |
| from django.test import TestCase
 | |
| from django.test.utils import isolate_apps
 | |
| 
 | |
| from .models import (
 | |
|     Animal,
 | |
|     ForProxyModelModel,
 | |
|     Gecko,
 | |
|     Mineral,
 | |
|     ProxyRelatedModel,
 | |
|     TaggedItem,
 | |
| )
 | |
| 
 | |
| 
 | |
| class CustomWidget(forms.TextInput):
 | |
|     pass
 | |
| 
 | |
| 
 | |
| class TaggedItemForm(forms.ModelForm):
 | |
|     class Meta:
 | |
|         model = TaggedItem
 | |
|         fields = "__all__"
 | |
|         widgets = {"tag": CustomWidget}
 | |
| 
 | |
| 
 | |
| class GenericInlineFormsetTests(TestCase):
 | |
|     def test_output(self):
 | |
|         GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
 | |
|         formset = GenericFormSet()
 | |
|         self.assertHTMLEqual(
 | |
|             "".join(form.as_p() for form in formset.forms),
 | |
|             """
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-0-tag">
 | |
|             Tag:</label>
 | |
|             <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag"
 | |
|                 type="text"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-tag"
 | |
|                 maxlength="50"></p>
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
 | |
|             Delete:</label>
 | |
|             <input type="checkbox"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-DELETE"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
 | |
|             <input type="hidden"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-id"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-0-id"></p>
 | |
|             """,
 | |
|         )
 | |
|         formset = GenericFormSet(instance=Animal())
 | |
|         self.assertHTMLEqual(
 | |
|             "".join(form.as_p() for form in formset.forms),
 | |
|             """
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-0-tag">
 | |
|             Tag:</label>
 | |
|             <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag"
 | |
|                 type="text"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-tag"
 | |
|                 maxlength="50"></p>
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
 | |
|             Delete:</label>
 | |
|             <input type="checkbox"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-DELETE"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
 | |
|             <input type="hidden"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-id"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-0-id"></p>
 | |
|             """,
 | |
|         )
 | |
|         platypus = Animal.objects.create(
 | |
|             common_name="Platypus",
 | |
|             latin_name="Ornithorhynchus anatinus",
 | |
|         )
 | |
|         platypus.tags.create(tag="shiny")
 | |
|         GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
 | |
|         formset = GenericFormSet(instance=platypus)
 | |
|         tagged_item_id = TaggedItem.objects.get(tag="shiny", object_id=platypus.id).id
 | |
|         self.assertHTMLEqual(
 | |
|             "".join(form.as_p() for form in formset.forms),
 | |
|             """
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-0-tag">
 | |
|             Tag:</label>
 | |
|             <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag"
 | |
|                 type="text"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-tag"
 | |
|                 value="shiny" maxlength="50"></p>
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
 | |
|             Delete:</label>
 | |
|             <input type="checkbox"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-DELETE"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
 | |
|             <input type="hidden"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-0-id"
 | |
|                 value="%s"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-0-id"></p>
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-1-tag">
 | |
|             Tag:</label>
 | |
|             <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag"
 | |
|                 type="text"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-1-tag"
 | |
|                 maxlength="50"></p>
 | |
|             <p><label
 | |
|                 for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">
 | |
|             Delete:</label>
 | |
|             <input type="checkbox"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-1-DELETE"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">
 | |
|             <input type="hidden"
 | |
|                 name="generic_relations-taggeditem-content_type-object_id-1-id"
 | |
|                 id="id_generic_relations-taggeditem-content_type-object_id-1-id"></p>
 | |
|             """
 | |
|             % tagged_item_id,
 | |
|         )
 | |
|         lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo")
 | |
|         formset = GenericFormSet(instance=lion, prefix="x")
 | |
|         self.assertHTMLEqual(
 | |
|             "".join(form.as_p() for form in formset.forms),
 | |
|             """
 | |
|             <p><label for="id_x-0-tag">Tag:</label>
 | |
|             <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50"></p>
 | |
|             <p><label for="id_x-0-DELETE">Delete:</label>
 | |
|             <input type="checkbox" name="x-0-DELETE" id="id_x-0-DELETE">
 | |
|             <input type="hidden" name="x-0-id" id="id_x-0-id"></p>
 | |
|             """,
 | |
|         )
 | |
| 
 | |
|     def test_options(self):
 | |
|         TaggedItemFormSet = generic_inlineformset_factory(
 | |
|             TaggedItem,
 | |
|             can_delete=False,
 | |
|             exclude=["tag"],
 | |
|             extra=3,
 | |
|         )
 | |
|         platypus = Animal.objects.create(
 | |
|             common_name="Platypus", latin_name="Ornithorhynchus anatinus"
 | |
|         )
 | |
|         harmless = platypus.tags.create(tag="harmless")
 | |
|         mammal = platypus.tags.create(tag="mammal")
 | |
|         # Works without a queryset.
 | |
|         formset = TaggedItemFormSet(instance=platypus)
 | |
|         self.assertEqual(len(formset.forms), 5)
 | |
|         self.assertHTMLEqual(
 | |
|             formset.forms[0].as_p(),
 | |
|             '<input type="hidden" '
 | |
|             'name="generic_relations-taggeditem-content_type-object_id-0-id" '
 | |
|             'value="%s" '
 | |
|             'id="id_generic_relations-taggeditem-content_type-object_id-0-id">'
 | |
|             % harmless.pk,
 | |
|         )
 | |
|         self.assertEqual(formset.forms[0].instance, harmless)
 | |
|         self.assertEqual(formset.forms[1].instance, mammal)
 | |
|         self.assertIsNone(formset.forms[2].instance.pk)
 | |
|         # A queryset can be used to alter display ordering.
 | |
|         formset = TaggedItemFormSet(
 | |
|             instance=platypus, queryset=TaggedItem.objects.order_by("-tag")
 | |
|         )
 | |
|         self.assertEqual(len(formset.forms), 5)
 | |
|         self.assertEqual(formset.forms[0].instance, mammal)
 | |
|         self.assertEqual(formset.forms[1].instance, harmless)
 | |
|         self.assertIsNone(formset.forms[2].instance.pk)
 | |
|         # A queryset that omits items.
 | |
|         formset = TaggedItemFormSet(
 | |
|             instance=platypus,
 | |
|             queryset=TaggedItem.objects.filter(tag__startswith="harm"),
 | |
|         )
 | |
|         self.assertEqual(len(formset.forms), 4)
 | |
|         self.assertEqual(formset.forms[0].instance, harmless)
 | |
|         self.assertIsNone(formset.forms[1].instance.pk)
 | |
| 
 | |
|     def test_get_queryset_ordering(self):
 | |
|         """
 | |
|         BaseGenericInlineFormSet.get_queryset() adds default ordering, if
 | |
|         needed.
 | |
|         """
 | |
|         inline_formset = generic_inlineformset_factory(TaggedItem, exclude=("tag",))
 | |
|         formset = inline_formset(instance=Gecko.objects.create())
 | |
|         self.assertIs(formset.get_queryset().ordered, True)
 | |
| 
 | |
|     def test_initial(self):
 | |
|         quartz = Mineral.objects.create(name="Quartz", hardness=7)
 | |
|         GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
 | |
|         ctype = ContentType.objects.get_for_model(quartz)
 | |
|         initial_data = [
 | |
|             {
 | |
|                 "tag": "lizard",
 | |
|                 "content_type": ctype.pk,
 | |
|                 "object_id": quartz.pk,
 | |
|             }
 | |
|         ]
 | |
|         formset = GenericFormSet(initial=initial_data)
 | |
|         self.assertEqual(formset.forms[0].initial, initial_data[0])
 | |
| 
 | |
|     def test_meta_widgets(self):
 | |
|         """TaggedItemForm has a widget defined in Meta."""
 | |
|         Formset = generic_inlineformset_factory(TaggedItem, TaggedItemForm)
 | |
|         form = Formset().forms[0]
 | |
|         self.assertIsInstance(form["tag"].field.widget, CustomWidget)
 | |
| 
 | |
|     @isolate_apps("generic_relations")
 | |
|     def test_incorrect_content_type(self):
 | |
|         class BadModel(models.Model):
 | |
|             content_type = models.PositiveIntegerField()
 | |
| 
 | |
|         msg = (
 | |
|             "fk_name 'generic_relations.BadModel.content_type' is not a ForeignKey to "
 | |
|             "ContentType"
 | |
|         )
 | |
|         with self.assertRaisesMessage(Exception, msg):
 | |
|             generic_inlineformset_factory(BadModel, TaggedItemForm)
 | |
| 
 | |
|     def test_save_new_uses_form_save(self):
 | |
|         class SaveTestForm(forms.ModelForm):
 | |
|             def save(self, *args, **kwargs):
 | |
|                 self.instance.saved_by = "custom method"
 | |
|                 return super().save(*args, **kwargs)
 | |
| 
 | |
|         Formset = generic_inlineformset_factory(
 | |
|             ForProxyModelModel, fields="__all__", form=SaveTestForm
 | |
|         )
 | |
|         instance = ProxyRelatedModel.objects.create()
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "1",
 | |
|             "form-INITIAL_FORMS": "0",
 | |
|             "form-MAX_NUM_FORMS": "",
 | |
|             "form-0-title": "foo",
 | |
|         }
 | |
|         formset = Formset(data, instance=instance, prefix="form")
 | |
|         self.assertTrue(formset.is_valid())
 | |
|         new_obj = formset.save()[0]
 | |
|         self.assertEqual(new_obj.saved_by, "custom method")
 | |
| 
 | |
|     def test_save_new_for_proxy(self):
 | |
|         Formset = generic_inlineformset_factory(
 | |
|             ForProxyModelModel, fields="__all__", for_concrete_model=False
 | |
|         )
 | |
|         instance = ProxyRelatedModel.objects.create()
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "1",
 | |
|             "form-INITIAL_FORMS": "0",
 | |
|             "form-MAX_NUM_FORMS": "",
 | |
|             "form-0-title": "foo",
 | |
|         }
 | |
|         formset = Formset(data, instance=instance, prefix="form")
 | |
|         self.assertTrue(formset.is_valid())
 | |
|         (new_obj,) = formset.save()
 | |
|         self.assertEqual(new_obj.obj, instance)
 | |
| 
 | |
|     def test_save_new_for_concrete(self):
 | |
|         Formset = generic_inlineformset_factory(
 | |
|             ForProxyModelModel, fields="__all__", for_concrete_model=True
 | |
|         )
 | |
|         instance = ProxyRelatedModel.objects.create()
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "1",
 | |
|             "form-INITIAL_FORMS": "0",
 | |
|             "form-MAX_NUM_FORMS": "",
 | |
|             "form-0-title": "foo",
 | |
|         }
 | |
|         formset = Formset(data, instance=instance, prefix="form")
 | |
|         self.assertTrue(formset.is_valid())
 | |
|         (new_obj,) = formset.save()
 | |
|         self.assertNotIsInstance(new_obj.obj, ProxyRelatedModel)
 | |
| 
 | |
|     def test_initial_count(self):
 | |
|         GenericFormSet = generic_inlineformset_factory(TaggedItem)
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "3",
 | |
|             "form-INITIAL_FORMS": "3",
 | |
|             "form-MAX_NUM_FORMS": "",
 | |
|         }
 | |
|         formset = GenericFormSet(data=data, prefix="form")
 | |
|         self.assertEqual(formset.initial_form_count(), 3)
 | |
|         formset = GenericFormSet(data=data, prefix="form", save_as_new=True)
 | |
|         self.assertEqual(formset.initial_form_count(), 0)
 | |
| 
 | |
|     def test_save_as_new(self):
 | |
|         """
 | |
|         The save_as_new parameter creates new items that are associated with
 | |
|         the object.
 | |
|         """
 | |
|         lion = Animal.objects.create(common_name="Lion", latin_name="Panthera leo")
 | |
|         yellow = lion.tags.create(tag="yellow")
 | |
|         hairy = lion.tags.create(tag="hairy")
 | |
|         GenericFormSet = generic_inlineformset_factory(TaggedItem)
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "3",
 | |
|             "form-INITIAL_FORMS": "2",
 | |
|             "form-MAX_NUM_FORMS": "",
 | |
|             "form-0-id": str(yellow.pk),
 | |
|             "form-0-tag": "hunts",
 | |
|             "form-1-id": str(hairy.pk),
 | |
|             "form-1-tag": "roars",
 | |
|         }
 | |
|         formset = GenericFormSet(data, instance=lion, prefix="form", save_as_new=True)
 | |
|         self.assertTrue(formset.is_valid())
 | |
|         tags = formset.save()
 | |
|         self.assertEqual([tag.tag for tag in tags], ["hunts", "roars"])
 | |
|         hunts, roars = tags
 | |
|         self.assertSequenceEqual(
 | |
|             lion.tags.order_by("tag"), [hairy, hunts, roars, yellow]
 | |
|         )
 | |
| 
 | |
|     def test_absolute_max(self):
 | |
|         GenericFormSet = generic_inlineformset_factory(TaggedItem, absolute_max=1500)
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "1501",
 | |
|             "form-INITIAL_FORMS": "0",
 | |
|             "form-MAX_NUM_FORMS": "0",
 | |
|         }
 | |
|         formset = GenericFormSet(data=data, prefix="form")
 | |
|         self.assertIs(formset.is_valid(), False)
 | |
|         self.assertEqual(len(formset.forms), 1500)
 | |
|         self.assertEqual(
 | |
|             formset.non_form_errors(),
 | |
|             ["Please submit at most 1000 forms."],
 | |
|         )
 | |
| 
 | |
|     def test_absolute_max_with_max_num(self):
 | |
|         GenericFormSet = generic_inlineformset_factory(
 | |
|             TaggedItem,
 | |
|             max_num=20,
 | |
|             absolute_max=100,
 | |
|         )
 | |
|         data = {
 | |
|             "form-TOTAL_FORMS": "101",
 | |
|             "form-INITIAL_FORMS": "0",
 | |
|             "form-MAX_NUM_FORMS": "0",
 | |
|         }
 | |
|         formset = GenericFormSet(data=data, prefix="form")
 | |
|         self.assertIs(formset.is_valid(), False)
 | |
|         self.assertEqual(len(formset.forms), 100)
 | |
|         self.assertEqual(
 | |
|             formset.non_form_errors(),
 | |
|             ["Please submit at most 20 forms."],
 | |
|         )
 | |
| 
 | |
|     def test_can_delete_extra(self):
 | |
|         GenericFormSet = generic_inlineformset_factory(
 | |
|             TaggedItem,
 | |
|             can_delete=True,
 | |
|             can_delete_extra=True,
 | |
|             extra=2,
 | |
|         )
 | |
|         formset = GenericFormSet()
 | |
|         self.assertEqual(len(formset), 2)
 | |
|         self.assertIn("DELETE", formset.forms[0].fields)
 | |
|         self.assertIn("DELETE", formset.forms[1].fields)
 | |
| 
 | |
|     def test_disable_delete_extra(self):
 | |
|         GenericFormSet = generic_inlineformset_factory(
 | |
|             TaggedItem,
 | |
|             can_delete=True,
 | |
|             can_delete_extra=False,
 | |
|             extra=2,
 | |
|         )
 | |
|         formset = GenericFormSet()
 | |
|         self.assertEqual(len(formset), 2)
 | |
|         self.assertNotIn("DELETE", formset.forms[0].fields)
 | |
|         self.assertNotIn("DELETE", formset.forms[1].fields)
 |