test_forms.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. from django import forms
  2. from django.contrib.contenttypes.forms import generic_inlineformset_factory
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.db import models
  5. from django.test import TestCase
  6. from django.test.utils import isolate_apps
  7. from .models import (
  8. Animal, ForProxyModelModel, Gecko, Mineral, ProxyRelatedModel, TaggedItem,
  9. )
  10. class CustomWidget(forms.TextInput):
  11. pass
  12. class TaggedItemForm(forms.ModelForm):
  13. class Meta:
  14. model = TaggedItem
  15. fields = '__all__'
  16. widgets = {'tag': CustomWidget}
  17. class GenericInlineFormsetTests(TestCase):
  18. def test_output(self):
  19. GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
  20. formset = GenericFormSet()
  21. self.assertHTMLEqual(
  22. ''.join(form.as_p() for form in formset.forms),
  23. """<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">
  24. Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text"
  25. name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50"></p>
  26. <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label>
  27. <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE"
  28. id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
  29. <input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id"
  30. id="id_generic_relations-taggeditem-content_type-object_id-0-id"></p>"""
  31. )
  32. formset = GenericFormSet(instance=Animal())
  33. self.assertHTMLEqual(
  34. ''.join(form.as_p() for form in formset.forms),
  35. """<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">
  36. Tag:</label> <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag"
  37. type="text" name="generic_relations-taggeditem-content_type-object_id-0-tag" maxlength="50"></p>
  38. <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label>
  39. <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE"
  40. id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE"><input type="hidden"
  41. name="generic_relations-taggeditem-content_type-object_id-0-id"
  42. id="id_generic_relations-taggeditem-content_type-object_id-0-id"></p>"""
  43. )
  44. platypus = Animal.objects.create(
  45. common_name='Platypus', latin_name='Ornithorhynchus anatinus',
  46. )
  47. platypus.tags.create(tag='shiny')
  48. GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
  49. formset = GenericFormSet(instance=platypus)
  50. tagged_item_id = TaggedItem.objects.get(tag='shiny', object_id=platypus.id).id
  51. self.assertHTMLEqual(
  52. ''.join(form.as_p() for form in formset.forms),
  53. """<p><label for="id_generic_relations-taggeditem-content_type-object_id-0-tag">Tag:</label>
  54. <input id="id_generic_relations-taggeditem-content_type-object_id-0-tag" type="text"
  55. name="generic_relations-taggeditem-content_type-object_id-0-tag" value="shiny" maxlength="50"></p>
  56. <p><label for="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">Delete:</label>
  57. <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-0-DELETE"
  58. id="id_generic_relations-taggeditem-content_type-object_id-0-DELETE">
  59. <input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id"
  60. value="%s" id="id_generic_relations-taggeditem-content_type-object_id-0-id"></p>
  61. <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-tag">Tag:</label>
  62. <input id="id_generic_relations-taggeditem-content_type-object_id-1-tag" type="text"
  63. name="generic_relations-taggeditem-content_type-object_id-1-tag" maxlength="50"></p>
  64. <p><label for="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">Delete:</label>
  65. <input type="checkbox" name="generic_relations-taggeditem-content_type-object_id-1-DELETE"
  66. id="id_generic_relations-taggeditem-content_type-object_id-1-DELETE">
  67. <input type="hidden" name="generic_relations-taggeditem-content_type-object_id-1-id"
  68. id="id_generic_relations-taggeditem-content_type-object_id-1-id"></p>""" % tagged_item_id
  69. )
  70. lion = Animal.objects.create(common_name='Lion', latin_name='Panthera leo')
  71. formset = GenericFormSet(instance=lion, prefix='x')
  72. self.assertHTMLEqual(
  73. ''.join(form.as_p() for form in formset.forms),
  74. """<p><label for="id_x-0-tag">Tag:</label>
  75. <input id="id_x-0-tag" type="text" name="x-0-tag" maxlength="50"></p>
  76. <p><label for="id_x-0-DELETE">Delete:</label> <input type="checkbox" name="x-0-DELETE" id="id_x-0-DELETE">
  77. <input type="hidden" name="x-0-id" id="id_x-0-id"></p>"""
  78. )
  79. def test_options(self):
  80. TaggedItemFormSet = generic_inlineformset_factory(
  81. TaggedItem,
  82. can_delete=False,
  83. exclude=['tag'],
  84. extra=3,
  85. )
  86. platypus = Animal.objects.create(common_name='Platypus', latin_name='Ornithorhynchus anatinus')
  87. harmless = platypus.tags.create(tag='harmless')
  88. mammal = platypus.tags.create(tag='mammal')
  89. # Works without a queryset.
  90. formset = TaggedItemFormSet(instance=platypus)
  91. self.assertEqual(len(formset.forms), 5)
  92. self.assertHTMLEqual(
  93. formset.forms[0].as_p(),
  94. '<input type="hidden" name="generic_relations-taggeditem-content_type-object_id-0-id" value="%s" '
  95. 'id="id_generic_relations-taggeditem-content_type-object_id-0-id">' % harmless.pk
  96. )
  97. self.assertEqual(formset.forms[0].instance, harmless)
  98. self.assertEqual(formset.forms[1].instance, mammal)
  99. self.assertIsNone(formset.forms[2].instance.pk)
  100. # A queryset can be used to alter display ordering.
  101. formset = TaggedItemFormSet(instance=platypus, queryset=TaggedItem.objects.order_by('-tag'))
  102. self.assertEqual(len(formset.forms), 5)
  103. self.assertEqual(formset.forms[0].instance, mammal)
  104. self.assertEqual(formset.forms[1].instance, harmless)
  105. self.assertIsNone(formset.forms[2].instance.pk)
  106. # A queryset that omits items.
  107. formset = TaggedItemFormSet(instance=platypus, queryset=TaggedItem.objects.filter(tag__startswith='harm'))
  108. self.assertEqual(len(formset.forms), 4)
  109. self.assertEqual(formset.forms[0].instance, harmless)
  110. self.assertIsNone(formset.forms[1].instance.pk)
  111. def test_get_queryset_ordering(self):
  112. """
  113. BaseGenericInlineFormSet.get_queryset() adds default ordering, if
  114. needed.
  115. """
  116. inline_formset = generic_inlineformset_factory(TaggedItem, exclude=('tag',))
  117. formset = inline_formset(instance=Gecko.objects.create())
  118. self.assertIs(formset.get_queryset().ordered, True)
  119. def test_initial(self):
  120. quartz = Mineral.objects.create(name='Quartz', hardness=7)
  121. GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
  122. ctype = ContentType.objects.get_for_model(quartz)
  123. initial_data = [{
  124. 'tag': 'lizard',
  125. 'content_type': ctype.pk,
  126. 'object_id': quartz.pk,
  127. }]
  128. formset = GenericFormSet(initial=initial_data)
  129. self.assertEqual(formset.forms[0].initial, initial_data[0])
  130. def test_meta_widgets(self):
  131. """TaggedItemForm has a widget defined in Meta."""
  132. Formset = generic_inlineformset_factory(TaggedItem, TaggedItemForm)
  133. form = Formset().forms[0]
  134. self.assertIsInstance(form['tag'].field.widget, CustomWidget)
  135. @isolate_apps('generic_relations')
  136. def test_incorrect_content_type(self):
  137. class BadModel(models.Model):
  138. content_type = models.PositiveIntegerField()
  139. msg = "fk_name 'generic_relations.BadModel.content_type' is not a ForeignKey to ContentType"
  140. with self.assertRaisesMessage(Exception, msg):
  141. generic_inlineformset_factory(BadModel, TaggedItemForm)
  142. def test_save_new_uses_form_save(self):
  143. class SaveTestForm(forms.ModelForm):
  144. def save(self, *args, **kwargs):
  145. self.instance.saved_by = 'custom method'
  146. return super().save(*args, **kwargs)
  147. Formset = generic_inlineformset_factory(ForProxyModelModel, fields='__all__', form=SaveTestForm)
  148. instance = ProxyRelatedModel.objects.create()
  149. data = {
  150. 'form-TOTAL_FORMS': '1',
  151. 'form-INITIAL_FORMS': '0',
  152. 'form-MAX_NUM_FORMS': '',
  153. 'form-0-title': 'foo',
  154. }
  155. formset = Formset(data, instance=instance, prefix='form')
  156. self.assertTrue(formset.is_valid())
  157. new_obj = formset.save()[0]
  158. self.assertEqual(new_obj.saved_by, 'custom method')
  159. def test_save_new_for_proxy(self):
  160. Formset = generic_inlineformset_factory(ForProxyModelModel, fields='__all__', for_concrete_model=False)
  161. instance = ProxyRelatedModel.objects.create()
  162. data = {
  163. 'form-TOTAL_FORMS': '1',
  164. 'form-INITIAL_FORMS': '0',
  165. 'form-MAX_NUM_FORMS': '',
  166. 'form-0-title': 'foo',
  167. }
  168. formset = Formset(data, instance=instance, prefix='form')
  169. self.assertTrue(formset.is_valid())
  170. new_obj, = formset.save()
  171. self.assertEqual(new_obj.obj, instance)
  172. def test_save_new_for_concrete(self):
  173. Formset = generic_inlineformset_factory(ForProxyModelModel, fields='__all__', for_concrete_model=True)
  174. instance = ProxyRelatedModel.objects.create()
  175. data = {
  176. 'form-TOTAL_FORMS': '1',
  177. 'form-INITIAL_FORMS': '0',
  178. 'form-MAX_NUM_FORMS': '',
  179. 'form-0-title': 'foo',
  180. }
  181. formset = Formset(data, instance=instance, prefix='form')
  182. self.assertTrue(formset.is_valid())
  183. new_obj, = formset.save()
  184. self.assertNotIsInstance(new_obj.obj, ProxyRelatedModel)
  185. def test_initial_count(self):
  186. GenericFormSet = generic_inlineformset_factory(TaggedItem)
  187. data = {
  188. 'form-TOTAL_FORMS': '3',
  189. 'form-INITIAL_FORMS': '3',
  190. 'form-MAX_NUM_FORMS': '',
  191. }
  192. formset = GenericFormSet(data=data, prefix='form')
  193. self.assertEqual(formset.initial_form_count(), 3)
  194. formset = GenericFormSet(data=data, prefix='form', save_as_new=True)
  195. self.assertEqual(formset.initial_form_count(), 0)
  196. def test_save_as_new(self):
  197. """
  198. The save_as_new parameter creates new items that are associated with
  199. the object.
  200. """
  201. lion = Animal.objects.create(common_name='Lion', latin_name='Panthera leo')
  202. yellow = lion.tags.create(tag='yellow')
  203. hairy = lion.tags.create(tag='hairy')
  204. GenericFormSet = generic_inlineformset_factory(TaggedItem)
  205. data = {
  206. 'form-TOTAL_FORMS': '3',
  207. 'form-INITIAL_FORMS': '2',
  208. 'form-MAX_NUM_FORMS': '',
  209. 'form-0-id': str(yellow.pk),
  210. 'form-0-tag': 'hunts',
  211. 'form-1-id': str(hairy.pk),
  212. 'form-1-tag': 'roars',
  213. }
  214. formset = GenericFormSet(data, instance=lion, prefix='form', save_as_new=True)
  215. self.assertTrue(formset.is_valid())
  216. tags = formset.save()
  217. self.assertEqual([tag.tag for tag in tags], ['hunts', 'roars'])
  218. hunts, roars = tags
  219. self.assertSequenceEqual(lion.tags.order_by('tag'), [hairy, hunts, roars, yellow])
  220. def test_absolute_max(self):
  221. GenericFormSet = generic_inlineformset_factory(TaggedItem, absolute_max=1500)
  222. data = {
  223. 'form-TOTAL_FORMS': '1501',
  224. 'form-INITIAL_FORMS': '0',
  225. 'form-MAX_NUM_FORMS': '0',
  226. }
  227. formset = GenericFormSet(data=data, prefix='form')
  228. self.assertIs(formset.is_valid(), False)
  229. self.assertEqual(len(formset.forms), 1500)
  230. self.assertEqual(
  231. formset.non_form_errors(),
  232. ['Please submit at most 1000 forms.'],
  233. )
  234. def test_absolute_max_with_max_num(self):
  235. GenericFormSet = generic_inlineformset_factory(
  236. TaggedItem,
  237. max_num=20,
  238. absolute_max=100,
  239. )
  240. data = {
  241. 'form-TOTAL_FORMS': '101',
  242. 'form-INITIAL_FORMS': '0',
  243. 'form-MAX_NUM_FORMS': '0',
  244. }
  245. formset = GenericFormSet(data=data, prefix='form')
  246. self.assertIs(formset.is_valid(), False)
  247. self.assertEqual(len(formset.forms), 100)
  248. self.assertEqual(
  249. formset.non_form_errors(),
  250. ['Please submit at most 20 forms.'],
  251. )
  252. def test_can_delete_extra(self):
  253. GenericFormSet = generic_inlineformset_factory(
  254. TaggedItem,
  255. can_delete=True,
  256. can_delete_extra=True,
  257. extra=2,
  258. )
  259. formset = GenericFormSet()
  260. self.assertEqual(len(formset), 2)
  261. self.assertIn('DELETE', formset.forms[0].fields)
  262. self.assertIn('DELETE', formset.forms[1].fields)
  263. def test_disable_delete_extra(self):
  264. GenericFormSet = generic_inlineformset_factory(
  265. TaggedItem,
  266. can_delete=True,
  267. can_delete_extra=False,
  268. extra=2,
  269. )
  270. formset = GenericFormSet()
  271. self.assertEqual(len(formset), 2)
  272. self.assertNotIn('DELETE', formset.forms[0].fields)
  273. self.assertNotIn('DELETE', formset.forms[1].fields)