views.py 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.core.exceptions import ObjectDoesNotExist
  3. from django.test import override_settings
  4. from django.urls import reverse
  5. from extras.choices import ObjectChangeActionChoices
  6. from extras.models import ObjectChange
  7. from users.models import ObjectPermission
  8. from .base import ModelTestCase
  9. from .utils import disable_warnings, post_data
  10. __all__ = (
  11. 'ModelViewTestCase',
  12. 'ViewTestCases',
  13. )
  14. #
  15. # UI Tests
  16. #
  17. class ModelViewTestCase(ModelTestCase):
  18. """
  19. Base TestCase for model views. Subclass to test individual views.
  20. """
  21. def _get_base_url(self):
  22. """
  23. Return the base format for a URL for the test's model. Override this to test for a model which belongs
  24. to a different app (e.g. testing Interfaces within the virtualization app).
  25. """
  26. return '{}:{}_{{}}'.format(
  27. self.model._meta.app_label,
  28. self.model._meta.model_name
  29. )
  30. def _get_url(self, action, instance=None):
  31. """
  32. Return the URL name for a specific action and optionally a specific instance
  33. """
  34. url_format = self._get_base_url()
  35. # If no instance was provided, assume we don't need a unique identifier
  36. if instance is None:
  37. return reverse(url_format.format(action))
  38. return reverse(url_format.format(action), kwargs={'pk': instance.pk})
  39. class ViewTestCases:
  40. """
  41. We keep any TestCases with test_* methods inside a class to prevent unittest from trying to run them.
  42. """
  43. class GetObjectViewTestCase(ModelViewTestCase):
  44. """
  45. Retrieve a single instance.
  46. """
  47. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  48. def test_get_object_anonymous(self):
  49. # Make the request as an unauthenticated user
  50. self.client.logout()
  51. response = self.client.get(self._get_queryset().first().get_absolute_url())
  52. self.assertHttpStatus(response, 200)
  53. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  54. def test_get_object_without_permission(self):
  55. instance = self._get_queryset().first()
  56. # Try GET without permission
  57. with disable_warnings('django.request'):
  58. self.assertHttpStatus(self.client.get(instance.get_absolute_url()), 403)
  59. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  60. def test_get_object_with_permission(self):
  61. instance = self._get_queryset().first()
  62. # Add model-level permission
  63. obj_perm = ObjectPermission(
  64. name='Test permission',
  65. actions=['view']
  66. )
  67. obj_perm.save()
  68. obj_perm.users.add(self.user)
  69. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  70. # Try GET with model-level permission
  71. self.assertHttpStatus(self.client.get(instance.get_absolute_url()), 200)
  72. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  73. def test_get_object_with_constrained_permission(self):
  74. instance1, instance2 = self._get_queryset().all()[:2]
  75. # Add object-level permission
  76. obj_perm = ObjectPermission(
  77. name='Test permission',
  78. constraints={'pk': instance1.pk},
  79. actions=['view']
  80. )
  81. obj_perm.save()
  82. obj_perm.users.add(self.user)
  83. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  84. # Try GET to permitted object
  85. self.assertHttpStatus(self.client.get(instance1.get_absolute_url()), 200)
  86. # Try GET to non-permitted object
  87. self.assertHttpStatus(self.client.get(instance2.get_absolute_url()), 404)
  88. class GetObjectChangelogViewTestCase(ModelViewTestCase):
  89. """
  90. View the changelog for an instance.
  91. """
  92. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  93. def test_get_object_changelog(self):
  94. url = self._get_url('changelog', self._get_queryset().first())
  95. response = self.client.get(url)
  96. self.assertHttpStatus(response, 200)
  97. class CreateObjectViewTestCase(ModelViewTestCase):
  98. """
  99. Create a single new instance.
  100. :form_data: Data to be used when creating a new object.
  101. """
  102. form_data = {}
  103. def test_create_object_without_permission(self):
  104. # Try GET without permission
  105. with disable_warnings('django.request'):
  106. self.assertHttpStatus(self.client.get(self._get_url('add')), 403)
  107. # Try POST without permission
  108. request = {
  109. 'path': self._get_url('add'),
  110. 'data': post_data(self.form_data),
  111. }
  112. response = self.client.post(**request)
  113. with disable_warnings('django.request'):
  114. self.assertHttpStatus(response, 403)
  115. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  116. def test_create_object_with_permission(self):
  117. initial_count = self._get_queryset().count()
  118. # Assign unconstrained permission
  119. obj_perm = ObjectPermission(
  120. name='Test permission',
  121. actions=['add']
  122. )
  123. obj_perm.save()
  124. obj_perm.users.add(self.user)
  125. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  126. # Try GET with model-level permission
  127. self.assertHttpStatus(self.client.get(self._get_url('add')), 200)
  128. # Try POST with model-level permission
  129. request = {
  130. 'path': self._get_url('add'),
  131. 'data': post_data(self.form_data),
  132. }
  133. self.assertHttpStatus(self.client.post(**request), 302)
  134. self.assertEqual(initial_count + 1, self._get_queryset().count())
  135. instance = self._get_queryset().order_by('pk').last()
  136. self.assertInstanceEqual(instance, self.form_data)
  137. # Verify ObjectChange creation
  138. objectchanges = ObjectChange.objects.filter(
  139. changed_object_type=ContentType.objects.get_for_model(instance),
  140. changed_object_id=instance.pk
  141. )
  142. self.assertEqual(len(objectchanges), 1)
  143. self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_CREATE)
  144. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  145. def test_create_object_with_constrained_permission(self):
  146. initial_count = self._get_queryset().count()
  147. # Assign constrained permission
  148. obj_perm = ObjectPermission(
  149. name='Test permission',
  150. constraints={'pk': 0}, # Dummy permission to deny all
  151. actions=['add']
  152. )
  153. obj_perm.save()
  154. obj_perm.users.add(self.user)
  155. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  156. # Try GET with object-level permission
  157. self.assertHttpStatus(self.client.get(self._get_url('add')), 200)
  158. # Try to create an object (not permitted)
  159. request = {
  160. 'path': self._get_url('add'),
  161. 'data': post_data(self.form_data),
  162. }
  163. self.assertHttpStatus(self.client.post(**request), 200)
  164. self.assertEqual(initial_count, self._get_queryset().count()) # Check that no object was created
  165. # Update the ObjectPermission to allow creation
  166. obj_perm.constraints = {'pk__gt': 0}
  167. obj_perm.save()
  168. # Try to create an object (permitted)
  169. request = {
  170. 'path': self._get_url('add'),
  171. 'data': post_data(self.form_data),
  172. }
  173. self.assertHttpStatus(self.client.post(**request), 302)
  174. self.assertEqual(initial_count + 1, self._get_queryset().count())
  175. self.assertInstanceEqual(self._get_queryset().order_by('pk').last(), self.form_data)
  176. class EditObjectViewTestCase(ModelViewTestCase):
  177. """
  178. Edit a single existing instance.
  179. :form_data: Data to be used when updating the first existing object.
  180. """
  181. form_data = {}
  182. def test_edit_object_without_permission(self):
  183. instance = self._get_queryset().first()
  184. # Try GET without permission
  185. with disable_warnings('django.request'):
  186. self.assertHttpStatus(self.client.get(self._get_url('edit', instance)), 403)
  187. # Try POST without permission
  188. request = {
  189. 'path': self._get_url('edit', instance),
  190. 'data': post_data(self.form_data),
  191. }
  192. with disable_warnings('django.request'):
  193. self.assertHttpStatus(self.client.post(**request), 403)
  194. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  195. def test_edit_object_with_permission(self):
  196. instance = self._get_queryset().first()
  197. # Assign model-level permission
  198. obj_perm = ObjectPermission(
  199. name='Test permission',
  200. actions=['change']
  201. )
  202. obj_perm.save()
  203. obj_perm.users.add(self.user)
  204. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  205. # Try GET with model-level permission
  206. self.assertHttpStatus(self.client.get(self._get_url('edit', instance)), 200)
  207. # Try POST with model-level permission
  208. request = {
  209. 'path': self._get_url('edit', instance),
  210. 'data': post_data(self.form_data),
  211. }
  212. self.assertHttpStatus(self.client.post(**request), 302)
  213. self.assertInstanceEqual(self._get_queryset().get(pk=instance.pk), self.form_data)
  214. # Verify ObjectChange creation
  215. objectchanges = ObjectChange.objects.filter(
  216. changed_object_type=ContentType.objects.get_for_model(instance),
  217. changed_object_id=instance.pk
  218. )
  219. self.assertEqual(len(objectchanges), 1)
  220. self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_UPDATE)
  221. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  222. def test_edit_object_with_constrained_permission(self):
  223. instance1, instance2 = self._get_queryset().all()[:2]
  224. # Assign constrained permission
  225. obj_perm = ObjectPermission(
  226. name='Test permission',
  227. constraints={'pk': instance1.pk},
  228. actions=['change']
  229. )
  230. obj_perm.save()
  231. obj_perm.users.add(self.user)
  232. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  233. # Try GET with a permitted object
  234. self.assertHttpStatus(self.client.get(self._get_url('edit', instance1)), 200)
  235. # Try GET with a non-permitted object
  236. self.assertHttpStatus(self.client.get(self._get_url('edit', instance2)), 404)
  237. # Try to edit a permitted object
  238. request = {
  239. 'path': self._get_url('edit', instance1),
  240. 'data': post_data(self.form_data),
  241. }
  242. self.assertHttpStatus(self.client.post(**request), 302)
  243. self.assertInstanceEqual(self._get_queryset().get(pk=instance1.pk), self.form_data)
  244. # Try to edit a non-permitted object
  245. request = {
  246. 'path': self._get_url('edit', instance2),
  247. 'data': post_data(self.form_data),
  248. }
  249. self.assertHttpStatus(self.client.post(**request), 404)
  250. class DeleteObjectViewTestCase(ModelViewTestCase):
  251. """
  252. Delete a single instance.
  253. """
  254. def test_delete_object_without_permission(self):
  255. instance = self._get_queryset().first()
  256. # Try GET without permission
  257. with disable_warnings('django.request'):
  258. self.assertHttpStatus(self.client.get(self._get_url('delete', instance)), 403)
  259. # Try POST without permission
  260. request = {
  261. 'path': self._get_url('delete', instance),
  262. 'data': post_data({'confirm': True}),
  263. }
  264. with disable_warnings('django.request'):
  265. self.assertHttpStatus(self.client.post(**request), 403)
  266. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  267. def test_delete_object_with_permission(self):
  268. instance = self._get_queryset().first()
  269. # Assign model-level permission
  270. obj_perm = ObjectPermission(
  271. name='Test permission',
  272. actions=['delete']
  273. )
  274. obj_perm.save()
  275. obj_perm.users.add(self.user)
  276. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  277. # Try GET with model-level permission
  278. self.assertHttpStatus(self.client.get(self._get_url('delete', instance)), 200)
  279. # Try POST with model-level permission
  280. request = {
  281. 'path': self._get_url('delete', instance),
  282. 'data': post_data({'confirm': True}),
  283. }
  284. self.assertHttpStatus(self.client.post(**request), 302)
  285. with self.assertRaises(ObjectDoesNotExist):
  286. self._get_queryset().get(pk=instance.pk)
  287. # Verify ObjectChange creation
  288. objectchanges = ObjectChange.objects.filter(
  289. changed_object_type=ContentType.objects.get_for_model(instance),
  290. changed_object_id=instance.pk
  291. )
  292. self.assertEqual(len(objectchanges), 1)
  293. self.assertEqual(objectchanges[0].action, ObjectChangeActionChoices.ACTION_DELETE)
  294. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  295. def test_delete_object_with_constrained_permission(self):
  296. instance1, instance2 = self._get_queryset().all()[:2]
  297. # Assign object-level permission
  298. obj_perm = ObjectPermission(
  299. name='Test permission',
  300. constraints={'pk': instance1.pk},
  301. actions=['delete']
  302. )
  303. obj_perm.save()
  304. obj_perm.users.add(self.user)
  305. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  306. # Try GET with a permitted object
  307. self.assertHttpStatus(self.client.get(self._get_url('delete', instance1)), 200)
  308. # Try GET with a non-permitted object
  309. self.assertHttpStatus(self.client.get(self._get_url('delete', instance2)), 404)
  310. # Try to delete a permitted object
  311. request = {
  312. 'path': self._get_url('delete', instance1),
  313. 'data': post_data({'confirm': True}),
  314. }
  315. self.assertHttpStatus(self.client.post(**request), 302)
  316. with self.assertRaises(ObjectDoesNotExist):
  317. self._get_queryset().get(pk=instance1.pk)
  318. # Try to delete a non-permitted object
  319. request = {
  320. 'path': self._get_url('delete', instance2),
  321. 'data': post_data({'confirm': True}),
  322. }
  323. self.assertHttpStatus(self.client.post(**request), 404)
  324. self.assertTrue(self._get_queryset().filter(pk=instance2.pk).exists())
  325. class ListObjectsViewTestCase(ModelViewTestCase):
  326. """
  327. Retrieve multiple instances.
  328. """
  329. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  330. def test_list_objects_anonymous(self):
  331. # Make the request as an unauthenticated user
  332. self.client.logout()
  333. response = self.client.get(self._get_url('list'))
  334. self.assertHttpStatus(response, 200)
  335. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  336. def test_list_objects_without_permission(self):
  337. # Try GET without permission
  338. with disable_warnings('django.request'):
  339. self.assertHttpStatus(self.client.get(self._get_url('list')), 403)
  340. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  341. def test_list_objects_with_permission(self):
  342. # Add model-level permission
  343. obj_perm = ObjectPermission(
  344. name='Test permission',
  345. actions=['view']
  346. )
  347. obj_perm.save()
  348. obj_perm.users.add(self.user)
  349. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  350. # Try GET with model-level permission
  351. self.assertHttpStatus(self.client.get(self._get_url('list')), 200)
  352. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  353. def test_list_objects_with_constrained_permission(self):
  354. instance1, instance2 = self._get_queryset().all()[:2]
  355. # Add object-level permission
  356. obj_perm = ObjectPermission(
  357. name='Test permission',
  358. constraints={'pk': instance1.pk},
  359. actions=['view']
  360. )
  361. obj_perm.save()
  362. obj_perm.users.add(self.user)
  363. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  364. # Try GET with object-level permission
  365. response = self.client.get(self._get_url('list'))
  366. self.assertHttpStatus(response, 200)
  367. content = str(response.content)
  368. if hasattr(self.model, 'name'):
  369. self.assertIn(instance1.name, content)
  370. self.assertNotIn(instance2.name, content)
  371. elif hasattr(self.model, 'get_absolute_url'):
  372. self.assertIn(instance1.get_absolute_url(), content)
  373. self.assertNotIn(instance2.get_absolute_url(), content)
  374. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  375. def test_export_objects(self):
  376. url = self._get_url('list')
  377. # Test default CSV export
  378. if hasattr(self.model, 'csv_headers'):
  379. response = self.client.get(f'{url}?export')
  380. self.assertHttpStatus(response, 200)
  381. self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
  382. # Test table-based export
  383. response = self.client.get(f'{url}?export=table')
  384. self.assertHttpStatus(response, 200)
  385. self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
  386. class CreateMultipleObjectsViewTestCase(ModelViewTestCase):
  387. """
  388. Create multiple instances using a single form. Expects the creation of three new instances by default.
  389. :bulk_create_count: The number of objects expected to be created (default: 3).
  390. :bulk_create_data: A dictionary of data to be used for bulk object creation.
  391. """
  392. bulk_create_count = 3
  393. bulk_create_data = {}
  394. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  395. def test_create_multiple_objects_without_permission(self):
  396. request = {
  397. 'path': self._get_url('add'),
  398. 'data': post_data(self.bulk_create_data),
  399. }
  400. # Try POST without permission
  401. with disable_warnings('django.request'):
  402. self.assertHttpStatus(self.client.post(**request), 403)
  403. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  404. def test_create_multiple_objects_with_permission(self):
  405. initial_count = self._get_queryset().count()
  406. request = {
  407. 'path': self._get_url('add'),
  408. 'data': post_data(self.bulk_create_data),
  409. }
  410. # Assign non-constrained permission
  411. obj_perm = ObjectPermission(
  412. name='Test permission',
  413. actions=['add'],
  414. )
  415. obj_perm.save()
  416. obj_perm.users.add(self.user)
  417. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  418. # Bulk create objects
  419. response = self.client.post(**request)
  420. self.assertHttpStatus(response, 302)
  421. self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
  422. for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
  423. self.assertInstanceEqual(instance, self.bulk_create_data)
  424. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  425. def test_create_multiple_objects_with_constrained_permission(self):
  426. initial_count = self._get_queryset().count()
  427. request = {
  428. 'path': self._get_url('add'),
  429. 'data': post_data(self.bulk_create_data),
  430. }
  431. # Assign constrained permission
  432. obj_perm = ObjectPermission(
  433. name='Test permission',
  434. actions=['add'],
  435. constraints={'pk': 0} # Dummy constraint to deny all
  436. )
  437. obj_perm.save()
  438. obj_perm.users.add(self.user)
  439. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  440. # Attempt to make the request with unmet constraints
  441. self.assertHttpStatus(self.client.post(**request), 200)
  442. self.assertEqual(self._get_queryset().count(), initial_count)
  443. # Update the ObjectPermission to allow creation
  444. obj_perm.constraints = {'pk__gt': 0} # Dummy constraint to allow all
  445. obj_perm.save()
  446. response = self.client.post(**request)
  447. self.assertHttpStatus(response, 302)
  448. self.assertEqual(initial_count + self.bulk_create_count, self._get_queryset().count())
  449. for instance in self._get_queryset().order_by('-pk')[:self.bulk_create_count]:
  450. self.assertInstanceEqual(instance, self.bulk_create_data)
  451. class BulkImportObjectsViewTestCase(ModelViewTestCase):
  452. """
  453. Create multiple instances from imported data.
  454. :csv_data: A list of CSV-formatted lines (starting with the headers) to be used for bulk object import.
  455. """
  456. csv_data = ()
  457. def _get_csv_data(self):
  458. return '\n'.join(self.csv_data)
  459. def test_bulk_import_objects_without_permission(self):
  460. data = {
  461. 'csv': self._get_csv_data(),
  462. }
  463. # Test GET without permission
  464. with disable_warnings('django.request'):
  465. self.assertHttpStatus(self.client.get(self._get_url('import')), 403)
  466. # Try POST without permission
  467. response = self.client.post(self._get_url('import'), data)
  468. with disable_warnings('django.request'):
  469. self.assertHttpStatus(response, 403)
  470. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  471. def test_bulk_import_objects_with_permission(self):
  472. initial_count = self._get_queryset().count()
  473. data = {
  474. 'csv': self._get_csv_data(),
  475. }
  476. # Assign model-level permission
  477. obj_perm = ObjectPermission(
  478. name='Test permission',
  479. actions=['add']
  480. )
  481. obj_perm.save()
  482. obj_perm.users.add(self.user)
  483. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  484. # Try GET with model-level permission
  485. self.assertHttpStatus(self.client.get(self._get_url('import')), 200)
  486. # Test POST with permission
  487. self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
  488. self.assertEqual(self._get_queryset().count(), initial_count + len(self.csv_data) - 1)
  489. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  490. def test_bulk_import_objects_with_constrained_permission(self):
  491. initial_count = self._get_queryset().count()
  492. data = {
  493. 'csv': self._get_csv_data(),
  494. }
  495. # Assign constrained permission
  496. obj_perm = ObjectPermission(
  497. name='Test permission',
  498. constraints={'pk': 0}, # Dummy permission to deny all
  499. actions=['add']
  500. )
  501. obj_perm.save()
  502. obj_perm.users.add(self.user)
  503. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  504. # Attempt to import non-permitted objects
  505. self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
  506. self.assertEqual(self._get_queryset().count(), initial_count)
  507. # Update permission constraints
  508. obj_perm.constraints = {'pk__gt': 0} # Dummy permission to allow all
  509. obj_perm.save()
  510. # Import permitted objects
  511. self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
  512. self.assertEqual(self._get_queryset().count(), initial_count + len(self.csv_data) - 1)
  513. class BulkEditObjectsViewTestCase(ModelViewTestCase):
  514. """
  515. Edit multiple instances.
  516. :bulk_edit_data: A dictionary of data to be used when bulk editing a set of objects. This data should differ
  517. from that used for initial object creation within setUpTestData().
  518. """
  519. bulk_edit_data = {}
  520. def test_bulk_edit_objects_without_permission(self):
  521. pk_list = self._get_queryset().values_list('pk', flat=True)[:3]
  522. data = {
  523. 'pk': pk_list,
  524. '_apply': True, # Form button
  525. }
  526. # Test GET without permission
  527. with disable_warnings('django.request'):
  528. self.assertHttpStatus(self.client.get(self._get_url('bulk_edit')), 403)
  529. # Try POST without permission
  530. with disable_warnings('django.request'):
  531. self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 403)
  532. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  533. def test_bulk_edit_objects_with_permission(self):
  534. pk_list = self._get_queryset().values_list('pk', flat=True)[:3]
  535. data = {
  536. 'pk': pk_list,
  537. '_apply': True, # Form button
  538. }
  539. # Append the form data to the request
  540. data.update(post_data(self.bulk_edit_data))
  541. # Assign model-level permission
  542. obj_perm = ObjectPermission(
  543. name='Test permission',
  544. actions=['change']
  545. )
  546. obj_perm.save()
  547. obj_perm.users.add(self.user)
  548. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  549. # Try POST with model-level permission
  550. self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 302)
  551. for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
  552. self.assertInstanceEqual(instance, self.bulk_edit_data)
  553. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  554. def test_bulk_edit_objects_with_constrained_permission(self):
  555. pk_list = list(self._get_queryset().values_list('pk', flat=True)[:3])
  556. data = {
  557. 'pk': pk_list,
  558. '_apply': True, # Form button
  559. }
  560. # Append the form data to the request
  561. data.update(post_data(self.bulk_edit_data))
  562. # Dynamically determine a constraint that will *not* be matched by the updated objects.
  563. attr_name = list(self.bulk_edit_data.keys())[0]
  564. field = self.model._meta.get_field(attr_name)
  565. value = field.value_from_object(self._get_queryset().first())
  566. # Assign constrained permission
  567. obj_perm = ObjectPermission(
  568. name='Test permission',
  569. constraints={attr_name: value},
  570. actions=['change']
  571. )
  572. obj_perm.save()
  573. obj_perm.users.add(self.user)
  574. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  575. # Attempt to bulk edit permitted objects into a non-permitted state
  576. response = self.client.post(self._get_url('bulk_edit'), data)
  577. self.assertHttpStatus(response, 200)
  578. # Update permission constraints
  579. obj_perm.constraints = {'pk__gt': 0}
  580. obj_perm.save()
  581. # Bulk edit permitted objects
  582. self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 302)
  583. for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
  584. self.assertInstanceEqual(instance, self.bulk_edit_data)
  585. class BulkDeleteObjectsViewTestCase(ModelViewTestCase):
  586. """
  587. Delete multiple instances.
  588. """
  589. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  590. def test_bulk_delete_objects_without_permission(self):
  591. pk_list = self._get_queryset().values_list('pk', flat=True)[:3]
  592. data = {
  593. 'pk': pk_list,
  594. 'confirm': True,
  595. '_confirm': True, # Form button
  596. }
  597. # Test GET without permission
  598. with disable_warnings('django.request'):
  599. self.assertHttpStatus(self.client.get(self._get_url('bulk_delete')), 403)
  600. # Try POST without permission
  601. with disable_warnings('django.request'):
  602. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 403)
  603. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  604. def test_bulk_delete_objects_with_permission(self):
  605. pk_list = self._get_queryset().values_list('pk', flat=True)
  606. data = {
  607. 'pk': pk_list,
  608. 'confirm': True,
  609. '_confirm': True, # Form button
  610. }
  611. # Assign unconstrained permission
  612. obj_perm = ObjectPermission(
  613. name='Test permission',
  614. actions=['delete']
  615. )
  616. obj_perm.save()
  617. obj_perm.users.add(self.user)
  618. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  619. # Try POST with model-level permission
  620. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
  621. self.assertEqual(self._get_queryset().count(), 0)
  622. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  623. def test_bulk_delete_objects_with_constrained_permission(self):
  624. initial_count = self._get_queryset().count()
  625. pk_list = self._get_queryset().values_list('pk', flat=True)
  626. data = {
  627. 'pk': pk_list,
  628. 'confirm': True,
  629. '_confirm': True, # Form button
  630. }
  631. # Assign constrained permission
  632. obj_perm = ObjectPermission(
  633. name='Test permission',
  634. constraints={'pk': 0}, # Dummy permission to deny all
  635. actions=['delete']
  636. )
  637. obj_perm.save()
  638. obj_perm.users.add(self.user)
  639. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  640. # Attempt to bulk delete non-permitted objects
  641. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
  642. self.assertEqual(self._get_queryset().count(), initial_count)
  643. # Update permission constraints
  644. obj_perm.constraints = {'pk__gt': 0} # Dummy permission to allow all
  645. obj_perm.save()
  646. # Bulk delete permitted objects
  647. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
  648. self.assertEqual(self._get_queryset().count(), 0)
  649. class BulkRenameObjectsViewTestCase(ModelViewTestCase):
  650. """
  651. Rename multiple instances.
  652. """
  653. rename_data = {
  654. 'find': '^(.*)$',
  655. 'replace': '\\1X', # Append an X to the original value
  656. 'use_regex': True,
  657. }
  658. def test_bulk_rename_objects_without_permission(self):
  659. pk_list = self._get_queryset().values_list('pk', flat=True)[:3]
  660. data = {
  661. 'pk': pk_list,
  662. '_apply': True, # Form button
  663. }
  664. data.update(self.rename_data)
  665. # Test GET without permission
  666. with disable_warnings('django.request'):
  667. self.assertHttpStatus(self.client.get(self._get_url('bulk_rename')), 403)
  668. # Try POST without permission
  669. with disable_warnings('django.request'):
  670. self.assertHttpStatus(self.client.post(self._get_url('bulk_rename'), data), 403)
  671. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  672. def test_bulk_rename_objects_with_permission(self):
  673. objects = self._get_queryset().all()[:3]
  674. pk_list = [obj.pk for obj in objects]
  675. data = {
  676. 'pk': pk_list,
  677. '_apply': True, # Form button
  678. }
  679. data.update(self.rename_data)
  680. # Assign model-level permission
  681. obj_perm = ObjectPermission(
  682. name='Test permission',
  683. actions=['change']
  684. )
  685. obj_perm.save()
  686. obj_perm.users.add(self.user)
  687. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  688. # Try POST with model-level permission
  689. self.assertHttpStatus(self.client.post(self._get_url('bulk_rename'), data), 302)
  690. for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
  691. self.assertEqual(instance.name, f'{objects[i].name}X')
  692. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  693. def test_bulk_rename_objects_with_constrained_permission(self):
  694. objects = self._get_queryset().all()[:3]
  695. pk_list = [obj.pk for obj in objects]
  696. data = {
  697. 'pk': pk_list,
  698. '_apply': True, # Form button
  699. }
  700. data.update(self.rename_data)
  701. # Assign constrained permission
  702. obj_perm = ObjectPermission(
  703. name='Test permission',
  704. constraints={'name__regex': '[^X]$'},
  705. actions=['change']
  706. )
  707. obj_perm.save()
  708. obj_perm.users.add(self.user)
  709. obj_perm.object_types.add(ContentType.objects.get_for_model(self.model))
  710. # Attempt to bulk edit permitted objects into a non-permitted state
  711. response = self.client.post(self._get_url('bulk_rename'), data)
  712. self.assertHttpStatus(response, 200)
  713. # Update permission constraints
  714. obj_perm.constraints = {'pk__gt': 0}
  715. obj_perm.save()
  716. # Bulk rename permitted objects
  717. self.assertHttpStatus(self.client.post(self._get_url('bulk_rename'), data), 302)
  718. for i, instance in enumerate(self._get_queryset().filter(pk__in=pk_list)):
  719. self.assertEqual(instance.name, f'{objects[i].name}X')
  720. class PrimaryObjectViewTestCase(
  721. GetObjectViewTestCase,
  722. GetObjectChangelogViewTestCase,
  723. CreateObjectViewTestCase,
  724. EditObjectViewTestCase,
  725. DeleteObjectViewTestCase,
  726. ListObjectsViewTestCase,
  727. BulkImportObjectsViewTestCase,
  728. BulkEditObjectsViewTestCase,
  729. BulkDeleteObjectsViewTestCase,
  730. ):
  731. """
  732. TestCase suitable for testing all standard View functions for primary objects
  733. """
  734. maxDiff = None
  735. class OrganizationalObjectViewTestCase(
  736. GetObjectViewTestCase,
  737. GetObjectChangelogViewTestCase,
  738. CreateObjectViewTestCase,
  739. EditObjectViewTestCase,
  740. DeleteObjectViewTestCase,
  741. ListObjectsViewTestCase,
  742. BulkImportObjectsViewTestCase,
  743. BulkEditObjectsViewTestCase,
  744. BulkDeleteObjectsViewTestCase,
  745. ):
  746. """
  747. TestCase suitable for all organizational objects
  748. """
  749. maxDiff = None
  750. class DeviceComponentTemplateViewTestCase(
  751. EditObjectViewTestCase,
  752. DeleteObjectViewTestCase,
  753. CreateMultipleObjectsViewTestCase,
  754. BulkEditObjectsViewTestCase,
  755. BulkRenameObjectsViewTestCase,
  756. BulkDeleteObjectsViewTestCase,
  757. ):
  758. """
  759. TestCase suitable for testing device component template models (ConsolePortTemplates, InterfaceTemplates, etc.)
  760. """
  761. maxDiff = None
  762. class DeviceComponentViewTestCase(
  763. GetObjectViewTestCase,
  764. GetObjectChangelogViewTestCase,
  765. EditObjectViewTestCase,
  766. DeleteObjectViewTestCase,
  767. ListObjectsViewTestCase,
  768. CreateMultipleObjectsViewTestCase,
  769. BulkImportObjectsViewTestCase,
  770. BulkEditObjectsViewTestCase,
  771. BulkRenameObjectsViewTestCase,
  772. BulkDeleteObjectsViewTestCase,
  773. ):
  774. """
  775. TestCase suitable for testing device component models (ConsolePorts, Interfaces, etc.)
  776. """
  777. maxDiff = None