test_views.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. from django.contrib.contenttypes.models import ContentType
  2. from django.urls import reverse
  3. from django.test import tag
  4. from unittest.mock import patch, PropertyMock
  5. from core.choices import ManagedFileRootPathChoices
  6. from core.events import *
  7. from core.models import ObjectType
  8. from dcim.models import DeviceType, Manufacturer, Site
  9. from extras.choices import *
  10. from extras.models import *
  11. from extras.scripts import Script as PythonClass, IntegerVar, BooleanVar
  12. from users.models import Group, User
  13. from utilities.testing import ViewTestCases, TestCase
  14. class CustomFieldTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  15. model = CustomField
  16. @classmethod
  17. def setUpTestData(cls):
  18. site_type = ObjectType.objects.get_for_model(Site)
  19. CustomFieldChoiceSet.objects.create(
  20. name='Choice Set 1',
  21. extra_choices=(
  22. ('A', 'A'),
  23. ('B', 'B'),
  24. ('C', 'C'),
  25. )
  26. )
  27. custom_fields = (
  28. CustomField(name='field1', label='Field 1', type=CustomFieldTypeChoices.TYPE_TEXT),
  29. CustomField(name='field2', label='Field 2', type=CustomFieldTypeChoices.TYPE_TEXT),
  30. CustomField(name='field3', label='Field 3', type=CustomFieldTypeChoices.TYPE_TEXT),
  31. )
  32. for customfield in custom_fields:
  33. customfield.save()
  34. customfield.object_types.add(site_type)
  35. cls.form_data = {
  36. 'name': 'field_x',
  37. 'label': 'Field X',
  38. 'type': 'text',
  39. 'object_types': [site_type.pk],
  40. 'search_weight': 2000,
  41. 'filter_logic': CustomFieldFilterLogicChoices.FILTER_EXACT,
  42. 'default': None,
  43. 'weight': 200,
  44. 'required': True,
  45. 'ui_visible': CustomFieldUIVisibleChoices.ALWAYS,
  46. 'ui_editable': CustomFieldUIEditableChoices.YES,
  47. }
  48. cls.csv_data = (
  49. 'name,label,type,object_types,related_object_type,weight,search_weight,filter_logic,choice_set,validation_minimum,validation_maximum,validation_regex,ui_visible,ui_editable',
  50. 'field4,Field 4,text,dcim.site,,100,1000,exact,,,,[a-z]{3},always,yes',
  51. 'field5,Field 5,integer,dcim.site,,100,2000,exact,,1,100,,always,yes',
  52. 'field6,Field 6,select,dcim.site,,100,3000,exact,Choice Set 1,,,,always,yes',
  53. 'field7,Field 7,object,dcim.site,dcim.region,100,4000,exact,,,,,always,yes',
  54. )
  55. cls.csv_update_data = (
  56. 'id,label',
  57. f'{custom_fields[0].pk},New label 1',
  58. f'{custom_fields[1].pk},New label 2',
  59. f'{custom_fields[2].pk},New label 3',
  60. )
  61. cls.bulk_edit_data = {
  62. 'required': True,
  63. 'weight': 200,
  64. }
  65. class CustomFieldChoiceSetTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  66. model = CustomFieldChoiceSet
  67. @classmethod
  68. def setUpTestData(cls):
  69. choice_sets = (
  70. CustomFieldChoiceSet(
  71. name='Choice Set 1',
  72. extra_choices=(('A1', 'Choice 1'), ('A2', 'Choice 2'), ('A3', 'Choice 3'))
  73. ),
  74. CustomFieldChoiceSet(
  75. name='Choice Set 2',
  76. extra_choices=(('B1', 'Choice 1'), ('B2', 'Choice 2'), ('B3', 'Choice 3'))
  77. ),
  78. CustomFieldChoiceSet(
  79. name='Choice Set 3',
  80. extra_choices=(('C1', 'Choice 1'), ('C2', 'Choice 2'), ('C3', 'Choice 3'))
  81. ),
  82. CustomFieldChoiceSet(
  83. name='Choice Set 4',
  84. extra_choices=(('D1', 'Choice 1'), ('D2', 'Choice 2'), ('D3', 'Choice 3'))
  85. ),
  86. )
  87. CustomFieldChoiceSet.objects.bulk_create(choice_sets)
  88. cls.form_data = {
  89. 'name': 'Choice Set X',
  90. 'extra_choices': '\n'.join(['X1:Choice 1', 'X2:Choice 2', 'X3:Choice 3'])
  91. }
  92. cls.csv_data = (
  93. 'name,extra_choices',
  94. 'Choice Set 5,"D1,D2,D3"',
  95. 'Choice Set 6,"E1,E2,E3"',
  96. 'Choice Set 7,"F1,F2,F3"',
  97. 'Choice Set 8,"F1:L1,F2:L2,F3:L3"',
  98. )
  99. cls.csv_update_data = (
  100. 'id,extra_choices',
  101. f'{choice_sets[0].pk},"A,B,C"',
  102. f'{choice_sets[1].pk},"A,B,C"',
  103. f'{choice_sets[2].pk},"A,B,C"',
  104. f'{choice_sets[3].pk},"A:L1,B:L2,C:L3"',
  105. )
  106. cls.bulk_edit_data = {
  107. 'description': 'New description',
  108. }
  109. # This is here as extra_choices field splits on colon, but is returned
  110. # from DB as comma separated.
  111. def assertInstanceEqual(self, instance, data, exclude=None, api=False):
  112. if 'extra_choices' in data:
  113. data['extra_choices'] = data['extra_choices'].replace(':', ',')
  114. return super().assertInstanceEqual(instance, data, exclude, api)
  115. class CustomLinkTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  116. model = CustomLink
  117. @classmethod
  118. def setUpTestData(cls):
  119. site_type = ObjectType.objects.get_for_model(Site)
  120. custom_links = (
  121. CustomLink(name='Custom Link 1', enabled=True, link_text='Link 1', link_url='http://example.com/?1'),
  122. CustomLink(name='Custom Link 2', enabled=True, link_text='Link 2', link_url='http://example.com/?2'),
  123. CustomLink(name='Custom Link 3', enabled=False, link_text='Link 3', link_url='http://example.com/?3'),
  124. )
  125. CustomLink.objects.bulk_create(custom_links)
  126. for i, custom_link in enumerate(custom_links):
  127. custom_link.object_types.set([site_type])
  128. cls.form_data = {
  129. 'name': 'Custom Link X',
  130. 'object_types': [site_type.pk],
  131. 'enabled': False,
  132. 'weight': 100,
  133. 'button_class': CustomLinkButtonClassChoices.DEFAULT,
  134. 'link_text': 'Link X',
  135. 'link_url': 'http://example.com/?x'
  136. }
  137. cls.csv_data = (
  138. "name,object_types,enabled,weight,button_class,link_text,link_url",
  139. "Custom Link 4,dcim.site,True,100,blue,Link 4,http://exmaple.com/?4",
  140. "Custom Link 5,dcim.site,True,100,blue,Link 5,http://exmaple.com/?5",
  141. "Custom Link 6,dcim.site,False,100,blue,Link 6,http://exmaple.com/?6",
  142. )
  143. cls.csv_update_data = (
  144. "id,name",
  145. f"{custom_links[0].pk},Custom Link 7",
  146. f"{custom_links[1].pk},Custom Link 8",
  147. f"{custom_links[2].pk},Custom Link 9",
  148. )
  149. cls.bulk_edit_data = {
  150. 'button_class': CustomLinkButtonClassChoices.CYAN,
  151. 'enabled': False,
  152. 'weight': 200,
  153. }
  154. class SavedFilterTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  155. model = SavedFilter
  156. @classmethod
  157. def setUpTestData(cls):
  158. site_type = ObjectType.objects.get_for_model(Site)
  159. users = (
  160. User(username='User 1'),
  161. User(username='User 2'),
  162. User(username='User 3'),
  163. )
  164. User.objects.bulk_create(users)
  165. saved_filters = (
  166. SavedFilter(
  167. name='Saved Filter 1',
  168. slug='saved-filter-1',
  169. user=users[0],
  170. weight=100,
  171. parameters={'status': ['active']}
  172. ),
  173. SavedFilter(
  174. name='Saved Filter 2',
  175. slug='saved-filter-2',
  176. user=users[1],
  177. weight=200,
  178. parameters={'status': ['planned']}
  179. ),
  180. SavedFilter(
  181. name='Saved Filter 3',
  182. slug='saved-filter-3',
  183. user=users[2],
  184. weight=300,
  185. parameters={'status': ['retired']}
  186. ),
  187. )
  188. SavedFilter.objects.bulk_create(saved_filters)
  189. for i, savedfilter in enumerate(saved_filters):
  190. savedfilter.object_types.set([site_type])
  191. cls.form_data = {
  192. 'name': 'Saved Filter X',
  193. 'slug': 'saved-filter-x',
  194. 'object_types': [site_type.pk],
  195. 'description': 'Foo',
  196. 'weight': 1000,
  197. 'enabled': True,
  198. 'shared': True,
  199. 'parameters': '{"foo": 123}',
  200. }
  201. cls.csv_data = (
  202. 'name,slug,object_types,weight,enabled,shared,parameters',
  203. 'Saved Filter 4,saved-filter-4,dcim.device,400,True,True,{"foo": "a"}',
  204. 'Saved Filter 5,saved-filter-5,dcim.device,500,True,True,{"foo": "b"}',
  205. 'Saved Filter 6,saved-filter-6,dcim.device,600,True,True,{"foo": "c"}',
  206. )
  207. cls.csv_update_data = (
  208. "id,name",
  209. f"{saved_filters[0].pk},Saved Filter 7",
  210. f"{saved_filters[1].pk},Saved Filter 8",
  211. f"{saved_filters[2].pk},Saved Filter 9",
  212. )
  213. cls.bulk_edit_data = {
  214. 'weight': 999,
  215. }
  216. class BookmarkTestCase(
  217. ViewTestCases.DeleteObjectViewTestCase,
  218. ViewTestCases.ListObjectsViewTestCase,
  219. ViewTestCases.BulkDeleteObjectsViewTestCase
  220. ):
  221. model = Bookmark
  222. @classmethod
  223. def setUpTestData(cls):
  224. site_ct = ContentType.objects.get_for_model(Site)
  225. sites = (
  226. Site(name='Site 1', slug='site-1'),
  227. Site(name='Site 2', slug='site-2'),
  228. Site(name='Site 3', slug='site-3'),
  229. Site(name='Site 4', slug='site-4'),
  230. )
  231. Site.objects.bulk_create(sites)
  232. cls.form_data = {
  233. 'object_type': site_ct.pk,
  234. 'object_id': sites[3].pk,
  235. }
  236. def setUp(self):
  237. super().setUp()
  238. sites = Site.objects.all()
  239. user = self.user
  240. bookmarks = (
  241. Bookmark(object=sites[0], user=user),
  242. Bookmark(object=sites[1], user=user),
  243. Bookmark(object=sites[2], user=user),
  244. )
  245. Bookmark.objects.bulk_create(bookmarks)
  246. def _get_url(self, action, instance=None):
  247. if action == 'list':
  248. return reverse('account:bookmarks')
  249. return super()._get_url(action, instance)
  250. def test_list_objects_anonymous(self):
  251. return
  252. def test_list_objects_with_constrained_permission(self):
  253. return
  254. class ExportTemplateTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  255. model = ExportTemplate
  256. @classmethod
  257. def setUpTestData(cls):
  258. site_type = ObjectType.objects.get_for_model(Site)
  259. TEMPLATE_CODE = """{% for object in queryset %}{{ object }}{% endfor %}"""
  260. ENVIRONMENT_PARAMS = """{"trim_blocks": true}"""
  261. export_templates = (
  262. ExportTemplate(name='Export Template 1', template_code=TEMPLATE_CODE),
  263. ExportTemplate(
  264. name='Export Template 2', template_code=TEMPLATE_CODE, environment_params={"trim_blocks": True}
  265. ),
  266. ExportTemplate(name='Export Template 3', template_code=TEMPLATE_CODE, file_name='export_template_3')
  267. )
  268. ExportTemplate.objects.bulk_create(export_templates)
  269. for et in export_templates:
  270. et.object_types.set([site_type])
  271. cls.form_data = {
  272. 'name': 'Export Template X',
  273. 'object_types': [site_type.pk],
  274. 'template_code': TEMPLATE_CODE,
  275. 'environment_params': ENVIRONMENT_PARAMS,
  276. 'file_name': 'template_x',
  277. }
  278. cls.csv_data = (
  279. "name,object_types,template_code,file_name",
  280. f"Export Template 4,dcim.site,{TEMPLATE_CODE},",
  281. f"Export Template 5,dcim.site,{TEMPLATE_CODE},template_5",
  282. f"Export Template 6,dcim.site,{TEMPLATE_CODE},",
  283. )
  284. cls.csv_update_data = (
  285. "id,name",
  286. f"{export_templates[0].pk},Export Template 7",
  287. f"{export_templates[1].pk},Export Template 8",
  288. f"{export_templates[2].pk},Export Template 9",
  289. )
  290. cls.bulk_edit_data = {
  291. 'mime_type': 'text/html',
  292. 'file_extension': 'html',
  293. 'as_attachment': True,
  294. }
  295. class WebhookTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  296. model = Webhook
  297. @classmethod
  298. def setUpTestData(cls):
  299. webhooks = (
  300. Webhook(name='Webhook 1', payload_url='http://example.com/?1', http_method='POST'),
  301. Webhook(name='Webhook 2', payload_url='http://example.com/?2', http_method='POST'),
  302. Webhook(name='Webhook 3', payload_url='http://example.com/?3', http_method='POST'),
  303. )
  304. for webhook in webhooks:
  305. webhook.save()
  306. cls.form_data = {
  307. 'name': 'Webhook X',
  308. 'payload_url': 'http://example.com/?x',
  309. 'http_method': 'GET',
  310. 'http_content_type': 'application/foo',
  311. 'description': 'My webhook',
  312. }
  313. cls.csv_data = (
  314. "name,payload_url,http_method,http_content_type,description",
  315. "Webhook 4,http://example.com/?4,GET,application/json,Foo",
  316. "Webhook 5,http://example.com/?5,GET,application/json,Bar",
  317. "Webhook 6,http://example.com/?6,GET,application/json,Baz",
  318. )
  319. cls.csv_update_data = (
  320. "id,name,description",
  321. f"{webhooks[0].pk},Webhook 7,Foo",
  322. f"{webhooks[1].pk},Webhook 8,Bar",
  323. f"{webhooks[2].pk},Webhook 9,Baz",
  324. )
  325. cls.bulk_edit_data = {
  326. 'http_method': 'GET',
  327. }
  328. class EventRulesTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  329. model = EventRule
  330. @classmethod
  331. def setUpTestData(cls):
  332. webhooks = (
  333. Webhook(name='Webhook 1', payload_url='http://example.com/?1', http_method='POST'),
  334. Webhook(name='Webhook 2', payload_url='http://example.com/?2', http_method='POST'),
  335. Webhook(name='Webhook 3', payload_url='http://example.com/?3', http_method='POST'),
  336. )
  337. for webhook in webhooks:
  338. webhook.save()
  339. site_type = ObjectType.objects.get_for_model(Site)
  340. event_rules = (
  341. EventRule(name='EventRule 1', event_types=[OBJECT_CREATED], action_object=webhooks[0]),
  342. EventRule(name='EventRule 2', event_types=[OBJECT_CREATED], action_object=webhooks[1]),
  343. EventRule(name='EventRule 3', event_types=[OBJECT_CREATED], action_object=webhooks[2]),
  344. )
  345. for event in event_rules:
  346. event.save()
  347. event.object_types.add(site_type)
  348. webhook_ct = ContentType.objects.get_for_model(Webhook)
  349. cls.form_data = {
  350. 'name': 'Event X',
  351. 'object_types': [site_type.pk],
  352. 'event_types': [OBJECT_UPDATED, OBJECT_DELETED],
  353. 'conditions': None,
  354. 'action_type': 'webhook',
  355. 'action_object_type': webhook_ct.pk,
  356. 'action_object_id': webhooks[0].pk,
  357. 'action_choice': webhooks[0],
  358. 'description': 'New description',
  359. }
  360. cls.csv_data = (
  361. 'name,object_types,event_types,action_type,action_object',
  362. f'Webhook 4,dcim.site,"{OBJECT_CREATED},{OBJECT_UPDATED}",webhook,Webhook 1',
  363. )
  364. cls.csv_update_data = (
  365. "id,name",
  366. f"{event_rules[0].pk},Event 7",
  367. f"{event_rules[1].pk},Event 8",
  368. f"{event_rules[2].pk},Event 9",
  369. )
  370. cls.bulk_edit_data = {
  371. 'description': 'New description',
  372. }
  373. class TagTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  374. model = Tag
  375. @classmethod
  376. def setUpTestData(cls):
  377. site_ct = ContentType.objects.get_for_model(Site)
  378. tags = (
  379. Tag(name='Tag 1', slug='tag-1'),
  380. Tag(name='Tag 2', slug='tag-2', weight=1),
  381. Tag(name='Tag 3', slug='tag-3', weight=32767),
  382. )
  383. Tag.objects.bulk_create(tags)
  384. cls.form_data = {
  385. 'name': 'Tag X',
  386. 'slug': 'tag-x',
  387. 'color': 'c0c0c0',
  388. 'comments': 'Some comments',
  389. 'object_types': [site_ct.pk],
  390. 'weight': 11,
  391. }
  392. cls.csv_data = (
  393. "name,slug,color,description,object_types,weight",
  394. "Tag 4,tag-4,ff0000,Fourth tag,dcim.interface,0",
  395. "Tag 5,tag-5,00ff00,Fifth tag,'dcim.device,dcim.site',1111",
  396. "Tag 6,tag-6,0000ff,Sixth tag,dcim.site,0",
  397. )
  398. cls.csv_update_data = (
  399. "id,name,description",
  400. f"{tags[0].pk},Tag 7,Fourth tag7",
  401. f"{tags[1].pk},Tag 8,Fifth tag8",
  402. f"{tags[2].pk},Tag 9,Sixth tag9",
  403. )
  404. cls.bulk_edit_data = {
  405. 'color': '00ff00',
  406. }
  407. class ConfigContextProfileTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  408. model = ConfigContextProfile
  409. @classmethod
  410. def setUpTestData(cls):
  411. profiles = (
  412. ConfigContextProfile(
  413. name='Config Context Profile 1',
  414. schema={
  415. "properties": {
  416. "foo": {
  417. "type": "string"
  418. }
  419. },
  420. "required": [
  421. "foo"
  422. ]
  423. }
  424. ),
  425. ConfigContextProfile(
  426. name='Config Context Profile 2',
  427. schema={
  428. "properties": {
  429. "bar": {
  430. "type": "string"
  431. }
  432. },
  433. "required": [
  434. "bar"
  435. ]
  436. }
  437. ),
  438. ConfigContextProfile(
  439. name='Config Context Profile 3',
  440. schema={
  441. "properties": {
  442. "baz": {
  443. "type": "string"
  444. }
  445. },
  446. "required": [
  447. "baz"
  448. ]
  449. }
  450. ),
  451. )
  452. ConfigContextProfile.objects.bulk_create(profiles)
  453. cls.form_data = {
  454. 'name': 'Config Context Profile X',
  455. 'description': 'A new config context profile',
  456. }
  457. cls.bulk_edit_data = {
  458. 'description': 'New description',
  459. }
  460. cls.csv_data = (
  461. 'name,description',
  462. 'Config context profile 1,Foo',
  463. 'Config context profile 2,Bar',
  464. 'Config context profile 3,Baz',
  465. )
  466. cls.csv_update_data = (
  467. "id,description",
  468. f"{profiles[0].pk},New description",
  469. f"{profiles[1].pk},New description",
  470. f"{profiles[2].pk},New description",
  471. )
  472. # TODO: Change base class to PrimaryObjectViewTestCase
  473. # Blocked by absence of standard create/edit, bulk create views
  474. class ConfigContextTestCase(
  475. ViewTestCases.GetObjectViewTestCase,
  476. ViewTestCases.GetObjectChangelogViewTestCase,
  477. ViewTestCases.DeleteObjectViewTestCase,
  478. ViewTestCases.ListObjectsViewTestCase,
  479. ViewTestCases.BulkEditObjectsViewTestCase,
  480. ViewTestCases.BulkDeleteObjectsViewTestCase
  481. ):
  482. model = ConfigContext
  483. @classmethod
  484. def setUpTestData(cls):
  485. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  486. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  487. # Create three ConfigContexts
  488. for i in range(1, 4):
  489. configcontext = ConfigContext(
  490. name='Config Context {}'.format(i),
  491. data={'foo': i}
  492. )
  493. configcontext.save()
  494. configcontext.device_types.add(devicetype)
  495. cls.form_data = {
  496. 'name': 'Config Context X',
  497. 'weight': 200,
  498. 'description': 'A new config context',
  499. 'is_active': True,
  500. 'regions': [],
  501. 'sites': [],
  502. 'roles': [],
  503. 'platforms': [],
  504. 'tenant_groups': [],
  505. 'tenants': [],
  506. 'device_types': [devicetype.id],
  507. 'tags': [],
  508. 'data': '{"foo": 123}',
  509. }
  510. cls.bulk_edit_data = {
  511. 'weight': 300,
  512. 'is_active': False,
  513. 'description': 'New description',
  514. }
  515. class ConfigTemplateTestCase(
  516. ViewTestCases.GetObjectViewTestCase,
  517. ViewTestCases.GetObjectChangelogViewTestCase,
  518. ViewTestCases.DeleteObjectViewTestCase,
  519. ViewTestCases.ListObjectsViewTestCase,
  520. ViewTestCases.BulkEditObjectsViewTestCase,
  521. ViewTestCases.BulkDeleteObjectsViewTestCase
  522. ):
  523. model = ConfigTemplate
  524. @classmethod
  525. def setUpTestData(cls):
  526. TEMPLATE_CODE = """Foo: {{ foo }}"""
  527. ENVIRONMENT_PARAMS = """{"trim_blocks": true}"""
  528. config_templates = (
  529. ConfigTemplate(
  530. name='Config Template 1',
  531. template_code=TEMPLATE_CODE)
  532. ,
  533. ConfigTemplate(
  534. name='Config Template 2',
  535. template_code=TEMPLATE_CODE,
  536. environment_params={"trim_blocks": True},
  537. ),
  538. ConfigTemplate(
  539. name='Config Template 3',
  540. template_code=TEMPLATE_CODE,
  541. file_name='config_template_3',
  542. ),
  543. )
  544. ConfigTemplate.objects.bulk_create(config_templates)
  545. cls.form_data = {
  546. 'name': 'Config Template X',
  547. 'description': 'Config template',
  548. 'template_code': TEMPLATE_CODE,
  549. 'environment_params': ENVIRONMENT_PARAMS,
  550. 'file_name': 'config_x',
  551. }
  552. cls.csv_update_data = (
  553. "id,name",
  554. f"{config_templates[0].pk},Config Template 7",
  555. f"{config_templates[1].pk},Config Template 8",
  556. f"{config_templates[2].pk},Config Template 9",
  557. )
  558. cls.bulk_edit_data = {
  559. 'description': 'New description',
  560. 'mime_type': 'text/html',
  561. 'file_name': 'output',
  562. 'file_extension': 'html',
  563. 'as_attachment': True,
  564. }
  565. class JournalEntryTestCase(
  566. # ViewTestCases.GetObjectViewTestCase,
  567. ViewTestCases.CreateObjectViewTestCase,
  568. ViewTestCases.EditObjectViewTestCase,
  569. ViewTestCases.DeleteObjectViewTestCase,
  570. ViewTestCases.ListObjectsViewTestCase,
  571. ViewTestCases.BulkEditObjectsViewTestCase,
  572. ViewTestCases.BulkDeleteObjectsViewTestCase
  573. ):
  574. model = JournalEntry
  575. @classmethod
  576. def setUpTestData(cls):
  577. site_ct = ContentType.objects.get_for_model(Site)
  578. site = Site.objects.create(name='Site 1', slug='site-1')
  579. user = User.objects.create(username='User 1')
  580. JournalEntry.objects.bulk_create((
  581. JournalEntry(assigned_object=site, created_by=user, comments='First entry'),
  582. JournalEntry(assigned_object=site, created_by=user, comments='Second entry'),
  583. JournalEntry(assigned_object=site, created_by=user, comments='Third entry'),
  584. ))
  585. cls.form_data = {
  586. 'assigned_object_type': site_ct.pk,
  587. 'assigned_object_id': site.pk,
  588. 'kind': 'info',
  589. 'comments': 'A new entry',
  590. }
  591. cls.bulk_edit_data = {
  592. 'kind': 'success',
  593. 'comments': 'Overwritten',
  594. }
  595. class CustomLinkTest(TestCase):
  596. user_permissions = ['dcim.view_site']
  597. def test_view_object_with_custom_link(self):
  598. customlink = CustomLink(
  599. name='Test',
  600. link_text='FOO {{ object.name }} BAR',
  601. link_url='http://example.com/?site={{ object.slug }}',
  602. new_window=False
  603. )
  604. customlink.save()
  605. customlink.object_types.set([ObjectType.objects.get_for_model(Site)])
  606. site = Site(name='Test Site', slug='test-site')
  607. site.save()
  608. response = self.client.get(site.get_absolute_url(), follow=True)
  609. self.assertEqual(response.status_code, 200)
  610. self.assertIn(f'FOO {site.name} BAR', str(response.content))
  611. class SubscriptionTestCase(
  612. ViewTestCases.CreateObjectViewTestCase,
  613. ViewTestCases.DeleteObjectViewTestCase,
  614. ViewTestCases.ListObjectsViewTestCase,
  615. ViewTestCases.BulkDeleteObjectsViewTestCase
  616. ):
  617. model = Subscription
  618. @classmethod
  619. def setUpTestData(cls):
  620. site_ct = ContentType.objects.get_for_model(Site)
  621. sites = (
  622. Site(name='Site 1', slug='site-1'),
  623. Site(name='Site 2', slug='site-2'),
  624. Site(name='Site 3', slug='site-3'),
  625. Site(name='Site 4', slug='site-4'),
  626. )
  627. Site.objects.bulk_create(sites)
  628. cls.form_data = {
  629. 'object_type': site_ct.pk,
  630. 'object_id': sites[3].pk,
  631. }
  632. def setUp(self):
  633. super().setUp()
  634. sites = Site.objects.all()
  635. user = self.user
  636. subscriptions = (
  637. Subscription(object=sites[0], user=user),
  638. Subscription(object=sites[1], user=user),
  639. Subscription(object=sites[2], user=user),
  640. )
  641. Subscription.objects.bulk_create(subscriptions)
  642. def _get_url(self, action, instance=None):
  643. if action == 'list':
  644. return reverse('account:subscriptions')
  645. return super()._get_url(action, instance)
  646. def test_list_objects_anonymous(self):
  647. self.client.logout()
  648. url = reverse('account:subscriptions')
  649. login_url = reverse('login')
  650. self.assertRedirects(self.client.get(url), f'{login_url}?next={url}')
  651. def test_list_objects_with_permission(self):
  652. return
  653. def test_list_objects_with_constrained_permission(self):
  654. return
  655. class NotificationGroupTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  656. model = NotificationGroup
  657. @classmethod
  658. def setUpTestData(cls):
  659. users = (
  660. User(username='User 1'),
  661. User(username='User 2'),
  662. User(username='User 3'),
  663. )
  664. User.objects.bulk_create(users)
  665. groups = (
  666. Group(name='Group 1'),
  667. Group(name='Group 2'),
  668. Group(name='Group 3'),
  669. )
  670. Group.objects.bulk_create(groups)
  671. notification_groups = (
  672. NotificationGroup(name='Notification Group 1'),
  673. NotificationGroup(name='Notification Group 2'),
  674. NotificationGroup(name='Notification Group 3'),
  675. )
  676. NotificationGroup.objects.bulk_create(notification_groups)
  677. for i, notification_group in enumerate(notification_groups):
  678. notification_group.users.add(users[i])
  679. notification_group.groups.add(groups[i])
  680. cls.form_data = {
  681. 'name': 'Notification Group X',
  682. 'description': 'Blah',
  683. 'users': [users[0].pk, users[1].pk],
  684. 'groups': [groups[0].pk, groups[1].pk],
  685. }
  686. cls.csv_data = (
  687. 'name,description,users,groups',
  688. 'Notification Group 4,Foo,"User 1,User 2","Group 1,Group 2"',
  689. 'Notification Group 5,Bar,"User 1,User 2","Group 1,Group 2"',
  690. 'Notification Group 6,Baz,"User 1,User 2","Group 1,Group 2"',
  691. )
  692. cls.csv_update_data = (
  693. "id,name",
  694. f"{notification_groups[0].pk},Notification Group 7",
  695. f"{notification_groups[1].pk},Notification Group 8",
  696. f"{notification_groups[2].pk},Notification Group 9",
  697. )
  698. cls.bulk_edit_data = {
  699. 'description': 'New description',
  700. }
  701. class NotificationTestCase(
  702. ViewTestCases.DeleteObjectViewTestCase,
  703. ViewTestCases.ListObjectsViewTestCase,
  704. ViewTestCases.BulkDeleteObjectsViewTestCase
  705. ):
  706. model = Notification
  707. @classmethod
  708. def setUpTestData(cls):
  709. site_ct = ContentType.objects.get_for_model(Site)
  710. sites = (
  711. Site(name='Site 1', slug='site-1'),
  712. Site(name='Site 2', slug='site-2'),
  713. Site(name='Site 3', slug='site-3'),
  714. Site(name='Site 4', slug='site-4'),
  715. )
  716. Site.objects.bulk_create(sites)
  717. cls.form_data = {
  718. 'object_type': site_ct.pk,
  719. 'object_id': sites[3].pk,
  720. }
  721. def setUp(self):
  722. super().setUp()
  723. sites = Site.objects.all()
  724. user = self.user
  725. notifications = (
  726. Notification(object=sites[0], user=user),
  727. Notification(object=sites[1], user=user),
  728. Notification(object=sites[2], user=user),
  729. )
  730. Notification.objects.bulk_create(notifications)
  731. def _get_url(self, action, instance=None):
  732. if action == 'list':
  733. return reverse('account:notifications')
  734. return super()._get_url(action, instance)
  735. def test_list_objects_anonymous(self):
  736. self.client.logout()
  737. url = reverse('account:notifications')
  738. login_url = reverse('login')
  739. self.assertRedirects(self.client.get(url), f'{login_url}?next={url}')
  740. def test_list_objects_with_permission(self):
  741. return
  742. def test_list_objects_with_constrained_permission(self):
  743. return
  744. class ScriptListViewTest(TestCase):
  745. user_permissions = ['extras.view_script']
  746. def test_script_list_embedded_parameter(self):
  747. """Test that ScriptListView accepts embedded parameter without error"""
  748. url = reverse('extras:script_list')
  749. # Test normal request
  750. response = self.client.get(url)
  751. self.assertEqual(response.status_code, 200)
  752. self.assertTemplateUsed(response, 'extras/script_list.html')
  753. # Test embedded request
  754. response = self.client.get(url, {'embedded': 'true'})
  755. self.assertEqual(response.status_code, 200)
  756. self.assertTemplateUsed(response, 'extras/inc/script_list_content.html')
  757. class ScriptValidationErrorTest(TestCase):
  758. user_permissions = ['extras.view_script', 'extras.run_script']
  759. class TestScriptMixin:
  760. bar = IntegerVar(min_value=0, max_value=30)
  761. class TestScriptClass(TestScriptMixin, PythonClass):
  762. class Meta:
  763. name = 'Test script'
  764. commit_default = False
  765. fieldsets = (("Logging", ("debug_mode",)),)
  766. debug_mode = BooleanVar(default=False)
  767. def run(self, data, commit):
  768. return "Complete"
  769. @classmethod
  770. def setUpTestData(cls):
  771. module = ScriptModule.objects.create(file_root=ManagedFileRootPathChoices.SCRIPTS, file_path='test_script.py')
  772. cls.script = Script.objects.create(module=module, name='Test script', is_executable=True)
  773. def setUp(self):
  774. super().setUp()
  775. Script.python_class = property(lambda self: ScriptValidationErrorTest.TestScriptClass)
  776. @tag('regression')
  777. def test_script_validation_error_displays_message(self):
  778. url = reverse('extras:script', kwargs={'pk': self.script.pk})
  779. with patch('extras.views.get_workers_for_queue', return_value=['worker']):
  780. response = self.client.post(url, {'debug_mode': 'true', '_commit': 'true'})
  781. self.assertEqual(response.status_code, 200)
  782. messages = list(response.context['messages'])
  783. self.assertEqual(len(messages), 1)
  784. self.assertEqual(str(messages[0]), "bar: This field is required.")
  785. @tag('regression')
  786. def test_script_validation_error_no_toast_for_fieldset_fields(self):
  787. class FieldsetScript(PythonClass):
  788. class Meta:
  789. name = 'Fieldset test'
  790. commit_default = False
  791. fieldsets = (("Fields", ("required_field",)),)
  792. required_field = IntegerVar(min_value=10)
  793. def run(self, data, commit):
  794. return "Complete"
  795. url = reverse('extras:script', kwargs={'pk': self.script.pk})
  796. with patch.object(Script, 'python_class', new_callable=PropertyMock) as mock_python_class:
  797. mock_python_class.return_value = FieldsetScript
  798. with patch('extras.views.get_workers_for_queue', return_value=['worker']):
  799. response = self.client.post(url, {'required_field': '5', '_commit': 'true'})
  800. self.assertEqual(response.status_code, 200)
  801. messages = list(response.context['messages'])
  802. self.assertEqual(len(messages), 0)
  803. class ScriptDefaultValuesTest(TestCase):
  804. user_permissions = ['extras.view_script', 'extras.run_script']
  805. class TestScriptClass(PythonClass):
  806. class Meta:
  807. name = 'Test script'
  808. commit_default = False
  809. bool_default_true = BooleanVar(default=True)
  810. bool_default_false = BooleanVar(default=False)
  811. int_with_default = IntegerVar(default=0)
  812. int_without_default = IntegerVar(required=False)
  813. def run(self, data, commit):
  814. return "Complete"
  815. @classmethod
  816. def setUpTestData(cls):
  817. module = ScriptModule.objects.create(file_root=ManagedFileRootPathChoices.SCRIPTS, file_path='test_script.py')
  818. cls.script = Script.objects.create(module=module, name='Test script', is_executable=True)
  819. def setUp(self):
  820. super().setUp()
  821. Script.python_class = property(lambda self: ScriptDefaultValuesTest.TestScriptClass)
  822. def test_default_values_are_used(self):
  823. url = reverse('extras:script', kwargs={'pk': self.script.pk})
  824. with patch('extras.views.get_workers_for_queue', return_value=['worker']):
  825. with patch('extras.jobs.ScriptJob.enqueue') as mock_enqueue:
  826. mock_enqueue.return_value.pk = 1
  827. self.client.post(url, {})
  828. call_kwargs = mock_enqueue.call_args.kwargs
  829. self.assertEqual(call_kwargs['data']['bool_default_true'], True)
  830. self.assertEqual(call_kwargs['data']['bool_default_false'], False)
  831. self.assertEqual(call_kwargs['data']['int_with_default'], 0)
  832. self.assertIsNone(call_kwargs['data']['int_without_default'])