views.py 38 KB

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