tests.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457
  1. from django.contrib import admin
  2. from django.contrib.admin.sites import AdminSite
  3. from django.contrib.auth.models import User
  4. from django.contrib.contenttypes.admin import GenericTabularInline
  5. from django.contrib.contenttypes.models import ContentType
  6. from django.forms.formsets import DEFAULT_MAX_NUM
  7. from django.forms.models import ModelForm
  8. from django.test import (
  9. RequestFactory, SimpleTestCase, TestCase, override_settings,
  10. )
  11. from django.urls import reverse
  12. from .admin import MediaInline, MediaPermanentInline, site as admin_site
  13. from .models import Category, Episode, EpisodePermanent, Media, PhoneNumber
  14. class TestDataMixin:
  15. @classmethod
  16. def setUpTestData(cls):
  17. cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com')
  18. @override_settings(ROOT_URLCONF='generic_inline_admin.urls')
  19. class GenericAdminViewTest(TestDataMixin, TestCase):
  20. def setUp(self):
  21. self.client.force_login(self.superuser)
  22. e = Episode.objects.create(name='This Week in Django')
  23. self.episode_pk = e.pk
  24. m = Media(content_object=e, url='http://example.com/podcast.mp3')
  25. m.save()
  26. self.mp3_media_pk = m.pk
  27. m = Media(content_object=e, url='http://example.com/logo.png')
  28. m.save()
  29. self.png_media_pk = m.pk
  30. def test_basic_add_GET(self):
  31. """
  32. A smoke test to ensure GET on the add_view works.
  33. """
  34. response = self.client.get(reverse('admin:generic_inline_admin_episode_add'))
  35. self.assertEqual(response.status_code, 200)
  36. def test_basic_edit_GET(self):
  37. """
  38. A smoke test to ensure GET on the change_view works.
  39. """
  40. response = self.client.get(
  41. reverse('admin:generic_inline_admin_episode_change', args=(self.episode_pk,))
  42. )
  43. self.assertEqual(response.status_code, 200)
  44. def test_basic_add_POST(self):
  45. """
  46. A smoke test to ensure POST on add_view works.
  47. """
  48. post_data = {
  49. "name": "This Week in Django",
  50. # inline data
  51. "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": "1",
  52. "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": "0",
  53. "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": "0",
  54. }
  55. response = self.client.post(reverse('admin:generic_inline_admin_episode_add'), post_data)
  56. self.assertEqual(response.status_code, 302) # redirect somewhere
  57. def test_basic_edit_POST(self):
  58. """
  59. A smoke test to ensure POST on edit_view works.
  60. """
  61. post_data = {
  62. "name": "This Week in Django",
  63. # inline data
  64. "generic_inline_admin-media-content_type-object_id-TOTAL_FORMS": "3",
  65. "generic_inline_admin-media-content_type-object_id-INITIAL_FORMS": "2",
  66. "generic_inline_admin-media-content_type-object_id-MAX_NUM_FORMS": "0",
  67. "generic_inline_admin-media-content_type-object_id-0-id": str(self.mp3_media_pk),
  68. "generic_inline_admin-media-content_type-object_id-0-url": "http://example.com/podcast.mp3",
  69. "generic_inline_admin-media-content_type-object_id-1-id": str(self.png_media_pk),
  70. "generic_inline_admin-media-content_type-object_id-1-url": "http://example.com/logo.png",
  71. "generic_inline_admin-media-content_type-object_id-2-id": "",
  72. "generic_inline_admin-media-content_type-object_id-2-url": "",
  73. }
  74. url = reverse('admin:generic_inline_admin_episode_change', args=(self.episode_pk,))
  75. response = self.client.post(url, post_data)
  76. self.assertEqual(response.status_code, 302) # redirect somewhere
  77. @override_settings(ROOT_URLCONF='generic_inline_admin.urls')
  78. class GenericInlineAdminParametersTest(TestDataMixin, TestCase):
  79. factory = RequestFactory()
  80. def setUp(self):
  81. self.client.force_login(self.superuser)
  82. def _create_object(self, model):
  83. """
  84. Create a model with an attached Media object via GFK. We can't
  85. load content via a fixture (since the GenericForeignKey relies on
  86. content type IDs, which will vary depending on what other tests
  87. have been run), thus we do it here.
  88. """
  89. e = model.objects.create(name='This Week in Django')
  90. Media.objects.create(content_object=e, url='http://example.com/podcast.mp3')
  91. return e
  92. def test_no_param(self):
  93. """
  94. With one initial form, extra (default) at 3, there should be 4 forms.
  95. """
  96. e = self._create_object(Episode)
  97. response = self.client.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  98. formset = response.context['inline_admin_formsets'][0].formset
  99. self.assertEqual(formset.total_form_count(), 4)
  100. self.assertEqual(formset.initial_form_count(), 1)
  101. def test_extra_param(self):
  102. """
  103. With extra=0, there should be one form.
  104. """
  105. class ExtraInline(GenericTabularInline):
  106. model = Media
  107. extra = 0
  108. modeladmin = admin.ModelAdmin(Episode, admin_site)
  109. modeladmin.inlines = [ExtraInline]
  110. e = self._create_object(Episode)
  111. request = self.factory.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  112. request.user = User(username='super', is_superuser=True)
  113. response = modeladmin.changeform_view(request, object_id=str(e.pk))
  114. formset = response.context_data['inline_admin_formsets'][0].formset
  115. self.assertEqual(formset.total_form_count(), 1)
  116. self.assertEqual(formset.initial_form_count(), 1)
  117. def test_max_num_param(self):
  118. """
  119. With extra=5 and max_num=2, there should be only 2 forms.
  120. """
  121. class MaxNumInline(GenericTabularInline):
  122. model = Media
  123. extra = 5
  124. max_num = 2
  125. modeladmin = admin.ModelAdmin(Episode, admin_site)
  126. modeladmin.inlines = [MaxNumInline]
  127. e = self._create_object(Episode)
  128. request = self.factory.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  129. request.user = User(username='super', is_superuser=True)
  130. response = modeladmin.changeform_view(request, object_id=str(e.pk))
  131. formset = response.context_data['inline_admin_formsets'][0].formset
  132. self.assertEqual(formset.total_form_count(), 2)
  133. self.assertEqual(formset.initial_form_count(), 1)
  134. def test_min_num_param(self):
  135. """
  136. With extra=3 and min_num=2, there should be five forms.
  137. """
  138. class MinNumInline(GenericTabularInline):
  139. model = Media
  140. extra = 3
  141. min_num = 2
  142. modeladmin = admin.ModelAdmin(Episode, admin_site)
  143. modeladmin.inlines = [MinNumInline]
  144. e = self._create_object(Episode)
  145. request = self.factory.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  146. request.user = User(username='super', is_superuser=True)
  147. response = modeladmin.changeform_view(request, object_id=str(e.pk))
  148. formset = response.context_data['inline_admin_formsets'][0].formset
  149. self.assertEqual(formset.total_form_count(), 5)
  150. self.assertEqual(formset.initial_form_count(), 1)
  151. def test_get_extra(self):
  152. class GetExtraInline(GenericTabularInline):
  153. model = Media
  154. extra = 4
  155. def get_extra(self, request, obj):
  156. return 2
  157. modeladmin = admin.ModelAdmin(Episode, admin_site)
  158. modeladmin.inlines = [GetExtraInline]
  159. e = self._create_object(Episode)
  160. request = self.factory.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  161. request.user = User(username='super', is_superuser=True)
  162. response = modeladmin.changeform_view(request, object_id=str(e.pk))
  163. formset = response.context_data['inline_admin_formsets'][0].formset
  164. self.assertEqual(formset.extra, 2)
  165. def test_get_min_num(self):
  166. class GetMinNumInline(GenericTabularInline):
  167. model = Media
  168. min_num = 5
  169. def get_min_num(self, request, obj):
  170. return 2
  171. modeladmin = admin.ModelAdmin(Episode, admin_site)
  172. modeladmin.inlines = [GetMinNumInline]
  173. e = self._create_object(Episode)
  174. request = self.factory.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  175. request.user = User(username='super', is_superuser=True)
  176. response = modeladmin.changeform_view(request, object_id=str(e.pk))
  177. formset = response.context_data['inline_admin_formsets'][0].formset
  178. self.assertEqual(formset.min_num, 2)
  179. def test_get_max_num(self):
  180. class GetMaxNumInline(GenericTabularInline):
  181. model = Media
  182. extra = 5
  183. def get_max_num(self, request, obj):
  184. return 2
  185. modeladmin = admin.ModelAdmin(Episode, admin_site)
  186. modeladmin.inlines = [GetMaxNumInline]
  187. e = self._create_object(Episode)
  188. request = self.factory.get(reverse('admin:generic_inline_admin_episode_change', args=(e.pk,)))
  189. request.user = User(username='super', is_superuser=True)
  190. response = modeladmin.changeform_view(request, object_id=str(e.pk))
  191. formset = response.context_data['inline_admin_formsets'][0].formset
  192. self.assertEqual(formset.max_num, 2)
  193. @override_settings(ROOT_URLCONF='generic_inline_admin.urls')
  194. class GenericInlineAdminWithUniqueTogetherTest(TestDataMixin, TestCase):
  195. def setUp(self):
  196. self.client.force_login(self.superuser)
  197. def test_add(self):
  198. category_id = Category.objects.create(name='male').pk
  199. post_data = {
  200. "name": "John Doe",
  201. # inline data
  202. "generic_inline_admin-phonenumber-content_type-object_id-TOTAL_FORMS": "1",
  203. "generic_inline_admin-phonenumber-content_type-object_id-INITIAL_FORMS": "0",
  204. "generic_inline_admin-phonenumber-content_type-object_id-MAX_NUM_FORMS": "0",
  205. "generic_inline_admin-phonenumber-content_type-object_id-0-id": "",
  206. "generic_inline_admin-phonenumber-content_type-object_id-0-phone_number": "555-555-5555",
  207. "generic_inline_admin-phonenumber-content_type-object_id-0-category": str(category_id),
  208. }
  209. response = self.client.get(reverse('admin:generic_inline_admin_contact_add'))
  210. self.assertEqual(response.status_code, 200)
  211. response = self.client.post(reverse('admin:generic_inline_admin_contact_add'), post_data)
  212. self.assertEqual(response.status_code, 302) # redirect somewhere
  213. def test_delete(self):
  214. from .models import Contact
  215. c = Contact.objects.create(name='foo')
  216. PhoneNumber.objects.create(
  217. object_id=c.id,
  218. content_type=ContentType.objects.get_for_model(Contact),
  219. phone_number="555-555-5555",
  220. )
  221. response = self.client.post(reverse('admin:generic_inline_admin_contact_delete', args=[c.pk]))
  222. self.assertContains(response, 'Are you sure you want to delete')
  223. @override_settings(ROOT_URLCONF='generic_inline_admin.urls')
  224. class NoInlineDeletionTest(SimpleTestCase):
  225. def test_no_deletion(self):
  226. inline = MediaPermanentInline(EpisodePermanent, admin_site)
  227. fake_request = object()
  228. formset = inline.get_formset(fake_request)
  229. self.assertFalse(formset.can_delete)
  230. class MockRequest:
  231. pass
  232. class MockSuperUser:
  233. def has_perm(self, perm):
  234. return True
  235. request = MockRequest()
  236. request.user = MockSuperUser()
  237. @override_settings(ROOT_URLCONF='generic_inline_admin.urls')
  238. class GenericInlineModelAdminTest(SimpleTestCase):
  239. def setUp(self):
  240. self.site = AdminSite()
  241. def test_get_formset_kwargs(self):
  242. media_inline = MediaInline(Media, AdminSite())
  243. # Create a formset with default arguments
  244. formset = media_inline.get_formset(request)
  245. self.assertEqual(formset.max_num, DEFAULT_MAX_NUM)
  246. self.assertIs(formset.can_order, False)
  247. # Create a formset with custom keyword arguments
  248. formset = media_inline.get_formset(request, max_num=100, can_order=True)
  249. self.assertEqual(formset.max_num, 100)
  250. self.assertIs(formset.can_order, True)
  251. def test_custom_form_meta_exclude_with_readonly(self):
  252. """
  253. The custom ModelForm's `Meta.exclude` is respected when
  254. used in conjunction with `GenericInlineModelAdmin.readonly_fields`
  255. and when no `ModelAdmin.exclude` is defined.
  256. """
  257. class MediaForm(ModelForm):
  258. class Meta:
  259. model = Media
  260. exclude = ['url']
  261. class MediaInline(GenericTabularInline):
  262. readonly_fields = ['description']
  263. form = MediaForm
  264. model = Media
  265. class EpisodeAdmin(admin.ModelAdmin):
  266. inlines = [
  267. MediaInline
  268. ]
  269. ma = EpisodeAdmin(Episode, self.site)
  270. self.assertEqual(
  271. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  272. ['keywords', 'id', 'DELETE'])
  273. def test_custom_form_meta_exclude(self):
  274. """
  275. The custom ModelForm's `Meta.exclude` is respected by
  276. `GenericInlineModelAdmin.get_formset`, and overridden if
  277. `ModelAdmin.exclude` or `GenericInlineModelAdmin.exclude` are defined.
  278. Refs #15907.
  279. """
  280. # First with `GenericInlineModelAdmin` -----------------
  281. class MediaForm(ModelForm):
  282. class Meta:
  283. model = Media
  284. exclude = ['url']
  285. class MediaInline(GenericTabularInline):
  286. exclude = ['description']
  287. form = MediaForm
  288. model = Media
  289. class EpisodeAdmin(admin.ModelAdmin):
  290. inlines = [
  291. MediaInline
  292. ]
  293. ma = EpisodeAdmin(Episode, self.site)
  294. self.assertEqual(
  295. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  296. ['url', 'keywords', 'id', 'DELETE'])
  297. # Then, only with `ModelForm` -----------------
  298. class MediaInline(GenericTabularInline):
  299. form = MediaForm
  300. model = Media
  301. class EpisodeAdmin(admin.ModelAdmin):
  302. inlines = [
  303. MediaInline
  304. ]
  305. ma = EpisodeAdmin(Episode, self.site)
  306. self.assertEqual(
  307. list(list(ma.get_formsets_with_inlines(request))[0][0]().forms[0].fields),
  308. ['description', 'keywords', 'id', 'DELETE'])
  309. def test_get_fieldsets(self):
  310. # get_fieldsets is called when figuring out form fields.
  311. # Refs #18681.
  312. class MediaForm(ModelForm):
  313. class Meta:
  314. model = Media
  315. fields = '__all__'
  316. class MediaInline(GenericTabularInline):
  317. form = MediaForm
  318. model = Media
  319. can_delete = False
  320. def get_fieldsets(self, request, obj=None):
  321. return [(None, {'fields': ['url', 'description']})]
  322. ma = MediaInline(Media, self.site)
  323. form = ma.get_formset(None).form
  324. self.assertEqual(form._meta.fields, ['url', 'description'])
  325. def test_get_formsets_with_inlines_returns_tuples(self):
  326. """
  327. get_formsets_with_inlines() returns the correct tuples.
  328. """
  329. class MediaForm(ModelForm):
  330. class Meta:
  331. model = Media
  332. exclude = ['url']
  333. class MediaInline(GenericTabularInline):
  334. form = MediaForm
  335. model = Media
  336. class AlternateInline(GenericTabularInline):
  337. form = MediaForm
  338. model = Media
  339. class EpisodeAdmin(admin.ModelAdmin):
  340. inlines = [
  341. AlternateInline, MediaInline
  342. ]
  343. ma = EpisodeAdmin(Episode, self.site)
  344. inlines = ma.get_inline_instances(request)
  345. for (formset, inline), other_inline in zip(ma.get_formsets_with_inlines(request), inlines):
  346. self.assertIsInstance(formset, other_inline.get_formset(request).__class__)
  347. def test_get_inline_instances_override_get_inlines(self):
  348. class MediaInline(GenericTabularInline):
  349. model = Media
  350. class AlternateInline(GenericTabularInline):
  351. model = Media
  352. class EpisodeAdmin(admin.ModelAdmin):
  353. inlines = (AlternateInline, MediaInline)
  354. def get_inlines(self, request, obj):
  355. if hasattr(request, 'name'):
  356. if request.name == 'alternate':
  357. return self.inlines[:1]
  358. elif request.name == 'media':
  359. return self.inlines[1:2]
  360. return []
  361. ma = EpisodeAdmin(Episode, self.site)
  362. self.assertEqual(ma.get_inlines(request, None), [])
  363. self.assertEqual(ma.get_inline_instances(request), [])
  364. for name, inline_class in (('alternate', AlternateInline), ('media', MediaInline)):
  365. request.name = name
  366. self.assertEqual(ma.get_inlines(request, None), (inline_class,)),
  367. self.assertEqual(type(ma.get_inline_instances(request)[0]), inline_class)