test_changelog.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.test import override_settings
  3. from django.urls import reverse
  4. from rest_framework import status
  5. from core.models import ObjectType
  6. from dcim.choices import SiteStatusChoices
  7. from dcim.models import Site
  8. from extras.choices import *
  9. from extras.models import CustomField, CustomFieldChoiceSet, ObjectChange, Tag
  10. from utilities.testing import APITestCase
  11. from utilities.testing.utils import create_tags, post_data
  12. from utilities.testing.views import ModelViewTestCase
  13. class ChangeLogViewTest(ModelViewTestCase):
  14. model = Site
  15. @classmethod
  16. def setUpTestData(cls):
  17. choice_set = CustomFieldChoiceSet.objects.create(
  18. name='Choice Set 1',
  19. extra_choices=(('foo', 'Foo'), ('bar', 'Bar'))
  20. )
  21. # Create a custom field on the Site model
  22. site_type = ObjectType.objects.get_for_model(Site)
  23. cf = CustomField(
  24. type=CustomFieldTypeChoices.TYPE_TEXT,
  25. name='cf1',
  26. required=False
  27. )
  28. cf.save()
  29. cf.object_types.set([site_type])
  30. # Create a select custom field on the Site model
  31. cf_select = CustomField(
  32. type=CustomFieldTypeChoices.TYPE_SELECT,
  33. name='cf2',
  34. required=False,
  35. choice_set=choice_set
  36. )
  37. cf_select.save()
  38. cf_select.object_types.set([site_type])
  39. def test_create_object(self):
  40. tags = create_tags('Tag 1', 'Tag 2')
  41. form_data = {
  42. 'name': 'Site 1',
  43. 'slug': 'site-1',
  44. 'status': SiteStatusChoices.STATUS_ACTIVE,
  45. 'cf_cf1': 'ABC',
  46. 'cf_cf2': 'bar',
  47. 'tags': [tag.pk for tag in tags],
  48. }
  49. request = {
  50. 'path': self._get_url('add'),
  51. 'data': post_data(form_data),
  52. }
  53. self.add_permissions('dcim.add_site', 'extras.view_tag')
  54. response = self.client.post(**request)
  55. self.assertHttpStatus(response, 302)
  56. # Verify the creation of a new ObjectChange record
  57. site = Site.objects.get(name='Site 1')
  58. oc = ObjectChange.objects.get(
  59. changed_object_type=ContentType.objects.get_for_model(Site),
  60. changed_object_id=site.pk
  61. )
  62. self.assertEqual(oc.changed_object, site)
  63. self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE)
  64. self.assertEqual(oc.prechange_data, None)
  65. self.assertEqual(oc.postchange_data['custom_fields']['cf1'], form_data['cf_cf1'])
  66. self.assertEqual(oc.postchange_data['custom_fields']['cf2'], form_data['cf_cf2'])
  67. self.assertEqual(oc.postchange_data['tags'], ['Tag 1', 'Tag 2'])
  68. def test_update_object(self):
  69. site = Site(name='Site 1', slug='site-1')
  70. site.save()
  71. tags = create_tags('Tag 1', 'Tag 2', 'Tag 3')
  72. site.tags.set(['Tag 1', 'Tag 2'])
  73. form_data = {
  74. 'name': 'Site X',
  75. 'slug': 'site-x',
  76. 'status': SiteStatusChoices.STATUS_PLANNED,
  77. 'cf_cf1': 'DEF',
  78. 'cf_cf2': 'foo',
  79. 'tags': [tags[2].pk],
  80. }
  81. request = {
  82. 'path': self._get_url('edit', instance=site),
  83. 'data': post_data(form_data),
  84. }
  85. self.add_permissions('dcim.change_site', 'extras.view_tag')
  86. response = self.client.post(**request)
  87. self.assertHttpStatus(response, 302)
  88. # Verify the creation of a new ObjectChange record
  89. site.refresh_from_db()
  90. oc = ObjectChange.objects.filter(
  91. changed_object_type=ContentType.objects.get_for_model(Site),
  92. changed_object_id=site.pk
  93. ).first()
  94. self.assertEqual(oc.changed_object, site)
  95. self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE)
  96. self.assertEqual(oc.prechange_data['name'], 'Site 1')
  97. self.assertEqual(oc.prechange_data['tags'], ['Tag 1', 'Tag 2'])
  98. self.assertEqual(oc.postchange_data['custom_fields']['cf1'], form_data['cf_cf1'])
  99. self.assertEqual(oc.postchange_data['custom_fields']['cf2'], form_data['cf_cf2'])
  100. self.assertEqual(oc.postchange_data['tags'], ['Tag 3'])
  101. def test_delete_object(self):
  102. site = Site(
  103. name='Site 1',
  104. slug='site-1',
  105. custom_field_data={
  106. 'cf1': 'ABC',
  107. 'cf2': 'Bar'
  108. }
  109. )
  110. site.save()
  111. create_tags('Tag 1', 'Tag 2')
  112. site.tags.set(['Tag 1', 'Tag 2'])
  113. request = {
  114. 'path': self._get_url('delete', instance=site),
  115. 'data': post_data({'confirm': True}),
  116. }
  117. self.add_permissions('dcim.delete_site')
  118. response = self.client.post(**request)
  119. self.assertHttpStatus(response, 302)
  120. oc = ObjectChange.objects.first()
  121. self.assertEqual(oc.changed_object, None)
  122. self.assertEqual(oc.object_repr, site.name)
  123. self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE)
  124. self.assertEqual(oc.prechange_data['custom_fields']['cf1'], 'ABC')
  125. self.assertEqual(oc.prechange_data['custom_fields']['cf2'], 'Bar')
  126. self.assertEqual(oc.prechange_data['tags'], ['Tag 1', 'Tag 2'])
  127. self.assertEqual(oc.postchange_data, None)
  128. def test_bulk_update_objects(self):
  129. sites = (
  130. Site(name='Site 1', slug='site-1', status=SiteStatusChoices.STATUS_ACTIVE),
  131. Site(name='Site 2', slug='site-2', status=SiteStatusChoices.STATUS_ACTIVE),
  132. Site(name='Site 3', slug='site-3', status=SiteStatusChoices.STATUS_ACTIVE),
  133. )
  134. Site.objects.bulk_create(sites)
  135. form_data = {
  136. 'pk': [site.pk for site in sites],
  137. '_apply': True,
  138. 'status': SiteStatusChoices.STATUS_PLANNED,
  139. 'description': 'New description',
  140. }
  141. request = {
  142. 'path': self._get_url('bulk_edit'),
  143. 'data': post_data(form_data),
  144. }
  145. self.add_permissions('dcim.view_site', 'dcim.change_site')
  146. response = self.client.post(**request)
  147. self.assertHttpStatus(response, 302)
  148. objectchange = ObjectChange.objects.get(
  149. changed_object_type=ContentType.objects.get_for_model(Site),
  150. changed_object_id=sites[0].pk
  151. )
  152. self.assertEqual(objectchange.changed_object, sites[0])
  153. self.assertEqual(objectchange.action, ObjectChangeActionChoices.ACTION_UPDATE)
  154. self.assertEqual(objectchange.prechange_data['status'], SiteStatusChoices.STATUS_ACTIVE)
  155. self.assertEqual(objectchange.prechange_data['description'], '')
  156. self.assertEqual(objectchange.postchange_data['status'], form_data['status'])
  157. self.assertEqual(objectchange.postchange_data['description'], form_data['description'])
  158. def test_bulk_delete_objects(self):
  159. sites = (
  160. Site(name='Site 1', slug='site-1', status=SiteStatusChoices.STATUS_ACTIVE),
  161. Site(name='Site 2', slug='site-2', status=SiteStatusChoices.STATUS_ACTIVE),
  162. Site(name='Site 3', slug='site-3', status=SiteStatusChoices.STATUS_ACTIVE),
  163. )
  164. Site.objects.bulk_create(sites)
  165. form_data = {
  166. 'pk': [site.pk for site in sites],
  167. 'confirm': True,
  168. '_confirm': True,
  169. }
  170. request = {
  171. 'path': self._get_url('bulk_delete'),
  172. 'data': post_data(form_data),
  173. }
  174. self.add_permissions('dcim.delete_site')
  175. response = self.client.post(**request)
  176. self.assertHttpStatus(response, 302)
  177. objectchange = ObjectChange.objects.get(
  178. changed_object_type=ContentType.objects.get_for_model(Site),
  179. changed_object_id=sites[0].pk
  180. )
  181. self.assertEqual(objectchange.changed_object_type, ContentType.objects.get_for_model(Site))
  182. self.assertEqual(objectchange.changed_object_id, sites[0].pk)
  183. self.assertEqual(objectchange.action, ObjectChangeActionChoices.ACTION_DELETE)
  184. self.assertEqual(objectchange.prechange_data['name'], sites[0].name)
  185. self.assertEqual(objectchange.prechange_data['slug'], sites[0].slug)
  186. self.assertEqual(objectchange.postchange_data, None)
  187. @override_settings(CHANGELOG_SKIP_EMPTY_CHANGES=False)
  188. def test_update_object_change(self):
  189. # Create a Site
  190. site = Site.objects.create(
  191. name='Site 1',
  192. slug='site-1',
  193. status=SiteStatusChoices.STATUS_PLANNED,
  194. custom_field_data={
  195. 'cf1': None,
  196. 'cf2': None
  197. }
  198. )
  199. # Update it with the same field values
  200. form_data = {
  201. 'name': site.name,
  202. 'slug': site.slug,
  203. 'status': SiteStatusChoices.STATUS_PLANNED,
  204. }
  205. request = {
  206. 'path': self._get_url('edit', instance=site),
  207. 'data': post_data(form_data),
  208. }
  209. self.add_permissions('dcim.change_site', 'extras.view_tag')
  210. response = self.client.post(**request)
  211. self.assertHttpStatus(response, 302)
  212. # Check that an ObjectChange record has been created
  213. self.assertEqual(ObjectChange.objects.count(), 1)
  214. @override_settings(CHANGELOG_SKIP_EMPTY_CHANGES=True)
  215. def test_update_object_nochange(self):
  216. # Create a Site
  217. site = Site.objects.create(
  218. name='Site 1',
  219. slug='site-1',
  220. status=SiteStatusChoices.STATUS_PLANNED,
  221. custom_field_data={
  222. 'cf1': None,
  223. 'cf2': None
  224. }
  225. )
  226. # Update it with the same field values
  227. form_data = {
  228. 'name': site.name,
  229. 'slug': site.slug,
  230. 'status': SiteStatusChoices.STATUS_PLANNED,
  231. }
  232. request = {
  233. 'path': self._get_url('edit', instance=site),
  234. 'data': post_data(form_data),
  235. }
  236. self.add_permissions('dcim.change_site', 'extras.view_tag')
  237. response = self.client.post(**request)
  238. self.assertHttpStatus(response, 302)
  239. # Check that no ObjectChange records have been created
  240. self.assertEqual(ObjectChange.objects.count(), 0)
  241. class ChangeLogAPITest(APITestCase):
  242. @classmethod
  243. def setUpTestData(cls):
  244. # Create a custom field on the Site model
  245. site_type = ObjectType.objects.get_for_model(Site)
  246. cf = CustomField(
  247. type=CustomFieldTypeChoices.TYPE_TEXT,
  248. name='cf1',
  249. required=False
  250. )
  251. cf.save()
  252. cf.object_types.set([site_type])
  253. # Create a select custom field on the Site model
  254. choice_set = CustomFieldChoiceSet.objects.create(
  255. name='Choice Set 1',
  256. extra_choices=(('foo', 'Foo'), ('bar', 'Bar'))
  257. )
  258. cf_select = CustomField(
  259. type=CustomFieldTypeChoices.TYPE_SELECT,
  260. name='cf2',
  261. required=False,
  262. choice_set=choice_set
  263. )
  264. cf_select.save()
  265. cf_select.object_types.set([site_type])
  266. # Create some tags
  267. tags = (
  268. Tag(name='Tag 1', slug='tag-1'),
  269. Tag(name='Tag 2', slug='tag-2'),
  270. Tag(name='Tag 3', slug='tag-3'),
  271. )
  272. Tag.objects.bulk_create(tags)
  273. def test_create_object(self):
  274. data = {
  275. 'name': 'Site 1',
  276. 'slug': 'site-1',
  277. 'custom_fields': {
  278. 'cf1': 'ABC',
  279. 'cf2': 'bar',
  280. },
  281. 'tags': [
  282. {'name': 'Tag 1'},
  283. {'name': 'Tag 2'},
  284. ]
  285. }
  286. self.assertEqual(ObjectChange.objects.count(), 0)
  287. url = reverse('dcim-api:site-list')
  288. self.add_permissions('dcim.add_site')
  289. response = self.client.post(url, data, format='json', **self.header)
  290. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  291. site = Site.objects.get(pk=response.data['id'])
  292. oc = ObjectChange.objects.get(
  293. changed_object_type=ContentType.objects.get_for_model(Site),
  294. changed_object_id=site.pk
  295. )
  296. self.assertEqual(oc.changed_object, site)
  297. self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_CREATE)
  298. self.assertEqual(oc.prechange_data, None)
  299. self.assertEqual(oc.postchange_data['custom_fields'], data['custom_fields'])
  300. self.assertEqual(oc.postchange_data['tags'], ['Tag 1', 'Tag 2'])
  301. def test_update_object(self):
  302. site = Site(name='Site 1', slug='site-1')
  303. site.save()
  304. data = {
  305. 'name': 'Site X',
  306. 'slug': 'site-x',
  307. 'custom_fields': {
  308. 'cf1': 'DEF',
  309. 'cf2': 'foo',
  310. },
  311. 'tags': [
  312. {'name': 'Tag 3'}
  313. ]
  314. }
  315. self.assertEqual(ObjectChange.objects.count(), 0)
  316. self.add_permissions('dcim.change_site')
  317. url = reverse('dcim-api:site-detail', kwargs={'pk': site.pk})
  318. response = self.client.put(url, data, format='json', **self.header)
  319. self.assertHttpStatus(response, status.HTTP_200_OK)
  320. site = Site.objects.get(pk=response.data['id'])
  321. oc = ObjectChange.objects.get(
  322. changed_object_type=ContentType.objects.get_for_model(Site),
  323. changed_object_id=site.pk
  324. )
  325. self.assertEqual(oc.changed_object, site)
  326. self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_UPDATE)
  327. self.assertEqual(oc.postchange_data['custom_fields'], data['custom_fields'])
  328. self.assertEqual(oc.postchange_data['tags'], ['Tag 3'])
  329. def test_delete_object(self):
  330. site = Site(
  331. name='Site 1',
  332. slug='site-1',
  333. custom_field_data={
  334. 'cf1': 'ABC',
  335. 'cf2': 'Bar'
  336. }
  337. )
  338. site.save()
  339. site.tags.set(Tag.objects.all()[:2])
  340. self.assertEqual(ObjectChange.objects.count(), 0)
  341. self.add_permissions('dcim.delete_site')
  342. url = reverse('dcim-api:site-detail', kwargs={'pk': site.pk})
  343. response = self.client.delete(url, **self.header)
  344. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  345. self.assertEqual(Site.objects.count(), 0)
  346. oc = ObjectChange.objects.first()
  347. self.assertEqual(oc.changed_object, None)
  348. self.assertEqual(oc.object_repr, site.name)
  349. self.assertEqual(oc.action, ObjectChangeActionChoices.ACTION_DELETE)
  350. self.assertEqual(oc.prechange_data['custom_fields']['cf1'], 'ABC')
  351. self.assertEqual(oc.prechange_data['custom_fields']['cf2'], 'Bar')
  352. self.assertEqual(oc.prechange_data['tags'], ['Tag 1', 'Tag 2'])
  353. self.assertEqual(oc.postchange_data, None)
  354. def test_bulk_create_objects(self):
  355. data = (
  356. {
  357. 'name': 'Site 1',
  358. 'slug': 'site-1',
  359. },
  360. {
  361. 'name': 'Site 2',
  362. 'slug': 'site-2',
  363. },
  364. {
  365. 'name': 'Site 3',
  366. 'slug': 'site-3',
  367. },
  368. )
  369. self.assertEqual(ObjectChange.objects.count(), 0)
  370. url = reverse('dcim-api:site-list')
  371. self.add_permissions('dcim.add_site')
  372. response = self.client.post(url, data, format='json', **self.header)
  373. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  374. self.assertEqual(ObjectChange.objects.count(), 3)
  375. site1 = Site.objects.get(pk=response.data[0]['id'])
  376. objectchange = ObjectChange.objects.get(
  377. changed_object_type=ContentType.objects.get_for_model(Site),
  378. changed_object_id=site1.pk
  379. )
  380. self.assertEqual(objectchange.changed_object, site1)
  381. self.assertEqual(objectchange.action, ObjectChangeActionChoices.ACTION_CREATE)
  382. self.assertEqual(objectchange.prechange_data, None)
  383. self.assertEqual(objectchange.postchange_data['name'], data[0]['name'])
  384. self.assertEqual(objectchange.postchange_data['slug'], data[0]['slug'])
  385. def test_bulk_edit_objects(self):
  386. sites = (
  387. Site(name='Site 1', slug='site-1'),
  388. Site(name='Site 2', slug='site-2'),
  389. Site(name='Site 3', slug='site-3'),
  390. )
  391. Site.objects.bulk_create(sites)
  392. data = (
  393. {
  394. 'id': sites[0].pk,
  395. 'name': 'Site A',
  396. 'slug': 'site-A',
  397. },
  398. {
  399. 'id': sites[1].pk,
  400. 'name': 'Site B',
  401. 'slug': 'site-b',
  402. },
  403. {
  404. 'id': sites[2].pk,
  405. 'name': 'Site C',
  406. 'slug': 'site-c',
  407. },
  408. )
  409. self.assertEqual(ObjectChange.objects.count(), 0)
  410. url = reverse('dcim-api:site-list')
  411. self.add_permissions('dcim.change_site')
  412. response = self.client.patch(url, data, format='json', **self.header)
  413. self.assertHttpStatus(response, status.HTTP_200_OK)
  414. self.assertEqual(ObjectChange.objects.count(), 3)
  415. objectchange = ObjectChange.objects.get(
  416. changed_object_type=ContentType.objects.get_for_model(Site),
  417. changed_object_id=sites[0].pk
  418. )
  419. self.assertEqual(objectchange.changed_object, sites[0])
  420. self.assertEqual(objectchange.action, ObjectChangeActionChoices.ACTION_UPDATE)
  421. self.assertEqual(objectchange.prechange_data['name'], 'Site 1')
  422. self.assertEqual(objectchange.prechange_data['slug'], 'site-1')
  423. self.assertEqual(objectchange.postchange_data['name'], data[0]['name'])
  424. self.assertEqual(objectchange.postchange_data['slug'], data[0]['slug'])
  425. def test_bulk_delete_objects(self):
  426. sites = (
  427. Site(name='Site 1', slug='site-1'),
  428. Site(name='Site 2', slug='site-2'),
  429. Site(name='Site 3', slug='site-3'),
  430. )
  431. Site.objects.bulk_create(sites)
  432. data = (
  433. {
  434. 'id': sites[0].pk,
  435. },
  436. {
  437. 'id': sites[1].pk,
  438. },
  439. {
  440. 'id': sites[2].pk,
  441. },
  442. )
  443. self.assertEqual(ObjectChange.objects.count(), 0)
  444. url = reverse('dcim-api:site-list')
  445. self.add_permissions('dcim.delete_site')
  446. response = self.client.delete(url, data, format='json', **self.header)
  447. self.assertHttpStatus(response, status.HTTP_204_NO_CONTENT)
  448. self.assertEqual(ObjectChange.objects.count(), 3)
  449. objectchange = ObjectChange.objects.get(
  450. changed_object_type=ContentType.objects.get_for_model(Site),
  451. changed_object_id=sites[0].pk
  452. )
  453. self.assertEqual(objectchange.changed_object_type, ContentType.objects.get_for_model(Site))
  454. self.assertEqual(objectchange.changed_object_id, sites[0].pk)
  455. self.assertEqual(objectchange.action, ObjectChangeActionChoices.ACTION_DELETE)
  456. self.assertEqual(objectchange.prechange_data['name'], 'Site 1')
  457. self.assertEqual(objectchange.prechange_data['slug'], 'site-1')
  458. self.assertEqual(objectchange.postchange_data, None)