mirror of https://github.com/django/django.git
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)
|