test_customfields.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. from datetime import date
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.urls import reverse
  4. from rest_framework import status
  5. from dcim.forms import SiteCSVForm
  6. from dcim.models import Site
  7. from extras.choices import *
  8. from extras.models import CustomField, CustomFieldValue, CustomFieldChoice
  9. from utilities.testing import APITestCase, TestCase
  10. from virtualization.models import VirtualMachine
  11. class CustomFieldTest(TestCase):
  12. def setUp(self):
  13. Site.objects.bulk_create([
  14. Site(name='Site A', slug='site-a'),
  15. Site(name='Site B', slug='site-b'),
  16. Site(name='Site C', slug='site-c'),
  17. ])
  18. def test_simple_fields(self):
  19. DATA = (
  20. {'field_type': CustomFieldTypeChoices.TYPE_TEXT, 'field_value': 'Foobar!', 'empty_value': ''},
  21. {'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 0, 'empty_value': None},
  22. {'field_type': CustomFieldTypeChoices.TYPE_INTEGER, 'field_value': 42, 'empty_value': None},
  23. {'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': True, 'empty_value': None},
  24. {'field_type': CustomFieldTypeChoices.TYPE_BOOLEAN, 'field_value': False, 'empty_value': None},
  25. {'field_type': CustomFieldTypeChoices.TYPE_DATE, 'field_value': date(2016, 6, 23), 'empty_value': None},
  26. {'field_type': CustomFieldTypeChoices.TYPE_URL, 'field_value': 'http://example.com/', 'empty_value': ''},
  27. )
  28. obj_type = ContentType.objects.get_for_model(Site)
  29. for data in DATA:
  30. # Create a custom field
  31. cf = CustomField(type=data['field_type'], name='my_field', required=False)
  32. cf.save()
  33. cf.obj_type.set([obj_type])
  34. cf.save()
  35. # Assign a value to the first Site
  36. site = Site.objects.first()
  37. cfv = CustomFieldValue(field=cf, obj_type=obj_type, obj_id=site.id)
  38. cfv.value = data['field_value']
  39. cfv.save()
  40. # Retrieve the stored value
  41. cfv = CustomFieldValue.objects.filter(obj_type=obj_type, obj_id=site.pk).first()
  42. self.assertEqual(cfv.value, data['field_value'])
  43. # Delete the stored value
  44. cfv.value = data['empty_value']
  45. cfv.save()
  46. self.assertEqual(CustomFieldValue.objects.filter(obj_type=obj_type, obj_id=site.pk).count(), 0)
  47. # Delete the custom field
  48. cf.delete()
  49. def test_select_field(self):
  50. obj_type = ContentType.objects.get_for_model(Site)
  51. # Create a custom field
  52. cf = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='my_field', required=False)
  53. cf.save()
  54. cf.obj_type.set([obj_type])
  55. cf.save()
  56. # Create some choices for the field
  57. CustomFieldChoice.objects.bulk_create([
  58. CustomFieldChoice(field=cf, value='Option A'),
  59. CustomFieldChoice(field=cf, value='Option B'),
  60. CustomFieldChoice(field=cf, value='Option C'),
  61. ])
  62. # Assign a value to the first Site
  63. site = Site.objects.first()
  64. cfv = CustomFieldValue(field=cf, obj_type=obj_type, obj_id=site.id)
  65. cfv.value = cf.choices.first()
  66. cfv.save()
  67. # Retrieve the stored value
  68. cfv = CustomFieldValue.objects.filter(obj_type=obj_type, obj_id=site.pk).first()
  69. self.assertEqual(str(cfv.value), 'Option A')
  70. # Delete the stored value
  71. cfv.value = None
  72. cfv.save()
  73. self.assertEqual(CustomFieldValue.objects.filter(obj_type=obj_type, obj_id=site.pk).count(), 0)
  74. # Delete the custom field
  75. cf.delete()
  76. class CustomFieldManagerTest(TestCase):
  77. def setUp(self):
  78. content_type = ContentType.objects.get_for_model(Site)
  79. custom_field = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo')
  80. custom_field.save()
  81. custom_field.obj_type.set([content_type])
  82. def test_get_for_model(self):
  83. self.assertEqual(CustomField.objects.get_for_model(Site).count(), 1)
  84. self.assertEqual(CustomField.objects.get_for_model(VirtualMachine).count(), 0)
  85. class CustomFieldAPITest(APITestCase):
  86. @classmethod
  87. def setUpTestData(cls):
  88. content_type = ContentType.objects.get_for_model(Site)
  89. # Text custom field
  90. cls.cf_text = CustomField(type=CustomFieldTypeChoices.TYPE_TEXT, name='text_field', default='foo')
  91. cls.cf_text.save()
  92. cls.cf_text.obj_type.set([content_type])
  93. # Integer custom field
  94. cls.cf_integer = CustomField(type=CustomFieldTypeChoices.TYPE_INTEGER, name='number_field', default=123)
  95. cls.cf_integer.save()
  96. cls.cf_integer.obj_type.set([content_type])
  97. # Boolean custom field
  98. cls.cf_boolean = CustomField(type=CustomFieldTypeChoices.TYPE_BOOLEAN, name='boolean_field', default=False)
  99. cls.cf_boolean.save()
  100. cls.cf_boolean.obj_type.set([content_type])
  101. # Date custom field
  102. cls.cf_date = CustomField(type=CustomFieldTypeChoices.TYPE_DATE, name='date_field', default='2020-01-01')
  103. cls.cf_date.save()
  104. cls.cf_date.obj_type.set([content_type])
  105. # URL custom field
  106. cls.cf_url = CustomField(type=CustomFieldTypeChoices.TYPE_URL, name='url_field', default='http://example.com/1')
  107. cls.cf_url.save()
  108. cls.cf_url.obj_type.set([content_type])
  109. # Select custom field
  110. cls.cf_select = CustomField(type=CustomFieldTypeChoices.TYPE_SELECT, name='choice_field')
  111. cls.cf_select.save()
  112. cls.cf_select.obj_type.set([content_type])
  113. cls.cf_select_choice1 = CustomFieldChoice(field=cls.cf_select, value='Foo')
  114. cls.cf_select_choice1.save()
  115. cls.cf_select_choice2 = CustomFieldChoice(field=cls.cf_select, value='Bar')
  116. cls.cf_select_choice2.save()
  117. cls.cf_select_choice3 = CustomFieldChoice(field=cls.cf_select, value='Baz')
  118. cls.cf_select_choice3.save()
  119. cls.cf_select.default = cls.cf_select_choice1.value
  120. cls.cf_select.save()
  121. # Create some sites
  122. cls.sites = (
  123. Site(name='Site 1', slug='site-1'),
  124. Site(name='Site 2', slug='site-2'),
  125. )
  126. Site.objects.bulk_create(cls.sites)
  127. # Assign custom field values for site 2
  128. site2_cfvs = {
  129. cls.cf_text: 'bar',
  130. cls.cf_integer: 456,
  131. cls.cf_boolean: True,
  132. cls.cf_date: '2020-01-02',
  133. cls.cf_url: 'http://example.com/2',
  134. cls.cf_select: cls.cf_select_choice2.pk,
  135. }
  136. for field, value in site2_cfvs.items():
  137. cfv = CustomFieldValue(field=field, obj=cls.sites[1])
  138. cfv.value = value
  139. cfv.save()
  140. def test_get_single_object_without_custom_field_values(self):
  141. """
  142. Validate that custom fields are present on an object even if it has no values defined.
  143. """
  144. url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[0].pk})
  145. self.add_permissions('dcim.view_site')
  146. response = self.client.get(url, **self.header)
  147. self.assertEqual(response.data['name'], self.sites[0].name)
  148. self.assertEqual(response.data['custom_fields'], {
  149. 'text_field': None,
  150. 'number_field': None,
  151. 'boolean_field': None,
  152. 'date_field': None,
  153. 'url_field': None,
  154. 'choice_field': None,
  155. })
  156. def test_get_single_object_with_custom_field_values(self):
  157. """
  158. Validate that custom fields are present and correctly set for an object with values defined.
  159. """
  160. site2_cfvs = {
  161. cfv.field.name: cfv.value for cfv in self.sites[1].custom_field_values.all()
  162. }
  163. url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[1].pk})
  164. self.add_permissions('dcim.view_site')
  165. response = self.client.get(url, **self.header)
  166. self.assertEqual(response.data['name'], self.sites[1].name)
  167. self.assertEqual(response.data['custom_fields']['text_field'], site2_cfvs['text_field'])
  168. self.assertEqual(response.data['custom_fields']['number_field'], site2_cfvs['number_field'])
  169. self.assertEqual(response.data['custom_fields']['boolean_field'], site2_cfvs['boolean_field'])
  170. self.assertEqual(response.data['custom_fields']['date_field'], site2_cfvs['date_field'])
  171. self.assertEqual(response.data['custom_fields']['url_field'], site2_cfvs['url_field'])
  172. self.assertEqual(response.data['custom_fields']['choice_field']['label'], self.cf_select_choice2.value)
  173. def test_create_single_object_with_defaults(self):
  174. """
  175. Create a new site with no specified custom field values and check that it received the default values.
  176. """
  177. data = {
  178. 'name': 'Site 3',
  179. 'slug': 'site-3',
  180. }
  181. url = reverse('dcim-api:site-list')
  182. self.add_permissions('dcim.add_site')
  183. response = self.client.post(url, data, format='json', **self.header)
  184. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  185. # Validate response data
  186. response_cf = response.data['custom_fields']
  187. self.assertEqual(response_cf['text_field'], self.cf_text.default)
  188. self.assertEqual(response_cf['number_field'], self.cf_integer.default)
  189. self.assertEqual(response_cf['boolean_field'], self.cf_boolean.default)
  190. self.assertEqual(response_cf['date_field'], self.cf_date.default)
  191. self.assertEqual(response_cf['url_field'], self.cf_url.default)
  192. self.assertEqual(response_cf['choice_field'], self.cf_select_choice1.pk)
  193. # Validate database data
  194. site = Site.objects.get(pk=response.data['id'])
  195. cfvs = {
  196. cfv.field.name: cfv.value for cfv in site.custom_field_values.all()
  197. }
  198. self.assertEqual(cfvs['text_field'], self.cf_text.default)
  199. self.assertEqual(cfvs['number_field'], self.cf_integer.default)
  200. self.assertEqual(cfvs['boolean_field'], self.cf_boolean.default)
  201. self.assertEqual(str(cfvs['date_field']), self.cf_date.default)
  202. self.assertEqual(cfvs['url_field'], self.cf_url.default)
  203. self.assertEqual(cfvs['choice_field'].pk, self.cf_select_choice1.pk)
  204. def test_create_single_object_with_values(self):
  205. """
  206. Create a single new site with a value for each type of custom field.
  207. """
  208. data = {
  209. 'name': 'Site 3',
  210. 'slug': 'site-3',
  211. 'custom_fields': {
  212. 'text_field': 'bar',
  213. 'number_field': 456,
  214. 'boolean_field': True,
  215. 'date_field': '2020-01-02',
  216. 'url_field': 'http://example.com/2',
  217. 'choice_field': self.cf_select_choice2.pk,
  218. },
  219. }
  220. url = reverse('dcim-api:site-list')
  221. self.add_permissions('dcim.add_site')
  222. response = self.client.post(url, data, format='json', **self.header)
  223. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  224. # Validate response data
  225. response_cf = response.data['custom_fields']
  226. data_cf = data['custom_fields']
  227. self.assertEqual(response_cf['text_field'], data_cf['text_field'])
  228. self.assertEqual(response_cf['number_field'], data_cf['number_field'])
  229. self.assertEqual(response_cf['boolean_field'], data_cf['boolean_field'])
  230. self.assertEqual(response_cf['date_field'], data_cf['date_field'])
  231. self.assertEqual(response_cf['url_field'], data_cf['url_field'])
  232. self.assertEqual(response_cf['choice_field'], data_cf['choice_field'])
  233. # Validate database data
  234. site = Site.objects.get(pk=response.data['id'])
  235. cfvs = {
  236. cfv.field.name: cfv.value for cfv in site.custom_field_values.all()
  237. }
  238. self.assertEqual(cfvs['text_field'], data_cf['text_field'])
  239. self.assertEqual(cfvs['number_field'], data_cf['number_field'])
  240. self.assertEqual(cfvs['boolean_field'], data_cf['boolean_field'])
  241. self.assertEqual(str(cfvs['date_field']), data_cf['date_field'])
  242. self.assertEqual(cfvs['url_field'], data_cf['url_field'])
  243. self.assertEqual(cfvs['choice_field'].pk, data_cf['choice_field'])
  244. def test_create_multiple_objects_with_defaults(self):
  245. """
  246. Create three news sites with no specified custom field values and check that each received
  247. the default custom field values.
  248. """
  249. data = (
  250. {
  251. 'name': 'Site 3',
  252. 'slug': 'site-3',
  253. },
  254. {
  255. 'name': 'Site 4',
  256. 'slug': 'site-4',
  257. },
  258. {
  259. 'name': 'Site 5',
  260. 'slug': 'site-5',
  261. },
  262. )
  263. url = reverse('dcim-api:site-list')
  264. self.add_permissions('dcim.add_site')
  265. response = self.client.post(url, data, format='json', **self.header)
  266. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  267. self.assertEqual(len(response.data), len(data))
  268. for i, obj in enumerate(data):
  269. # Validate response data
  270. response_cf = response.data[i]['custom_fields']
  271. self.assertEqual(response_cf['text_field'], self.cf_text.default)
  272. self.assertEqual(response_cf['number_field'], self.cf_integer.default)
  273. self.assertEqual(response_cf['boolean_field'], self.cf_boolean.default)
  274. self.assertEqual(response_cf['date_field'], self.cf_date.default)
  275. self.assertEqual(response_cf['url_field'], self.cf_url.default)
  276. self.assertEqual(response_cf['choice_field'], self.cf_select_choice1.pk)
  277. # Validate database data
  278. site = Site.objects.get(pk=response.data[i]['id'])
  279. cfvs = {
  280. cfv.field.name: cfv.value for cfv in site.custom_field_values.all()
  281. }
  282. self.assertEqual(cfvs['text_field'], self.cf_text.default)
  283. self.assertEqual(cfvs['number_field'], self.cf_integer.default)
  284. self.assertEqual(cfvs['boolean_field'], self.cf_boolean.default)
  285. self.assertEqual(str(cfvs['date_field']), self.cf_date.default)
  286. self.assertEqual(cfvs['url_field'], self.cf_url.default)
  287. self.assertEqual(cfvs['choice_field'].pk, self.cf_select_choice1.pk)
  288. def test_create_multiple_objects_with_values(self):
  289. """
  290. Create a three new sites, each with custom fields defined.
  291. """
  292. custom_field_data = {
  293. 'text_field': 'bar',
  294. 'number_field': 456,
  295. 'boolean_field': True,
  296. 'date_field': '2020-01-02',
  297. 'url_field': 'http://example.com/2',
  298. 'choice_field': self.cf_select_choice2.pk,
  299. }
  300. data = (
  301. {
  302. 'name': 'Site 3',
  303. 'slug': 'site-3',
  304. 'custom_fields': custom_field_data,
  305. },
  306. {
  307. 'name': 'Site 4',
  308. 'slug': 'site-4',
  309. 'custom_fields': custom_field_data,
  310. },
  311. {
  312. 'name': 'Site 5',
  313. 'slug': 'site-5',
  314. 'custom_fields': custom_field_data,
  315. },
  316. )
  317. url = reverse('dcim-api:site-list')
  318. self.add_permissions('dcim.add_site')
  319. response = self.client.post(url, data, format='json', **self.header)
  320. self.assertHttpStatus(response, status.HTTP_201_CREATED)
  321. self.assertEqual(len(response.data), len(data))
  322. for i, obj in enumerate(data):
  323. # Validate response data
  324. response_cf = response.data[i]['custom_fields']
  325. self.assertEqual(response_cf['text_field'], custom_field_data['text_field'])
  326. self.assertEqual(response_cf['number_field'], custom_field_data['number_field'])
  327. self.assertEqual(response_cf['boolean_field'], custom_field_data['boolean_field'])
  328. self.assertEqual(response_cf['date_field'], custom_field_data['date_field'])
  329. self.assertEqual(response_cf['url_field'], custom_field_data['url_field'])
  330. self.assertEqual(response_cf['choice_field'], custom_field_data['choice_field'])
  331. # Validate database data
  332. site = Site.objects.get(pk=response.data[i]['id'])
  333. cfvs = {
  334. cfv.field.name: cfv.value for cfv in site.custom_field_values.all()
  335. }
  336. self.assertEqual(cfvs['text_field'], custom_field_data['text_field'])
  337. self.assertEqual(cfvs['number_field'], custom_field_data['number_field'])
  338. self.assertEqual(cfvs['boolean_field'], custom_field_data['boolean_field'])
  339. self.assertEqual(str(cfvs['date_field']), custom_field_data['date_field'])
  340. self.assertEqual(cfvs['url_field'], custom_field_data['url_field'])
  341. self.assertEqual(cfvs['choice_field'].pk, custom_field_data['choice_field'])
  342. def test_update_single_object_with_values(self):
  343. """
  344. Update an object with existing custom field values. Ensure that only the updated custom field values are
  345. modified.
  346. """
  347. site2_original_cfvs = {
  348. cfv.field.name: cfv.value for cfv in self.sites[1].custom_field_values.all()
  349. }
  350. data = {
  351. 'custom_fields': {
  352. 'text_field': 'ABCD',
  353. 'number_field': 1234,
  354. },
  355. }
  356. url = reverse('dcim-api:site-detail', kwargs={'pk': self.sites[1].pk})
  357. self.add_permissions('dcim.change_site')
  358. response = self.client.patch(url, data, format='json', **self.header)
  359. self.assertHttpStatus(response, status.HTTP_200_OK)
  360. # Validate response data
  361. response_cf = response.data['custom_fields']
  362. data_cf = data['custom_fields']
  363. self.assertEqual(response_cf['text_field'], data_cf['text_field'])
  364. self.assertEqual(response_cf['number_field'], data_cf['number_field'])
  365. # TODO: Non-updated fields are missing from the response data
  366. # self.assertEqual(response_cf['boolean_field'], site2_original_cfvs['boolean_field'])
  367. # self.assertEqual(response_cf['date_field'], site2_original_cfvs['date_field'])
  368. # self.assertEqual(response_cf['url_field'], site2_original_cfvs['url_field'])
  369. # self.assertEqual(response_cf['choice_field']['label'], site2_original_cfvs['choice_field'].value)
  370. # Validate database data
  371. site2_updated_cfvs = {
  372. cfv.field.name: cfv.value for cfv in self.sites[1].custom_field_values.all()
  373. }
  374. self.assertEqual(site2_updated_cfvs['text_field'], data_cf['text_field'])
  375. self.assertEqual(site2_updated_cfvs['number_field'], data_cf['number_field'])
  376. self.assertEqual(site2_updated_cfvs['boolean_field'], site2_original_cfvs['boolean_field'])
  377. self.assertEqual(site2_updated_cfvs['date_field'], site2_original_cfvs['date_field'])
  378. self.assertEqual(site2_updated_cfvs['url_field'], site2_original_cfvs['url_field'])
  379. self.assertEqual(site2_updated_cfvs['choice_field'], site2_original_cfvs['choice_field'])
  380. class CustomFieldChoiceAPITest(APITestCase):
  381. def setUp(self):
  382. super().setUp()
  383. vm_content_type = ContentType.objects.get_for_model(VirtualMachine)
  384. self.cf_1 = CustomField.objects.create(name="cf_1", type=CustomFieldTypeChoices.TYPE_SELECT)
  385. self.cf_2 = CustomField.objects.create(name="cf_2", type=CustomFieldTypeChoices.TYPE_SELECT)
  386. self.cf_choice_1 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_1", weight=100)
  387. self.cf_choice_2 = CustomFieldChoice.objects.create(field=self.cf_1, value="cf_field_2", weight=50)
  388. self.cf_choice_3 = CustomFieldChoice.objects.create(field=self.cf_2, value="cf_field_3", weight=10)
  389. def test_list_cfc(self):
  390. url = reverse('extras-api:custom-field-choice-list')
  391. response = self.client.get(url, **self.header)
  392. self.assertEqual(len(response.data), 2)
  393. self.assertEqual(len(response.data[self.cf_1.name]), 2)
  394. self.assertEqual(len(response.data[self.cf_2.name]), 1)
  395. self.assertTrue(self.cf_choice_1.value in response.data[self.cf_1.name])
  396. self.assertTrue(self.cf_choice_2.value in response.data[self.cf_1.name])
  397. self.assertTrue(self.cf_choice_3.value in response.data[self.cf_2.name])
  398. self.assertEqual(self.cf_choice_1.pk, response.data[self.cf_1.name][self.cf_choice_1.value])
  399. self.assertEqual(self.cf_choice_2.pk, response.data[self.cf_1.name][self.cf_choice_2.value])
  400. self.assertEqual(self.cf_choice_3.pk, response.data[self.cf_2.name][self.cf_choice_3.value])
  401. class CustomFieldImportTest(TestCase):
  402. user_permissions = (
  403. 'dcim.view_site',
  404. 'dcim.add_site',
  405. )
  406. @classmethod
  407. def setUpTestData(cls):
  408. custom_fields = (
  409. CustomField(name='text', type=CustomFieldTypeChoices.TYPE_TEXT),
  410. CustomField(name='integer', type=CustomFieldTypeChoices.TYPE_INTEGER),
  411. CustomField(name='boolean', type=CustomFieldTypeChoices.TYPE_BOOLEAN),
  412. CustomField(name='date', type=CustomFieldTypeChoices.TYPE_DATE),
  413. CustomField(name='url', type=CustomFieldTypeChoices.TYPE_URL),
  414. CustomField(name='select', type=CustomFieldTypeChoices.TYPE_SELECT),
  415. )
  416. for cf in custom_fields:
  417. cf.save()
  418. cf.obj_type.set([ContentType.objects.get_for_model(Site)])
  419. CustomFieldChoice.objects.bulk_create((
  420. CustomFieldChoice(field=custom_fields[5], value='Choice A'),
  421. CustomFieldChoice(field=custom_fields[5], value='Choice B'),
  422. CustomFieldChoice(field=custom_fields[5], value='Choice C'),
  423. ))
  424. def test_import(self):
  425. """
  426. Import a Site in CSV format, including a value for each CustomField.
  427. """
  428. data = (
  429. ('name', 'slug', 'cf_text', 'cf_integer', 'cf_boolean', 'cf_date', 'cf_url', 'cf_select'),
  430. ('Site 1', 'site-1', 'ABC', '123', 'True', '2020-01-01', 'http://example.com/1', 'Choice A'),
  431. ('Site 2', 'site-2', 'DEF', '456', 'False', '2020-01-02', 'http://example.com/2', 'Choice B'),
  432. ('Site 3', 'site-3', '', '', '', '', '', ''),
  433. )
  434. csv_data = '\n'.join(','.join(row) for row in data)
  435. response = self.client.post(reverse('dcim:site_import'), {'csv': csv_data})
  436. self.assertEqual(response.status_code, 200)
  437. # Validate data for site 1
  438. custom_field_values = {
  439. cf.name: value for cf, value in Site.objects.get(name='Site 1').get_custom_fields().items()
  440. }
  441. self.assertEqual(len(custom_field_values), 6)
  442. self.assertEqual(custom_field_values['text'], 'ABC')
  443. self.assertEqual(custom_field_values['integer'], 123)
  444. self.assertEqual(custom_field_values['boolean'], True)
  445. self.assertEqual(custom_field_values['date'], date(2020, 1, 1))
  446. self.assertEqual(custom_field_values['url'], 'http://example.com/1')
  447. self.assertEqual(custom_field_values['select'].value, 'Choice A')
  448. # Validate data for site 2
  449. custom_field_values = {
  450. cf.name: value for cf, value in Site.objects.get(name='Site 2').get_custom_fields().items()
  451. }
  452. self.assertEqual(len(custom_field_values), 6)
  453. self.assertEqual(custom_field_values['text'], 'DEF')
  454. self.assertEqual(custom_field_values['integer'], 456)
  455. self.assertEqual(custom_field_values['boolean'], False)
  456. self.assertEqual(custom_field_values['date'], date(2020, 1, 2))
  457. self.assertEqual(custom_field_values['url'], 'http://example.com/2')
  458. self.assertEqual(custom_field_values['select'].value, 'Choice B')
  459. # No CustomFieldValues should be created for site 3
  460. obj_type = ContentType.objects.get_for_model(Site)
  461. site3 = Site.objects.get(name='Site 3')
  462. self.assertFalse(CustomFieldValue.objects.filter(obj_type=obj_type, obj_id=site3.pk).exists())
  463. self.assertEqual(CustomFieldValue.objects.count(), 12) # Sanity check
  464. def test_import_missing_required(self):
  465. """
  466. Attempt to import an object missing a required custom field.
  467. """
  468. # Set one of our CustomFields to required
  469. CustomField.objects.filter(name='text').update(required=True)
  470. form_data = {
  471. 'name': 'Site 1',
  472. 'slug': 'site-1',
  473. }
  474. form = SiteCSVForm(data=form_data)
  475. self.assertFalse(form.is_valid())
  476. self.assertIn('cf_text', form.errors)
  477. def test_import_invalid_choice(self):
  478. """
  479. Attempt to import an object with an invalid choice selection.
  480. """
  481. form_data = {
  482. 'name': 'Site 1',
  483. 'slug': 'site-1',
  484. 'cf_select': 'Choice X'
  485. }
  486. form = SiteCSVForm(data=form_data)
  487. self.assertFalse(form.is_valid())
  488. self.assertIn('cf_select', form.errors)