testcases.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.contrib.auth.models import User
  3. from django.core.exceptions import ObjectDoesNotExist
  4. from django.forms.models import model_to_dict
  5. from django.test import Client, TestCase as _TestCase, override_settings
  6. from django.urls import reverse, NoReverseMatch
  7. from rest_framework.test import APIClient
  8. from users.models import ObjectPermission, Token
  9. from utilities.permissions import resolve_permission
  10. from .utils import disable_warnings, post_data
  11. class TestCase(_TestCase):
  12. user_permissions = ()
  13. def setUp(self):
  14. # Create the test user and assign permissions
  15. self.user = User.objects.create_user(username='testuser')
  16. self.add_permissions(*self.user_permissions)
  17. # Initialize the test client
  18. self.client = Client()
  19. self.client.force_login(self.user)
  20. #
  21. # Permissions management
  22. #
  23. def add_permissions(self, *names):
  24. """
  25. Assign a set of permissions to the test user. Accepts permission names in the form <app>.<action>_<model>.
  26. """
  27. for name in names:
  28. ct, action = resolve_permission(name)
  29. obj_perm = ObjectPermission(**{f'can_{action}': True})
  30. obj_perm.save()
  31. obj_perm.users.add(self.user)
  32. obj_perm.content_types.add(ct)
  33. #
  34. # Convenience methods
  35. #
  36. def assertHttpStatus(self, response, expected_status):
  37. """
  38. TestCase method. Provide more detail in the event of an unexpected HTTP response.
  39. """
  40. err_message = "Expected HTTP status {}; received {}: {}"
  41. self.assertEqual(response.status_code, expected_status, err_message.format(
  42. expected_status, response.status_code, getattr(response, 'data', 'No data')
  43. ))
  44. class ModelViewTestCase(TestCase):
  45. """
  46. Base TestCase for model views. Subclass to test individual views.
  47. """
  48. model = None
  49. def __init__(self, *args, **kwargs):
  50. super().__init__(*args, **kwargs)
  51. if self.model is None:
  52. raise Exception("Test case requires model to be defined")
  53. def _get_base_url(self):
  54. """
  55. Return the base format for a URL for the test's model. Override this to test for a model which belongs
  56. to a different app (e.g. testing Interfaces within the virtualization app).
  57. """
  58. return '{}:{}_{{}}'.format(
  59. self.model._meta.app_label,
  60. self.model._meta.model_name
  61. )
  62. def _get_url(self, action, instance=None):
  63. """
  64. Return the URL name for a specific action. An instance must be specified for
  65. get/edit/delete views.
  66. """
  67. url_format = self._get_base_url()
  68. if action in ('list', 'add', 'import', 'bulk_edit', 'bulk_delete'):
  69. return reverse(url_format.format(action))
  70. elif action in ('get', 'edit', 'delete'):
  71. if instance is None:
  72. raise Exception("Resolving {} URL requires specifying an instance".format(action))
  73. # Attempt to resolve using slug first
  74. if hasattr(self.model, 'slug'):
  75. try:
  76. return reverse(url_format.format(action), kwargs={'slug': instance.slug})
  77. except NoReverseMatch:
  78. pass
  79. return reverse(url_format.format(action), kwargs={'pk': instance.pk})
  80. else:
  81. raise Exception("Invalid action for URL resolution: {}".format(action))
  82. def assertInstanceEqual(self, instance, data):
  83. """
  84. Compare a model instance to a dictionary, checking that its attribute values match those specified
  85. in the dictionary.
  86. """
  87. model_dict = model_to_dict(instance, fields=data.keys())
  88. for key in list(model_dict.keys()):
  89. # TODO: Differentiate between tags assigned to the instance and a M2M field for tags (ex: ConfigContext)
  90. if key == 'tags':
  91. model_dict[key] = ','.join(sorted([tag.name for tag in model_dict['tags']]))
  92. # Convert ManyToManyField to list of instance PKs
  93. elif model_dict[key] and type(model_dict[key]) in (list, tuple) and hasattr(model_dict[key][0], 'pk'):
  94. model_dict[key] = [obj.pk for obj in model_dict[key]]
  95. # Omit any dictionary keys which are not instance attributes
  96. relevant_data = {
  97. k: v for k, v in data.items() if hasattr(instance, k)
  98. }
  99. self.assertDictEqual(model_dict, relevant_data)
  100. class APITestCase(TestCase):
  101. client_class = APIClient
  102. def setUp(self):
  103. """
  104. Create a superuser and token for API calls.
  105. """
  106. self.user = User.objects.create(username='testuser', is_superuser=True)
  107. self.token = Token.objects.create(user=self.user)
  108. self.header = {'HTTP_AUTHORIZATION': 'Token {}'.format(self.token.key)}
  109. class ViewTestCases:
  110. """
  111. We keep any TestCases with test_* methods inside a class to prevent unittest from trying to run them.
  112. """
  113. class GetObjectViewTestCase(ModelViewTestCase):
  114. """
  115. Retrieve a single instance.
  116. """
  117. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  118. def test_get_object_anonymous(self):
  119. # Make the request as an unauthenticated user
  120. self.client.logout()
  121. response = self.client.get(self.model.objects.first().get_absolute_url())
  122. self.assertHttpStatus(response, 200)
  123. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  124. def test_get_object_without_permission(self):
  125. instance = self.model.objects.first()
  126. # Try GET without permission
  127. with disable_warnings('django.request'):
  128. self.assertHttpStatus(self.client.get(instance.get_absolute_url()), 403)
  129. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  130. def test_get_object_with_model_permission(self):
  131. instance = self.model.objects.first()
  132. # Add model-level permission
  133. obj_perm = ObjectPermission(
  134. can_view=True
  135. )
  136. obj_perm.save()
  137. obj_perm.users.add(self.user)
  138. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  139. # Try GET with model-level permission
  140. self.assertHttpStatus(self.client.get(instance.get_absolute_url()), 200)
  141. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  142. def test_get_object_with_object_permission(self):
  143. instance1, instance2 = self.model.objects.all()[:2]
  144. # Add object-level permission
  145. obj_perm = ObjectPermission(
  146. attrs={'pk': instance1.pk},
  147. can_view=True
  148. )
  149. obj_perm.save()
  150. obj_perm.users.add(self.user)
  151. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  152. # Try GET to permitted object
  153. self.assertHttpStatus(self.client.get(instance1.get_absolute_url()), 200)
  154. # Try GET to non-permitted object
  155. self.assertHttpStatus(self.client.get(instance2.get_absolute_url()), 404)
  156. class CreateObjectViewTestCase(ModelViewTestCase):
  157. """
  158. Create a single new instance.
  159. """
  160. form_data = {}
  161. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  162. def test_create_object_without_permission(self):
  163. # Try GET without permission
  164. with disable_warnings('django.request'):
  165. self.assertHttpStatus(self.client.post(self._get_url('add')), 403)
  166. # Try POST without permission
  167. request = {
  168. 'path': self._get_url('add'),
  169. 'data': post_data(self.form_data),
  170. }
  171. response = self.client.post(**request)
  172. with disable_warnings('django.request'):
  173. self.assertHttpStatus(response, 403)
  174. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  175. def test_create_object_with_model_permission(self):
  176. initial_count = self.model.objects.count()
  177. # Assign model-level permission
  178. obj_perm = ObjectPermission(
  179. can_add=True
  180. )
  181. obj_perm.save()
  182. obj_perm.users.add(self.user)
  183. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  184. # Try GET with model-level permission
  185. self.assertHttpStatus(self.client.get(self._get_url('add')), 200)
  186. # Try POST with model-level permission
  187. request = {
  188. 'path': self._get_url('add'),
  189. 'data': post_data(self.form_data),
  190. }
  191. self.assertHttpStatus(self.client.post(**request), 302)
  192. self.assertEqual(initial_count + 1, self.model.objects.count())
  193. self.assertInstanceEqual(self.model.objects.order_by('pk').last(), self.form_data)
  194. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  195. def test_create_object_with_object_permission(self):
  196. initial_count = self.model.objects.count()
  197. # Assign object-level permission
  198. obj_perm = ObjectPermission(
  199. attrs={'pk__gt': 0}, # Dummy permission to allow all
  200. can_add=True
  201. )
  202. obj_perm.save()
  203. obj_perm.users.add(self.user)
  204. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  205. # Try GET with object-level permission
  206. self.assertHttpStatus(self.client.get(self._get_url('add')), 200)
  207. # Try to create permitted object
  208. request = {
  209. 'path': self._get_url('add'),
  210. 'data': post_data(self.form_data),
  211. }
  212. self.assertHttpStatus(self.client.post(**request), 302)
  213. self.assertEqual(initial_count + 1, self.model.objects.count())
  214. self.assertInstanceEqual(self.model.objects.order_by('pk').last(), self.form_data)
  215. # Nullify ObjectPermission to disallow new object creation
  216. obj_perm.attrs = {'pk': 0}
  217. obj_perm.save()
  218. # Try to create a non-permitted object
  219. initial_count = self.model.objects.count()
  220. request = {
  221. 'path': self._get_url('add'),
  222. 'data': post_data(self.form_data),
  223. }
  224. self.assertHttpStatus(self.client.post(**request), 200)
  225. self.assertEqual(initial_count, self.model.objects.count()) # Check that no object was created
  226. class EditObjectViewTestCase(ModelViewTestCase):
  227. """
  228. Edit a single existing instance.
  229. """
  230. form_data = {}
  231. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  232. def test_edit_object_without_permission(self):
  233. instance = self.model.objects.first()
  234. # Try GET without permission
  235. with disable_warnings('django.request'):
  236. self.assertHttpStatus(self.client.post(self._get_url('edit', instance)), 403)
  237. # Try POST without permission
  238. request = {
  239. 'path': self._get_url('edit', instance),
  240. 'data': post_data(self.form_data),
  241. }
  242. with disable_warnings('django.request'):
  243. self.assertHttpStatus(self.client.post(**request), 403)
  244. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  245. def test_edit_object_with_model_permission(self):
  246. instance = self.model.objects.first()
  247. # Assign model-level permission
  248. obj_perm = ObjectPermission(
  249. can_change=True
  250. )
  251. obj_perm.save()
  252. obj_perm.users.add(self.user)
  253. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  254. # Try GET with model-level permission
  255. self.assertHttpStatus(self.client.get(self._get_url('edit', instance)), 200)
  256. # Try POST with model-level permission
  257. request = {
  258. 'path': self._get_url('edit', instance),
  259. 'data': post_data(self.form_data),
  260. }
  261. self.assertHttpStatus(self.client.post(**request), 302)
  262. self.assertInstanceEqual(self.model.objects.get(pk=instance.pk), self.form_data)
  263. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  264. def test_edit_object_with_object_permission(self):
  265. instance1, instance2 = self.model.objects.all()[:2]
  266. # Assign object-level permission
  267. obj_perm = ObjectPermission(
  268. attrs={'pk': instance1.pk},
  269. can_change=True
  270. )
  271. obj_perm.save()
  272. obj_perm.users.add(self.user)
  273. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  274. # Try GET with a permitted object
  275. self.assertHttpStatus(self.client.get(self._get_url('edit', instance1)), 200)
  276. # Try GET with a non-permitted object
  277. self.assertHttpStatus(self.client.get(self._get_url('edit', instance2)), 404)
  278. # Try to edit a permitted object
  279. request = {
  280. 'path': self._get_url('edit', instance1),
  281. 'data': post_data(self.form_data),
  282. }
  283. self.assertHttpStatus(self.client.post(**request), 302)
  284. self.assertInstanceEqual(self.model.objects.get(pk=instance1.pk), self.form_data)
  285. # Try to edit a non-permitted object
  286. request = {
  287. 'path': self._get_url('edit', instance2),
  288. 'data': post_data(self.form_data),
  289. }
  290. self.assertHttpStatus(self.client.post(**request), 404)
  291. class DeleteObjectViewTestCase(ModelViewTestCase):
  292. """
  293. Delete a single instance.
  294. """
  295. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  296. def test_delete_object_without_permission(self):
  297. instance = self.model.objects.first()
  298. # Try GET without permission
  299. with disable_warnings('django.request'):
  300. self.assertHttpStatus(self.client.get(self._get_url('delete', instance)), 403)
  301. # Try POST without permission
  302. request = {
  303. 'path': self._get_url('delete', instance),
  304. 'data': post_data({'confirm': True}),
  305. }
  306. with disable_warnings('django.request'):
  307. self.assertHttpStatus(self.client.post(**request), 403)
  308. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  309. def test_delete_object_with_model_permission(self):
  310. instance = self.model.objects.first()
  311. # Assign model-level permission
  312. obj_perm = ObjectPermission(
  313. can_delete=True
  314. )
  315. obj_perm.save()
  316. obj_perm.users.add(self.user)
  317. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  318. # Try GET with model-level permission
  319. self.assertHttpStatus(self.client.get(self._get_url('delete', instance)), 200)
  320. # Try POST with model-level permission
  321. request = {
  322. 'path': self._get_url('delete', instance),
  323. 'data': post_data({'confirm': True}),
  324. }
  325. self.assertHttpStatus(self.client.post(**request), 302)
  326. with self.assertRaises(ObjectDoesNotExist):
  327. self.model.objects.get(pk=instance.pk)
  328. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  329. def test_delete_object_with_object_permission(self):
  330. instance1, instance2 = self.model.objects.all()[:2]
  331. # Assign object-level permission
  332. obj_perm = ObjectPermission(
  333. attrs={'pk': instance1.pk},
  334. can_delete=True
  335. )
  336. obj_perm.save()
  337. obj_perm.users.add(self.user)
  338. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  339. # Try GET with a permitted object
  340. self.assertHttpStatus(self.client.get(self._get_url('delete', instance1)), 200)
  341. # Try GET with a non-permitted object
  342. self.assertHttpStatus(self.client.get(self._get_url('delete', instance2)), 404)
  343. # Try to delete a permitted object
  344. request = {
  345. 'path': self._get_url('delete', instance1),
  346. 'data': post_data({'confirm': True}),
  347. }
  348. self.assertHttpStatus(self.client.post(**request), 302)
  349. with self.assertRaises(ObjectDoesNotExist):
  350. self.model.objects.get(pk=instance1.pk)
  351. # Try to delete a non-permitted object
  352. request = {
  353. 'path': self._get_url('delete', instance2),
  354. 'data': post_data({'confirm': True}),
  355. }
  356. self.assertHttpStatus(self.client.post(**request), 404)
  357. self.assertTrue(self.model.objects.filter(pk=instance2.pk).exists())
  358. class ListObjectsViewTestCase(ModelViewTestCase):
  359. """
  360. Retrieve multiple instances.
  361. """
  362. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  363. def test_list_objects_anonymous(self):
  364. # Make the request as an unauthenticated user
  365. self.client.logout()
  366. response = self.client.get(self._get_url('list'))
  367. self.assertHttpStatus(response, 200)
  368. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  369. def test_list_objects_without_permission(self):
  370. # Try GET without permission
  371. with disable_warnings('django.request'):
  372. self.assertHttpStatus(self.client.get(self._get_url('list')), 403)
  373. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  374. def test_list_objects_with_model_permission(self):
  375. # Add model-level permission
  376. obj_perm = ObjectPermission(
  377. can_view=True
  378. )
  379. obj_perm.save()
  380. obj_perm.users.add(self.user)
  381. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  382. # Try GET with model-level permission
  383. self.assertHttpStatus(self.client.get(self._get_url('list')), 200)
  384. # Built-in CSV export
  385. if hasattr(self.model, 'csv_headers'):
  386. response = self.client.get('{}?export'.format(self._get_url('list')))
  387. self.assertHttpStatus(response, 200)
  388. self.assertEqual(response.get('Content-Type'), 'text/csv')
  389. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  390. def test_list_objects_with_object_permission(self):
  391. instance1, instance2 = self.model.objects.all()[:2]
  392. # Add object-level permission
  393. obj_perm = ObjectPermission(
  394. attrs={'pk': instance1.pk},
  395. can_view=True
  396. )
  397. obj_perm.save()
  398. obj_perm.users.add(self.user)
  399. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  400. # Try GET with object-level permission
  401. self.assertHttpStatus(self.client.get(self._get_url('list')), 200)
  402. # TODO: Verify that only the permitted object is returned
  403. class BulkCreateObjectsViewTestCase(ModelViewTestCase):
  404. """
  405. Create multiple instances using a single form. Expects the creation of three new instances by default.
  406. """
  407. bulk_create_count = 3
  408. bulk_create_data = {}
  409. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  410. def test_bulk_create_objects(self):
  411. initial_count = self.model.objects.count()
  412. request = {
  413. 'path': self._get_url('add'),
  414. 'data': post_data(self.bulk_create_data),
  415. }
  416. # Attempt to make the request without required permissions
  417. with disable_warnings('django.request'):
  418. self.assertHttpStatus(self.client.post(**request), 403)
  419. # Assign object-level permission
  420. obj_perm = ObjectPermission(can_add=True)
  421. obj_perm.save()
  422. obj_perm.users.add(self.user)
  423. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  424. response = self.client.post(**request)
  425. self.assertHttpStatus(response, 302)
  426. self.assertEqual(initial_count + self.bulk_create_count, self.model.objects.count())
  427. for instance in self.model.objects.order_by('-pk')[:self.bulk_create_count]:
  428. self.assertInstanceEqual(instance, self.bulk_create_data)
  429. class BulkImportObjectsViewTestCase(ModelViewTestCase):
  430. """
  431. Create multiple instances from imported data.
  432. """
  433. csv_data = ()
  434. def _get_csv_data(self):
  435. return '\n'.join(self.csv_data)
  436. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  437. def test_bulk_import_objects_without_permission(self):
  438. data = {
  439. 'csv': self._get_csv_data(),
  440. }
  441. # Test GET without permission
  442. with disable_warnings('django.request'):
  443. self.assertHttpStatus(self.client.get(self._get_url('import')), 403)
  444. # Try POST without permission
  445. response = self.client.post(self._get_url('import'), data)
  446. with disable_warnings('django.request'):
  447. self.assertHttpStatus(response, 403)
  448. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  449. def test_bulk_import_objects_with_model_permission(self):
  450. initial_count = self.model.objects.count()
  451. data = {
  452. 'csv': self._get_csv_data(),
  453. }
  454. # Assign model-level permission
  455. obj_perm = ObjectPermission(
  456. can_add=True
  457. )
  458. obj_perm.save()
  459. obj_perm.users.add(self.user)
  460. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  461. # Try GET with model-level permission
  462. self.assertHttpStatus(self.client.get(self._get_url('import')), 200)
  463. # Test POST with permission
  464. self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
  465. self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1)
  466. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  467. def test_bulk_import_objects_with_object_permission(self):
  468. initial_count = self.model.objects.count()
  469. data = {
  470. 'csv': self._get_csv_data(),
  471. }
  472. # Assign object-level permission
  473. obj_perm = ObjectPermission(
  474. attrs={'pk__gt': 0}, # Dummy permission to allow all
  475. can_add=True
  476. )
  477. obj_perm.save()
  478. obj_perm.users.add(self.user)
  479. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  480. # Test import with object-level permission
  481. self.assertHttpStatus(self.client.post(self._get_url('import'), data), 200)
  482. self.assertEqual(self.model.objects.count(), initial_count + len(self.csv_data) - 1)
  483. # TODO: Test importing non-permitted objects
  484. class BulkEditObjectsViewTestCase(ModelViewTestCase):
  485. """
  486. Edit multiple instances.
  487. """
  488. bulk_edit_data = {}
  489. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  490. def test_bulk_edit_objects_without_permission(self):
  491. pk_list = self.model.objects.values_list('pk', flat=True)[:3]
  492. data = {
  493. 'pk': pk_list,
  494. '_apply': True, # Form button
  495. }
  496. # Test GET without permission
  497. with disable_warnings('django.request'):
  498. self.assertHttpStatus(self.client.get(self._get_url('bulk_edit')), 403)
  499. # Try POST without permission
  500. with disable_warnings('django.request'):
  501. self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 403)
  502. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  503. def test_bulk_edit_objects_with_model_permission(self):
  504. pk_list = self.model.objects.values_list('pk', flat=True)[:3]
  505. data = {
  506. 'pk': pk_list,
  507. '_apply': True, # Form button
  508. }
  509. # Append the form data to the request
  510. data.update(post_data(self.bulk_edit_data))
  511. # Assign model-level permission
  512. obj_perm = ObjectPermission(
  513. can_change=True
  514. )
  515. obj_perm.save()
  516. obj_perm.users.add(self.user)
  517. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  518. # Try POST with model-level permission
  519. self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 302)
  520. for i, instance in enumerate(self.model.objects.filter(pk__in=pk_list)):
  521. self.assertInstanceEqual(instance, self.bulk_edit_data)
  522. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  523. def test_bulk_edit_objects_with_object_permission(self):
  524. pk_list = self.model.objects.values_list('pk', flat=True)[:3]
  525. data = {
  526. 'pk': pk_list,
  527. '_apply': True, # Form button
  528. }
  529. # Append the form data to the request
  530. data.update(post_data(self.bulk_edit_data))
  531. # Assign object-level permission
  532. obj_perm = ObjectPermission(
  533. attrs={'pk__in': list(pk_list)},
  534. can_change=True
  535. )
  536. obj_perm.save()
  537. obj_perm.users.add(self.user)
  538. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  539. # Try POST with model-level permission
  540. self.assertHttpStatus(self.client.post(self._get_url('bulk_edit'), data), 302)
  541. for i, instance in enumerate(self.model.objects.filter(pk__in=pk_list)):
  542. self.assertInstanceEqual(instance, self.bulk_edit_data)
  543. # TODO: Test editing non-permitted objects
  544. class BulkDeleteObjectsViewTestCase(ModelViewTestCase):
  545. """
  546. Delete multiple instances.
  547. """
  548. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  549. def test_bulk_delete_objects_without_permission(self):
  550. pk_list = self.model.objects.values_list('pk', flat=True)[:3]
  551. data = {
  552. 'pk': pk_list,
  553. 'confirm': True,
  554. '_confirm': True, # Form button
  555. }
  556. # Test GET without permission
  557. with disable_warnings('django.request'):
  558. self.assertHttpStatus(self.client.get(self._get_url('bulk_delete')), 403)
  559. # Try POST without permission
  560. with disable_warnings('django.request'):
  561. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 403)
  562. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  563. def test_bulk_delete_objects_with_model_permission(self):
  564. pk_list = self.model.objects.values_list('pk', flat=True)
  565. data = {
  566. 'pk': pk_list,
  567. 'confirm': True,
  568. '_confirm': True, # Form button
  569. }
  570. # Assign model-level permission
  571. obj_perm = ObjectPermission(
  572. can_delete=True
  573. )
  574. obj_perm.save()
  575. obj_perm.users.add(self.user)
  576. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  577. # Try POST with model-level permission
  578. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
  579. self.assertEqual(self.model.objects.count(), 0)
  580. @override_settings(EXEMPT_VIEW_PERMISSIONS=[])
  581. def test_bulk_delete_objects_with_object_permission(self):
  582. pk_list = self.model.objects.values_list('pk', flat=True)
  583. data = {
  584. 'pk': pk_list,
  585. 'confirm': True,
  586. '_confirm': True, # Form button
  587. }
  588. # Assign object-level permission
  589. obj_perm = ObjectPermission(
  590. attrs={'pk__in': list(pk_list)},
  591. can_delete=True
  592. )
  593. obj_perm.save()
  594. obj_perm.users.add(self.user)
  595. obj_perm.content_types.add(ContentType.objects.get_for_model(self.model))
  596. # Try POST with object-level permission
  597. self.assertHttpStatus(self.client.post(self._get_url('bulk_delete'), data), 302)
  598. self.assertEqual(self.model.objects.count(), 0)
  599. # TODO: Test deleting non-permitted objects
  600. class PrimaryObjectViewTestCase(
  601. GetObjectViewTestCase,
  602. CreateObjectViewTestCase,
  603. EditObjectViewTestCase,
  604. DeleteObjectViewTestCase,
  605. ListObjectsViewTestCase,
  606. BulkImportObjectsViewTestCase,
  607. BulkEditObjectsViewTestCase,
  608. BulkDeleteObjectsViewTestCase,
  609. ):
  610. """
  611. TestCase suitable for testing all standard View functions for primary objects
  612. """
  613. maxDiff = None
  614. class OrganizationalObjectViewTestCase(
  615. CreateObjectViewTestCase,
  616. EditObjectViewTestCase,
  617. ListObjectsViewTestCase,
  618. BulkImportObjectsViewTestCase,
  619. BulkDeleteObjectsViewTestCase,
  620. ):
  621. """
  622. TestCase suitable for all organizational objects
  623. """
  624. maxDiff = None
  625. class DeviceComponentTemplateViewTestCase(
  626. EditObjectViewTestCase,
  627. DeleteObjectViewTestCase,
  628. BulkCreateObjectsViewTestCase,
  629. BulkEditObjectsViewTestCase,
  630. BulkDeleteObjectsViewTestCase,
  631. ):
  632. """
  633. TestCase suitable for testing device component template models (ConsolePortTemplates, InterfaceTemplates, etc.)
  634. """
  635. maxDiff = None
  636. class DeviceComponentViewTestCase(
  637. EditObjectViewTestCase,
  638. DeleteObjectViewTestCase,
  639. ListObjectsViewTestCase,
  640. BulkCreateObjectsViewTestCase,
  641. BulkImportObjectsViewTestCase,
  642. BulkEditObjectsViewTestCase,
  643. BulkDeleteObjectsViewTestCase,
  644. ):
  645. """
  646. TestCase suitable for testing device component models (ConsolePorts, Interfaces, etc.)
  647. """
  648. maxDiff = None