test_views.py 144 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928
  1. import json
  2. from decimal import Decimal
  3. from zoneinfo import ZoneInfo
  4. import yaml
  5. from django.test import override_settings, tag
  6. from django.urls import reverse
  7. from netaddr import EUI
  8. from core.models import ObjectType
  9. from dcim.choices import *
  10. from dcim.constants import *
  11. from dcim.models import *
  12. from ipam.models import ASN, RIR, VLAN, VRF
  13. from netbox.choices import CSVDelimiterChoices, ImportFormatChoices, WeightUnitChoices
  14. from tenancy.models import Tenant
  15. from users.models import ObjectPermission, User
  16. from utilities.testing import ViewTestCases, create_tags, create_test_device, post_data
  17. from wireless.models import WirelessLAN
  18. class RegionTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  19. model = Region
  20. @classmethod
  21. def setUpTestData(cls):
  22. # Create three Regions
  23. regions = (
  24. Region(name='Region 1', slug='region-1', comments=''),
  25. Region(
  26. name='Region 2', slug='region-2', comments="It's going to take a lot to drag me away from you"
  27. ),
  28. Region(name='Region 3', slug='region-3'),
  29. )
  30. for region in regions:
  31. region.save()
  32. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  33. cls.form_data = {
  34. 'name': 'Region X',
  35. 'slug': 'region-x',
  36. 'parent': regions[2].pk,
  37. 'description': 'A new region',
  38. 'tags': [t.pk for t in tags],
  39. 'comments': 'This comment is really exciting!',
  40. }
  41. cls.csv_data = (
  42. "name,slug,description,comments",
  43. "Region 4,region-4,Fourth region,",
  44. "Region 5,region-5,Fifth region,hi guys",
  45. "Region 6,region-6,Sixth region,bye guys",
  46. )
  47. cls.csv_update_data = (
  48. "id,name,description",
  49. f"{regions[0].pk},Region 7,Fourth region7",
  50. f"{regions[1].pk},Region 8,Fifth region8",
  51. f"{regions[2].pk},Region 0,Sixth region9",
  52. )
  53. cls.bulk_edit_data = {
  54. 'description': 'New description',
  55. 'comments': 'This comment is super exciting!!!',
  56. }
  57. class SiteGroupTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  58. model = SiteGroup
  59. @classmethod
  60. def setUpTestData(cls):
  61. # Create three SiteGroups
  62. sitegroups = (
  63. SiteGroup(name='Site Group 1', slug='site-group-1', comments='Still here'),
  64. SiteGroup(name='Site Group 2', slug='site-group-2'),
  65. SiteGroup(name='Site Group 3', slug='site-group-3'),
  66. )
  67. for sitegroup in sitegroups:
  68. sitegroup.save()
  69. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  70. cls.form_data = {
  71. 'name': 'Site Group X',
  72. 'slug': 'site-group-x',
  73. 'parent': sitegroups[2].pk,
  74. 'description': 'A new site group',
  75. 'tags': [t.pk for t in tags],
  76. 'comments': 'still here',
  77. }
  78. cls.csv_data = (
  79. "name,slug,description,comments",
  80. "Site Group 4,site-group-4,Fourth site group,",
  81. "Site Group 5,site-group-5,Fifth site group,still hear",
  82. "Site Group 6,site-group-6,Sixth site group,"
  83. )
  84. cls.csv_update_data = (
  85. "id,name,description,comments",
  86. f"{sitegroups[0].pk},Site Group 7,Fourth site group7,",
  87. f"{sitegroups[1].pk},Site Group 8,Fifth site group8,when will it end",
  88. f"{sitegroups[2].pk},Site Group 0,Sixth site group9,",
  89. )
  90. cls.bulk_edit_data = {
  91. 'description': 'New description',
  92. 'comments': 'the end',
  93. }
  94. class SiteTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  95. model = Site
  96. @classmethod
  97. def setUpTestData(cls):
  98. regions = (
  99. Region(name='Region 1', slug='region-1'),
  100. Region(name='Region 2', slug='region-2'),
  101. )
  102. for region in regions:
  103. region.save()
  104. groups = (
  105. SiteGroup(name='Site Group 1', slug='site-group-1'),
  106. SiteGroup(name='Site Group 2', slug='site-group-2'),
  107. )
  108. for group in groups:
  109. group.save()
  110. rir = RIR.objects.create(name='RFC 6996', is_private=True)
  111. asns = [
  112. ASN(asn=65000 + i, rir=rir) for i in range(8)
  113. ]
  114. ASN.objects.bulk_create(asns)
  115. sites = Site.objects.bulk_create([
  116. Site(name='Site 1', slug='site-1', region=regions[0], group=groups[1]),
  117. Site(name='Site 2', slug='site-2', region=regions[0], group=groups[1]),
  118. Site(name='Site 3', slug='site-3', region=regions[0], group=groups[1]),
  119. ])
  120. sites[0].asns.set([asns[0], asns[1]])
  121. sites[1].asns.set([asns[2], asns[3]])
  122. sites[2].asns.set([asns[4], asns[5]])
  123. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  124. cls.form_data = {
  125. 'name': 'Site X',
  126. 'slug': 'site-x',
  127. 'status': SiteStatusChoices.STATUS_PLANNED,
  128. 'region': regions[1].pk,
  129. 'group': groups[1].pk,
  130. 'tenant': None,
  131. 'facility': 'Facility X',
  132. 'asns': [asns[6].pk, asns[7].pk],
  133. 'time_zone': ZoneInfo('UTC'),
  134. 'description': 'Site description',
  135. 'physical_address': '742 Evergreen Terrace, Springfield, USA',
  136. 'shipping_address': '742 Evergreen Terrace, Springfield, USA',
  137. 'latitude': Decimal('35.780000'),
  138. 'longitude': Decimal('-78.642000'),
  139. 'comments': 'Test site',
  140. 'tags': [t.pk for t in tags],
  141. }
  142. cls.csv_data = (
  143. "name,slug,status",
  144. "Site 4,site-4,planned",
  145. "Site 5,site-5,active",
  146. "Site 6,site-6,staging",
  147. )
  148. cls.csv_update_data = (
  149. "id,name,status",
  150. f"{sites[0].pk},Site 7,staging",
  151. f"{sites[1].pk},Site 8,planned",
  152. f"{sites[2].pk},Site 9,active",
  153. )
  154. cls.bulk_edit_data = {
  155. 'status': SiteStatusChoices.STATUS_PLANNED,
  156. 'region': regions[1].pk,
  157. 'group': groups[1].pk,
  158. 'tenant': None,
  159. 'time_zone': ZoneInfo('US/Eastern'),
  160. 'description': 'New description',
  161. }
  162. class LocationTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  163. model = Location
  164. @classmethod
  165. def setUpTestData(cls):
  166. site = Site.objects.create(name='Site 1', slug='site-1')
  167. tenant = Tenant.objects.create(name='Tenant 1', slug='tenant-1')
  168. locations = (
  169. Location(
  170. name='Location 1',
  171. slug='location-1',
  172. site=site,
  173. status=LocationStatusChoices.STATUS_ACTIVE,
  174. tenant=tenant,
  175. comments='',
  176. ),
  177. Location(
  178. name='Location 2',
  179. slug='location-2',
  180. site=site,
  181. status=LocationStatusChoices.STATUS_ACTIVE,
  182. tenant=tenant,
  183. comments='First comment!',
  184. ),
  185. Location(
  186. name='Location 3',
  187. slug='location-3',
  188. site=site,
  189. status=LocationStatusChoices.STATUS_ACTIVE,
  190. tenant=tenant,
  191. comments='_This_ is a **bold comment**',
  192. ),
  193. )
  194. for location in locations:
  195. location.save()
  196. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  197. cls.form_data = {
  198. 'name': 'Location X',
  199. 'slug': 'location-x',
  200. 'site': site.pk,
  201. 'status': LocationStatusChoices.STATUS_PLANNED,
  202. 'facility': 'Facility X',
  203. 'tenant': tenant.pk,
  204. 'description': 'A new location',
  205. 'tags': [t.pk for t in tags],
  206. 'comments': 'This comment is really boring',
  207. }
  208. cls.csv_data = (
  209. "site,tenant,name,slug,status,description,comments",
  210. "Site 1,Tenant 1,Location 4,location-4,planned,Fourth location,",
  211. "Site 1,Tenant 1,Location 5,location-5,planned,Fifth location,",
  212. "Site 1,Tenant 1,Location 6,location-6,planned,Sixth location,hi!",
  213. )
  214. cls.csv_update_data = (
  215. "id,name,description,comments",
  216. f"{locations[0].pk},Location 7,Fourth location7,Useful comment",
  217. f"{locations[1].pk},Location 8,Fifth location8,unuseful comment",
  218. f"{locations[2].pk},Location 0,Sixth location9,",
  219. )
  220. cls.bulk_edit_data = {
  221. 'description': 'New description',
  222. 'comments': 'This comment is also really boring',
  223. }
  224. class RackRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  225. model = RackRole
  226. @classmethod
  227. def setUpTestData(cls):
  228. rack_roles = (
  229. RackRole(name='Rack Role 1', slug='rack-role-1'),
  230. RackRole(name='Rack Role 2', slug='rack-role-2'),
  231. RackRole(name='Rack Role 3', slug='rack-role-3'),
  232. )
  233. RackRole.objects.bulk_create(rack_roles)
  234. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  235. cls.form_data = {
  236. 'name': 'Rack Role X',
  237. 'slug': 'rack-role-x',
  238. 'color': 'c0c0c0',
  239. 'description': 'New role',
  240. 'tags': [t.pk for t in tags],
  241. }
  242. cls.csv_data = (
  243. "name,slug,color",
  244. "Rack Role 4,rack-role-4,ff0000",
  245. "Rack Role 5,rack-role-5,00ff00",
  246. "Rack Role 6,rack-role-6,0000ff",
  247. )
  248. cls.csv_update_data = (
  249. "id,name,description",
  250. f"{rack_roles[0].pk},Rack Role 7,New description7",
  251. f"{rack_roles[1].pk},Rack Role 8,New description8",
  252. f"{rack_roles[2].pk},Rack Role 9,New description9",
  253. )
  254. cls.bulk_edit_data = {
  255. 'color': '00ff00',
  256. 'description': 'New description',
  257. }
  258. class RackReservationTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  259. model = RackReservation
  260. @classmethod
  261. def setUpTestData(cls):
  262. user2 = User.objects.create_user(username='testuser2')
  263. user3 = User.objects.create_user(username='testuser3')
  264. site = Site.objects.create(name='Site 1', slug='site-1')
  265. location = Location(name='Location 1', slug='location-1', site=site)
  266. location.save()
  267. rack = Rack(name='Rack 1', site=site, location=location)
  268. rack.save()
  269. rack_reservations = (
  270. RackReservation(rack=rack, user=user2, units=[1, 2, 3], description='Reservation 1'),
  271. RackReservation(rack=rack, user=user2, units=[4, 5, 6], description='Reservation 2'),
  272. RackReservation(rack=rack, user=user2, units=[7, 8, 9], description='Reservation 3'),
  273. )
  274. RackReservation.objects.bulk_create(rack_reservations)
  275. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  276. cls.form_data = {
  277. 'rack': rack.pk,
  278. 'units': "10,11,12",
  279. 'status': RackReservationStatusChoices.STATUS_PENDING,
  280. 'user': user3.pk,
  281. 'tenant': None,
  282. 'description': 'Rack reservation',
  283. 'tags': [t.pk for t in tags],
  284. }
  285. cls.csv_data = (
  286. 'site,location,rack,units,status,description',
  287. 'Site 1,Location 1,Rack 1,"10,11,12",active,Reservation 1',
  288. 'Site 1,Location 1,Rack 1,"13,14,15",pending,Reservation 2',
  289. 'Site 1,Location 1,Rack 1,"16,17,18",stale,Reservation 3',
  290. )
  291. cls.csv_update_data = (
  292. 'id,description',
  293. f'{rack_reservations[0].pk},New description1',
  294. f'{rack_reservations[1].pk},New description2',
  295. f'{rack_reservations[2].pk},New description3',
  296. )
  297. cls.bulk_edit_data = {
  298. 'status': RackReservationStatusChoices.STATUS_STALE,
  299. 'user': user3.pk,
  300. 'tenant': None,
  301. 'description': 'New description',
  302. }
  303. class RackTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  304. model = RackType
  305. @classmethod
  306. def setUpTestData(cls):
  307. manufacturers = (
  308. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  309. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  310. )
  311. Manufacturer.objects.bulk_create(manufacturers)
  312. rack_types = (
  313. RackType(
  314. manufacturer=manufacturers[0],
  315. model='RackType 1',
  316. slug='rack-type-1',
  317. form_factor=RackFormFactorChoices.TYPE_CABINET,
  318. ),
  319. RackType(
  320. manufacturer=manufacturers[0],
  321. model='RackType 2',
  322. slug='rack-type-2',
  323. form_factor=RackFormFactorChoices.TYPE_CABINET,
  324. ),
  325. RackType(
  326. manufacturer=manufacturers[0],
  327. model='RackType 3',
  328. slug='rack-type-3',
  329. form_factor=RackFormFactorChoices.TYPE_CABINET,
  330. ),
  331. )
  332. RackType.objects.bulk_create(rack_types)
  333. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  334. cls.form_data = {
  335. 'manufacturer': manufacturers[1].pk,
  336. 'model': 'RackType X',
  337. 'slug': 'rack-type-x',
  338. 'type': RackFormFactorChoices.TYPE_CABINET,
  339. 'width': RackWidthChoices.WIDTH_19IN,
  340. 'u_height': 48,
  341. 'desc_units': False,
  342. 'outer_width': 500,
  343. 'outer_depth': 500,
  344. 'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER,
  345. 'starting_unit': 1,
  346. 'weight': 100,
  347. 'max_weight': 2000,
  348. 'weight_unit': WeightUnitChoices.UNIT_POUND,
  349. 'form_factor': RackFormFactorChoices.TYPE_CABINET,
  350. 'comments': 'Some comments',
  351. 'tags': [t.pk for t in tags],
  352. }
  353. cls.csv_data = (
  354. "manufacturer,model,slug,width,u_height,weight,max_weight,weight_unit",
  355. "Manufacturer 1,RackType 4,rack-type-4,19,42,100,2000,kg",
  356. "Manufacturer 1,RackType 5,rack-type-5,19,42,100,2000,kg",
  357. "Manufacturer 1,RackType 6,rack-type-6,19,42,100,2000,kg",
  358. )
  359. cls.csv_update_data = (
  360. "id,model",
  361. f"{rack_types[0].pk},RackType 7",
  362. f"{rack_types[1].pk},RackType 8",
  363. f"{rack_types[2].pk},RackType 9",
  364. )
  365. cls.bulk_edit_data = {
  366. 'manufacturer': manufacturers[1].pk,
  367. 'type': RackFormFactorChoices.TYPE_4POST,
  368. 'width': RackWidthChoices.WIDTH_23IN,
  369. 'u_height': 49,
  370. 'desc_units': True,
  371. 'outer_width': 30,
  372. 'outer_depth': 30,
  373. 'outer_unit': RackDimensionUnitChoices.UNIT_INCH,
  374. 'weight': 200,
  375. 'max_weight': 4000,
  376. 'weight_unit': WeightUnitChoices.UNIT_POUND,
  377. 'comments': 'New comments',
  378. }
  379. class RackTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  380. model = Rack
  381. @classmethod
  382. def setUpTestData(cls):
  383. sites = (
  384. Site(name='Site 1', slug='site-1'),
  385. Site(name='Site 2', slug='site-2'),
  386. )
  387. Site.objects.bulk_create(sites)
  388. locations = (
  389. Location(name='Location 1', slug='location-1', site=sites[0]),
  390. Location(name='Location 2', slug='location-2', site=sites[1])
  391. )
  392. for location in locations:
  393. location.save()
  394. rackroles = (
  395. RackRole(name='Rack Role 1', slug='rack-role-1'),
  396. RackRole(name='Rack Role 2', slug='rack-role-2'),
  397. )
  398. RackRole.objects.bulk_create(rackroles)
  399. racks = (
  400. Rack(name='Rack 1', site=sites[0]),
  401. Rack(name='Rack 2', site=sites[0]),
  402. Rack(name='Rack 3', site=sites[0]),
  403. )
  404. Rack.objects.bulk_create(racks)
  405. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  406. cls.form_data = {
  407. 'name': 'Rack X',
  408. 'facility_id': 'Facility X',
  409. 'site': sites[1].pk,
  410. 'location': locations[1].pk,
  411. 'tenant': None,
  412. 'status': RackStatusChoices.STATUS_PLANNED,
  413. 'role': rackroles[1].pk,
  414. 'serial': '123456',
  415. 'asset_tag': 'ABCDEF',
  416. 'form_factor': RackFormFactorChoices.TYPE_CABINET,
  417. 'width': RackWidthChoices.WIDTH_19IN,
  418. 'u_height': 48,
  419. 'desc_units': False,
  420. 'outer_width': 500,
  421. 'outer_depth': 500,
  422. 'outer_unit': RackDimensionUnitChoices.UNIT_MILLIMETER,
  423. 'starting_unit': 1,
  424. 'weight': 100,
  425. 'max_weight': 2000,
  426. 'weight_unit': WeightUnitChoices.UNIT_POUND,
  427. 'comments': 'Some comments',
  428. 'tags': [t.pk for t in tags],
  429. }
  430. cls.csv_data = (
  431. "site,location,name,status,width,u_height,weight,max_weight,weight_unit",
  432. "Site 1,,Rack 4,active,19,42,100,2000,kg",
  433. "Site 1,Location 1,Rack 5,active,19,42,100,2000,kg",
  434. "Site 2,Location 2,Rack 6,active,19,42,100,2000,kg",
  435. )
  436. cls.csv_update_data = (
  437. "id,name,status",
  438. f"{racks[0].pk},Rack 7,{RackStatusChoices.STATUS_DEPRECATED}",
  439. f"{racks[1].pk},Rack 8,{RackStatusChoices.STATUS_DEPRECATED}",
  440. f"{racks[2].pk},Rack 9,{RackStatusChoices.STATUS_DEPRECATED}",
  441. )
  442. cls.bulk_edit_data = {
  443. 'site': sites[1].pk,
  444. 'location': locations[1].pk,
  445. 'tenant': None,
  446. 'status': RackStatusChoices.STATUS_DEPRECATED,
  447. 'role': rackroles[1].pk,
  448. 'serial': '654321',
  449. 'form_factor': RackFormFactorChoices.TYPE_4POST,
  450. 'width': RackWidthChoices.WIDTH_23IN,
  451. 'u_height': 49,
  452. 'desc_units': True,
  453. 'outer_width': 30,
  454. 'outer_depth': 30,
  455. 'outer_unit': RackDimensionUnitChoices.UNIT_INCH,
  456. 'weight': 200,
  457. 'max_weight': 4000,
  458. 'weight_unit': WeightUnitChoices.UNIT_POUND,
  459. 'comments': 'New comments',
  460. }
  461. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  462. def test_list_rack_elevations(self):
  463. """
  464. Test viewing the list of rack elevations.
  465. """
  466. response = self.client.get(reverse('dcim:rack_elevation_list'))
  467. self.assertHttpStatus(response, 200)
  468. class ManufacturerTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  469. model = Manufacturer
  470. @classmethod
  471. def setUpTestData(cls):
  472. manufacturers = (
  473. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  474. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  475. Manufacturer(name='Manufacturer 3', slug='manufacturer-3'),
  476. )
  477. Manufacturer.objects.bulk_create(manufacturers)
  478. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  479. cls.form_data = {
  480. 'name': 'Manufacturer X',
  481. 'slug': 'manufacturer-x',
  482. 'description': 'A new manufacturer',
  483. 'tags': [t.pk for t in tags],
  484. }
  485. cls.csv_data = (
  486. "name,slug,description",
  487. "Manufacturer 4,manufacturer-4,Fourth manufacturer",
  488. "Manufacturer 5,manufacturer-5,Fifth manufacturer",
  489. "Manufacturer 6,manufacturer-6,Sixth manufacturer",
  490. )
  491. cls.csv_update_data = (
  492. "id,name,description",
  493. f"{manufacturers[0].pk},Manufacturer 7,Fourth manufacturer7",
  494. f"{manufacturers[1].pk},Manufacturer 8,Fifth manufacturer8",
  495. f"{manufacturers[2].pk},Manufacturer 9,Sixth manufacturer9",
  496. )
  497. cls.bulk_edit_data = {
  498. 'description': 'New description',
  499. }
  500. # TODO: Change base class to PrimaryObjectViewTestCase
  501. # Blocked by absence of bulk import view for DeviceTypes
  502. class DeviceTypeTestCase(
  503. ViewTestCases.GetObjectViewTestCase,
  504. ViewTestCases.GetObjectChangelogViewTestCase,
  505. ViewTestCases.CreateObjectViewTestCase,
  506. ViewTestCases.EditObjectViewTestCase,
  507. ViewTestCases.DeleteObjectViewTestCase,
  508. ViewTestCases.ListObjectsViewTestCase,
  509. ViewTestCases.BulkEditObjectsViewTestCase,
  510. ViewTestCases.BulkDeleteObjectsViewTestCase
  511. ):
  512. model = DeviceType
  513. @classmethod
  514. def setUpTestData(cls):
  515. manufacturers = (
  516. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  517. Manufacturer(name='Manufacturer 2', slug='manufacturer-2')
  518. )
  519. Manufacturer.objects.bulk_create(manufacturers)
  520. platforms = (
  521. Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturers[0]),
  522. Platform(name='Platform 2', slug='platform-3', manufacturer=manufacturers[1]),
  523. )
  524. for platform in platforms:
  525. platform.save()
  526. DeviceType.objects.bulk_create([
  527. DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturers[0]),
  528. DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturers[0]),
  529. DeviceType(model='Device Type 3', slug='device-type-3', manufacturer=manufacturers[0]),
  530. ])
  531. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  532. cls.form_data = {
  533. 'manufacturer': manufacturers[1].pk,
  534. 'default_platform': platforms[0].pk,
  535. 'model': 'Device Type X',
  536. 'slug': 'device-type-x',
  537. 'part_number': '123ABC',
  538. 'u_height': 2,
  539. 'is_full_depth': True,
  540. 'subdevice_role': None,
  541. 'comments': 'Some comments',
  542. 'tags': [t.pk for t in tags],
  543. }
  544. cls.bulk_edit_data = {
  545. 'manufacturer': manufacturers[1].pk,
  546. 'default_platform': platforms[1].pk,
  547. 'u_height': 3,
  548. 'is_full_depth': False,
  549. }
  550. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  551. def test_devicetype_consoleports(self):
  552. devicetype = DeviceType.objects.first()
  553. console_ports = (
  554. ConsolePortTemplate(device_type=devicetype, name='Console Port 1'),
  555. ConsolePortTemplate(device_type=devicetype, name='Console Port 2'),
  556. ConsolePortTemplate(device_type=devicetype, name='Console Port 3'),
  557. )
  558. ConsolePortTemplate.objects.bulk_create(console_ports)
  559. url = reverse('dcim:devicetype_consoleports', kwargs={'pk': devicetype.pk})
  560. self.assertHttpStatus(self.client.get(url), 200)
  561. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  562. def test_devicetype_consoleserverports(self):
  563. devicetype = DeviceType.objects.first()
  564. console_server_ports = (
  565. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port 1'),
  566. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port 2'),
  567. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port 3'),
  568. )
  569. ConsoleServerPortTemplate.objects.bulk_create(console_server_ports)
  570. url = reverse('dcim:devicetype_consoleserverports', kwargs={'pk': devicetype.pk})
  571. self.assertHttpStatus(self.client.get(url), 200)
  572. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  573. def test_devicetype_powerports(self):
  574. devicetype = DeviceType.objects.first()
  575. power_ports = (
  576. PowerPortTemplate(device_type=devicetype, name='Power Port 1'),
  577. PowerPortTemplate(device_type=devicetype, name='Power Port 2'),
  578. PowerPortTemplate(device_type=devicetype, name='Power Port 3'),
  579. )
  580. PowerPortTemplate.objects.bulk_create(power_ports)
  581. url = reverse('dcim:devicetype_powerports', kwargs={'pk': devicetype.pk})
  582. self.assertHttpStatus(self.client.get(url), 200)
  583. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  584. def test_devicetype_poweroutlets(self):
  585. devicetype = DeviceType.objects.first()
  586. power_outlets = (
  587. PowerOutletTemplate(device_type=devicetype, name='Power Outlet 1'),
  588. PowerOutletTemplate(device_type=devicetype, name='Power Outlet 2'),
  589. PowerOutletTemplate(device_type=devicetype, name='Power Outlet 3'),
  590. )
  591. PowerOutletTemplate.objects.bulk_create(power_outlets)
  592. url = reverse('dcim:devicetype_poweroutlets', kwargs={'pk': devicetype.pk})
  593. self.assertHttpStatus(self.client.get(url), 200)
  594. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  595. def test_devicetype_interfaces(self):
  596. devicetype = DeviceType.objects.first()
  597. interfaces = (
  598. InterfaceTemplate(device_type=devicetype, name='Interface 1'),
  599. InterfaceTemplate(device_type=devicetype, name='Interface 2'),
  600. InterfaceTemplate(device_type=devicetype, name='Interface 3'),
  601. )
  602. InterfaceTemplate.objects.bulk_create(interfaces)
  603. url = reverse('dcim:devicetype_interfaces', kwargs={'pk': devicetype.pk})
  604. self.assertHttpStatus(self.client.get(url), 200)
  605. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  606. def test_devicetype_rearports(self):
  607. devicetype = DeviceType.objects.first()
  608. rear_ports = (
  609. RearPortTemplate(device_type=devicetype, name='Rear Port 1'),
  610. RearPortTemplate(device_type=devicetype, name='Rear Port 2'),
  611. RearPortTemplate(device_type=devicetype, name='Rear Port 3'),
  612. )
  613. RearPortTemplate.objects.bulk_create(rear_ports)
  614. url = reverse('dcim:devicetype_rearports', kwargs={'pk': devicetype.pk})
  615. self.assertHttpStatus(self.client.get(url), 200)
  616. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  617. def test_devicetype_frontports(self):
  618. devicetype = DeviceType.objects.first()
  619. rear_ports = (
  620. RearPortTemplate(device_type=devicetype, name='Rear Port 1'),
  621. RearPortTemplate(device_type=devicetype, name='Rear Port 2'),
  622. RearPortTemplate(device_type=devicetype, name='Rear Port 3'),
  623. )
  624. RearPortTemplate.objects.bulk_create(rear_ports)
  625. front_ports = (
  626. FrontPortTemplate(
  627. device_type=devicetype, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1
  628. ),
  629. FrontPortTemplate(
  630. device_type=devicetype, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1
  631. ),
  632. FrontPortTemplate(
  633. device_type=devicetype, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1
  634. ),
  635. )
  636. FrontPortTemplate.objects.bulk_create(front_ports)
  637. url = reverse('dcim:devicetype_frontports', kwargs={'pk': devicetype.pk})
  638. self.assertHttpStatus(self.client.get(url), 200)
  639. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  640. def test_devicetype_modulebays(self):
  641. devicetype = DeviceType.objects.first()
  642. module_bays = (
  643. ModuleBayTemplate(device_type=devicetype, name='Module Bay 1'),
  644. ModuleBayTemplate(device_type=devicetype, name='Module Bay 2'),
  645. ModuleBayTemplate(device_type=devicetype, name='Module Bay 3'),
  646. )
  647. ModuleBayTemplate.objects.bulk_create(module_bays)
  648. url = reverse('dcim:devicetype_modulebays', kwargs={'pk': devicetype.pk})
  649. self.assertHttpStatus(self.client.get(url), 200)
  650. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  651. def test_devicetype_devicebays(self):
  652. devicetype = DeviceType.objects.first()
  653. device_bays = (
  654. DeviceBayTemplate(device_type=devicetype, name='Device Bay 1'),
  655. DeviceBayTemplate(device_type=devicetype, name='Device Bay 2'),
  656. DeviceBayTemplate(device_type=devicetype, name='Device Bay 3'),
  657. )
  658. DeviceBayTemplate.objects.bulk_create(device_bays)
  659. url = reverse('dcim:devicetype_devicebays', kwargs={'pk': devicetype.pk})
  660. self.assertHttpStatus(self.client.get(url), 200)
  661. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  662. def test_devicetype_inventoryitems(self):
  663. devicetype = DeviceType.objects.first()
  664. inventory_items = (
  665. DeviceBayTemplate(device_type=devicetype, name='Device Bay 1'),
  666. DeviceBayTemplate(device_type=devicetype, name='Device Bay 2'),
  667. DeviceBayTemplate(device_type=devicetype, name='Device Bay 3'),
  668. )
  669. for inventory_item in inventory_items:
  670. inventory_item.save()
  671. url = reverse('dcim:devicetype_inventoryitems', kwargs={'pk': devicetype.pk})
  672. self.assertHttpStatus(self.client.get(url), 200)
  673. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  674. def test_import_objects(self):
  675. """
  676. Custom import test for YAML-based imports (versus CSV)
  677. """
  678. IMPORT_DATA = """
  679. manufacturer: Generic
  680. model: TEST-1000
  681. slug: test-1000
  682. default_platform: Platform
  683. u_height: 2
  684. is_full_depth: false
  685. airflow: front-to-rear
  686. subdevice_role: parent
  687. weight: 10
  688. weight_unit: kg
  689. comments: Test comment
  690. console-ports:
  691. - name: Console Port 1
  692. type: de-9
  693. - name: Console Port 2
  694. type: de-9
  695. - name: Console Port 3
  696. type: de-9
  697. console-server-ports:
  698. - name: Console Server Port 1
  699. type: rj-45
  700. - name: Console Server Port 2
  701. type: rj-45
  702. - name: Console Server Port 3
  703. type: rj-45
  704. power-ports:
  705. - name: Power Port 1
  706. type: iec-60320-c14
  707. - name: Power Port 2
  708. type: iec-60320-c14
  709. - name: Power Port 3
  710. type: iec-60320-c14
  711. power-outlets:
  712. - name: Power Outlet 1
  713. type: iec-60320-c13
  714. power_port: Power Port 1
  715. feed_leg: A
  716. - name: Power Outlet 2
  717. type: iec-60320-c13
  718. power_port: Power Port 1
  719. feed_leg: A
  720. - name: Power Outlet 3
  721. type: iec-60320-c13
  722. power_port: Power Port 1
  723. feed_leg: A
  724. interfaces:
  725. - name: Interface 1
  726. type: 1000base-t
  727. mgmt_only: true
  728. - name: Interface 2
  729. type: 1000base-t
  730. enabled: false
  731. - name: Interface 3
  732. type: 1000base-t
  733. rear-ports:
  734. - name: Rear Port 1
  735. type: 8p8c
  736. - name: Rear Port 2
  737. type: 8p8c
  738. - name: Rear Port 3
  739. type: 8p8c
  740. front-ports:
  741. - name: Front Port 1
  742. type: 8p8c
  743. rear_port: Rear Port 1
  744. - name: Front Port 2
  745. type: 8p8c
  746. rear_port: Rear Port 2
  747. - name: Front Port 3
  748. type: 8p8c
  749. rear_port: Rear Port 3
  750. module-bays:
  751. - name: Module Bay 1
  752. - name: Module Bay 2
  753. - name: Module Bay 3
  754. device-bays:
  755. - name: Device Bay 1
  756. - name: Device Bay 2
  757. - name: Device Bay 3
  758. inventory-items:
  759. - name: Inventory Item 1
  760. manufacturer: Generic
  761. - name: Inventory Item 2
  762. manufacturer: Generic
  763. - name: Inventory Item 3
  764. manufacturer: Generic
  765. """
  766. # Create the manufacturer and platform
  767. manufacturer = Manufacturer(name='Generic', slug='generic')
  768. manufacturer.save()
  769. platform = Platform(name='Platform', slug='test-platform', manufacturer=manufacturer)
  770. platform.save()
  771. # Add all required permissions to the test user
  772. self.add_permissions(
  773. 'dcim.view_devicetype',
  774. 'dcim.add_devicetype',
  775. 'dcim.add_consoleporttemplate',
  776. 'dcim.add_consoleserverporttemplate',
  777. 'dcim.add_powerporttemplate',
  778. 'dcim.add_poweroutlettemplate',
  779. 'dcim.add_interfacetemplate',
  780. 'dcim.add_frontporttemplate',
  781. 'dcim.add_rearporttemplate',
  782. 'dcim.add_modulebaytemplate',
  783. 'dcim.add_devicebaytemplate',
  784. 'dcim.add_inventoryitemtemplate',
  785. )
  786. form_data = {
  787. 'data': IMPORT_DATA,
  788. 'format': 'yaml'
  789. }
  790. response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
  791. self.assertHttpStatus(response, 200)
  792. device_type = DeviceType.objects.get(model='TEST-1000')
  793. self.assertEqual(device_type.manufacturer.pk, manufacturer.pk)
  794. self.assertEqual(device_type.default_platform.pk, platform.pk)
  795. self.assertEqual(device_type.slug, 'test-1000')
  796. self.assertEqual(device_type.u_height, 2)
  797. self.assertFalse(device_type.is_full_depth)
  798. self.assertEqual(device_type.airflow, DeviceAirflowChoices.AIRFLOW_FRONT_TO_REAR)
  799. self.assertEqual(device_type.subdevice_role, SubdeviceRoleChoices.ROLE_PARENT)
  800. self.assertEqual(device_type.weight, 10)
  801. self.assertEqual(device_type.weight_unit, WeightUnitChoices.UNIT_KILOGRAM)
  802. self.assertEqual(device_type.comments, 'Test comment')
  803. # Verify all of the components were created
  804. self.assertEqual(device_type.consoleporttemplates.count(), 3)
  805. cp1 = ConsolePortTemplate.objects.first()
  806. self.assertEqual(cp1.name, 'Console Port 1')
  807. self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
  808. self.assertEqual(device_type.consoleserverporttemplates.count(), 3)
  809. csp1 = ConsoleServerPortTemplate.objects.first()
  810. self.assertEqual(csp1.name, 'Console Server Port 1')
  811. self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
  812. self.assertEqual(device_type.powerporttemplates.count(), 3)
  813. pp1 = PowerPortTemplate.objects.first()
  814. self.assertEqual(pp1.name, 'Power Port 1')
  815. self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
  816. self.assertEqual(device_type.poweroutlettemplates.count(), 3)
  817. po1 = PowerOutletTemplate.objects.first()
  818. self.assertEqual(po1.name, 'Power Outlet 1')
  819. self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
  820. self.assertEqual(po1.power_port, pp1)
  821. self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
  822. self.assertEqual(device_type.interfacetemplates.count(), 3)
  823. iface1 = InterfaceTemplate.objects.first()
  824. self.assertEqual(iface1.name, 'Interface 1')
  825. self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
  826. self.assertTrue(iface1.mgmt_only)
  827. self.assertTrue(iface1.enabled)
  828. iface2 = InterfaceTemplate.objects.filter(name="Interface 2").first()
  829. self.assertFalse(iface2.enabled)
  830. self.assertEqual(device_type.rearporttemplates.count(), 3)
  831. rp1 = RearPortTemplate.objects.first()
  832. self.assertEqual(rp1.name, 'Rear Port 1')
  833. self.assertEqual(device_type.frontporttemplates.count(), 3)
  834. fp1 = FrontPortTemplate.objects.first()
  835. self.assertEqual(fp1.name, 'Front Port 1')
  836. self.assertEqual(fp1.rear_port, rp1)
  837. self.assertEqual(fp1.rear_port_position, 1)
  838. self.assertEqual(device_type.modulebaytemplates.count(), 3)
  839. mb1 = ModuleBayTemplate.objects.first()
  840. self.assertEqual(mb1.name, 'Module Bay 1')
  841. self.assertEqual(device_type.devicebaytemplates.count(), 3)
  842. db1 = DeviceBayTemplate.objects.first()
  843. self.assertEqual(db1.name, 'Device Bay 1')
  844. self.assertEqual(device_type.inventoryitemtemplates.count(), 3)
  845. ii1 = InventoryItemTemplate.objects.first()
  846. self.assertEqual(ii1.name, 'Inventory Item 1')
  847. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  848. def test_import_error_numbering(self):
  849. # Add all required permissions to the test user
  850. self.add_permissions(
  851. 'dcim.view_devicetype',
  852. 'dcim.add_devicetype',
  853. 'dcim.add_consoleporttemplate',
  854. 'dcim.add_consoleserverporttemplate',
  855. 'dcim.add_powerporttemplate',
  856. 'dcim.add_poweroutlettemplate',
  857. 'dcim.add_interfacetemplate',
  858. 'dcim.add_frontporttemplate',
  859. 'dcim.add_rearporttemplate',
  860. 'dcim.add_modulebaytemplate',
  861. 'dcim.add_devicebaytemplate',
  862. 'dcim.add_inventoryitemtemplate',
  863. )
  864. import_data = '''
  865. ---
  866. manufacturer: Manufacturer 1
  867. model: TEST-2001
  868. slug: test-2001
  869. u_height: 1
  870. module-bays:
  871. - name: Module Bay 1-1
  872. - name: Module Bay 1-2
  873. ---
  874. - manufacturer: Manufacturer 1
  875. model: TEST-2002
  876. slug: test-2002
  877. u_height: 1
  878. module-bays:
  879. - name: Module Bay 2-1
  880. - name: Module Bay 2-2
  881. - not_name: Module Bay 2-3
  882. - manufacturer: Manufacturer 1
  883. model: TEST-2003
  884. slug: test-2003
  885. u_height: 1
  886. module-bays:
  887. - name: Module Bay 3-1
  888. '''
  889. form_data = {
  890. 'data': import_data,
  891. 'format': 'yaml'
  892. }
  893. response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
  894. self.assertHttpStatus(response, 200)
  895. self.assertContains(response, "Record 2 module-bays[3].name: This field is required.")
  896. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  897. def test_import_nolist(self):
  898. # Add all required permissions to the test user
  899. self.add_permissions(
  900. 'dcim.view_devicetype',
  901. 'dcim.add_devicetype',
  902. 'dcim.add_consoleporttemplate',
  903. 'dcim.add_consoleserverporttemplate',
  904. 'dcim.add_powerporttemplate',
  905. 'dcim.add_poweroutlettemplate',
  906. 'dcim.add_interfacetemplate',
  907. 'dcim.add_frontporttemplate',
  908. 'dcim.add_rearporttemplate',
  909. 'dcim.add_modulebaytemplate',
  910. 'dcim.add_devicebaytemplate',
  911. 'dcim.add_inventoryitemtemplate',
  912. )
  913. for value in ('', 'null', '3', '"My console port"', '{name: "My other console port"}'):
  914. with self.subTest(value=value):
  915. import_data = f'''
  916. manufacturer: Manufacturer 1
  917. model: TEST-3000
  918. slug: test-3000
  919. u_height: 1
  920. console-ports: {value}
  921. '''
  922. form_data = {
  923. 'data': import_data,
  924. 'format': 'yaml'
  925. }
  926. response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
  927. self.assertHttpStatus(response, 200)
  928. self.assertContains(response, "Record 1 console-ports: Must be a list.")
  929. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  930. def test_import_nodict(self):
  931. # Add all required permissions to the test user
  932. self.add_permissions(
  933. 'dcim.view_devicetype',
  934. 'dcim.add_devicetype',
  935. 'dcim.add_consoleporttemplate',
  936. 'dcim.add_consoleserverporttemplate',
  937. 'dcim.add_powerporttemplate',
  938. 'dcim.add_poweroutlettemplate',
  939. 'dcim.add_interfacetemplate',
  940. 'dcim.add_frontporttemplate',
  941. 'dcim.add_rearporttemplate',
  942. 'dcim.add_modulebaytemplate',
  943. 'dcim.add_devicebaytemplate',
  944. 'dcim.add_inventoryitemtemplate',
  945. )
  946. for value in ('', 'null', '3', '"My console port"', '["My other console port"]'):
  947. with self.subTest(value=value):
  948. import_data = f'''
  949. manufacturer: Manufacturer 1
  950. model: TEST-4000
  951. slug: test-4000
  952. u_height: 1
  953. console-ports:
  954. - {value}
  955. '''
  956. form_data = {
  957. 'data': import_data,
  958. 'format': 'yaml'
  959. }
  960. response = self.client.post(reverse('dcim:devicetype_bulk_import'), data=form_data, follow=True)
  961. self.assertHttpStatus(response, 200)
  962. self.assertContains(response, "Record 1 console-ports[1]: Must be a dictionary.")
  963. def test_export_objects(self):
  964. url = reverse('dcim:devicetype_list')
  965. self.add_permissions('dcim.view_devicetype')
  966. # Test default YAML export
  967. response = self.client.get(f'{url}?export')
  968. self.assertEqual(response.status_code, 200)
  969. data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
  970. self.assertEqual(len(data), 3)
  971. self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1')
  972. self.assertEqual(data[0]['model'], 'Device Type 1')
  973. # Test table-based export
  974. response = self.client.get(f'{url}?export=table')
  975. self.assertHttpStatus(response, 200)
  976. self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
  977. class ModuleTypeTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  978. model = ModuleType
  979. @classmethod
  980. def setUpTestData(cls):
  981. manufacturers = (
  982. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  983. Manufacturer(name='Manufacturer 2', slug='manufacturer-2')
  984. )
  985. Manufacturer.objects.bulk_create(manufacturers)
  986. module_types = ModuleType.objects.bulk_create([
  987. ModuleType(model='Module Type 1', manufacturer=manufacturers[0]),
  988. ModuleType(model='Module Type 2', manufacturer=manufacturers[0]),
  989. ModuleType(model='Module Type 3', manufacturer=manufacturers[0]),
  990. ])
  991. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  992. fan_module_type_profile = ModuleTypeProfile.objects.get(name='Fan')
  993. cls.form_data = {
  994. 'manufacturer': manufacturers[1].pk,
  995. 'model': 'Device Type X',
  996. 'part_number': '123ABC',
  997. 'comments': 'Some comments',
  998. 'tags': [t.pk for t in tags],
  999. }
  1000. cls.bulk_edit_data = {
  1001. 'manufacturer': manufacturers[1].pk,
  1002. 'part_number': '456DEF',
  1003. }
  1004. cls.csv_data = (
  1005. "manufacturer,model,part_number,comments,profile",
  1006. f"Manufacturer 1,fan0,generic-fan,,{fan_module_type_profile.name}"
  1007. )
  1008. cls.csv_update_data = (
  1009. "id,model",
  1010. f"{module_types[0].id},test model",
  1011. )
  1012. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1013. def test_bulk_update_objects_with_permission(self):
  1014. self.add_permissions(
  1015. 'dcim.add_consoleporttemplate',
  1016. 'dcim.add_consoleserverporttemplate',
  1017. 'dcim.add_powerporttemplate',
  1018. 'dcim.add_poweroutlettemplate',
  1019. 'dcim.add_interfacetemplate',
  1020. 'dcim.add_frontporttemplate',
  1021. 'dcim.add_rearporttemplate',
  1022. 'dcim.add_modulebaytemplate',
  1023. )
  1024. # run base test
  1025. super().test_bulk_update_objects_with_permission()
  1026. @tag('regression')
  1027. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
  1028. def test_bulk_import_objects_with_permission(self):
  1029. self.add_permissions(
  1030. 'dcim.add_consoleporttemplate',
  1031. 'dcim.add_consoleserverporttemplate',
  1032. 'dcim.add_powerporttemplate',
  1033. 'dcim.add_poweroutlettemplate',
  1034. 'dcim.add_interfacetemplate',
  1035. 'dcim.add_frontporttemplate',
  1036. 'dcim.add_rearporttemplate',
  1037. 'dcim.add_modulebaytemplate',
  1038. )
  1039. def verify_module_type_profile(scenario_name):
  1040. # TODO: remove extra regression asserts once parent test supports testing all import fields
  1041. fan_module_type = ModuleType.objects.get(part_number='generic-fan')
  1042. fan_module_type_profile = ModuleTypeProfile.objects.get(name='Fan')
  1043. assert fan_module_type.profile == fan_module_type_profile
  1044. # run base test
  1045. super().test_bulk_import_objects_with_permission(post_import_callback=verify_module_type_profile)
  1046. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
  1047. def test_bulk_import_objects_with_constrained_permission(self):
  1048. self.add_permissions(
  1049. 'dcim.add_consoleporttemplate',
  1050. 'dcim.add_consoleserverporttemplate',
  1051. 'dcim.add_powerporttemplate',
  1052. 'dcim.add_poweroutlettemplate',
  1053. 'dcim.add_interfacetemplate',
  1054. 'dcim.add_frontporttemplate',
  1055. 'dcim.add_rearporttemplate',
  1056. 'dcim.add_modulebaytemplate',
  1057. )
  1058. super().test_bulk_import_objects_with_constrained_permission()
  1059. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1060. def test_moduletype_consoleports(self):
  1061. moduletype = ModuleType.objects.first()
  1062. console_ports = (
  1063. ConsolePortTemplate(module_type=moduletype, name='Console Port 1'),
  1064. ConsolePortTemplate(module_type=moduletype, name='Console Port 2'),
  1065. ConsolePortTemplate(module_type=moduletype, name='Console Port 3'),
  1066. )
  1067. ConsolePortTemplate.objects.bulk_create(console_ports)
  1068. url = reverse('dcim:moduletype_consoleports', kwargs={'pk': moduletype.pk})
  1069. self.assertHttpStatus(self.client.get(url), 200)
  1070. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1071. def test_moduletype_consoleserverports(self):
  1072. moduletype = ModuleType.objects.first()
  1073. console_server_ports = (
  1074. ConsoleServerPortTemplate(module_type=moduletype, name='Console Server Port 1'),
  1075. ConsoleServerPortTemplate(module_type=moduletype, name='Console Server Port 2'),
  1076. ConsoleServerPortTemplate(module_type=moduletype, name='Console Server Port 3'),
  1077. )
  1078. ConsoleServerPortTemplate.objects.bulk_create(console_server_ports)
  1079. url = reverse('dcim:moduletype_consoleserverports', kwargs={'pk': moduletype.pk})
  1080. self.assertHttpStatus(self.client.get(url), 200)
  1081. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1082. def test_moduletype_powerports(self):
  1083. moduletype = ModuleType.objects.first()
  1084. power_ports = (
  1085. PowerPortTemplate(module_type=moduletype, name='Power Port 1'),
  1086. PowerPortTemplate(module_type=moduletype, name='Power Port 2'),
  1087. PowerPortTemplate(module_type=moduletype, name='Power Port 3'),
  1088. )
  1089. PowerPortTemplate.objects.bulk_create(power_ports)
  1090. url = reverse('dcim:moduletype_powerports', kwargs={'pk': moduletype.pk})
  1091. self.assertHttpStatus(self.client.get(url), 200)
  1092. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1093. def test_moduletype_poweroutlets(self):
  1094. moduletype = ModuleType.objects.first()
  1095. power_outlets = (
  1096. PowerOutletTemplate(module_type=moduletype, name='Power Outlet 1'),
  1097. PowerOutletTemplate(module_type=moduletype, name='Power Outlet 2'),
  1098. PowerOutletTemplate(module_type=moduletype, name='Power Outlet 3'),
  1099. )
  1100. PowerOutletTemplate.objects.bulk_create(power_outlets)
  1101. url = reverse('dcim:moduletype_poweroutlets', kwargs={'pk': moduletype.pk})
  1102. self.assertHttpStatus(self.client.get(url), 200)
  1103. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1104. def test_moduletype_interfaces(self):
  1105. moduletype = ModuleType.objects.first()
  1106. interfaces = (
  1107. InterfaceTemplate(module_type=moduletype, name='Interface 1'),
  1108. InterfaceTemplate(module_type=moduletype, name='Interface 2'),
  1109. InterfaceTemplate(module_type=moduletype, name='Interface 3'),
  1110. )
  1111. InterfaceTemplate.objects.bulk_create(interfaces)
  1112. url = reverse('dcim:moduletype_interfaces', kwargs={'pk': moduletype.pk})
  1113. self.assertHttpStatus(self.client.get(url), 200)
  1114. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1115. def test_moduletype_rearports(self):
  1116. moduletype = ModuleType.objects.first()
  1117. rear_ports = (
  1118. RearPortTemplate(module_type=moduletype, name='Rear Port 1'),
  1119. RearPortTemplate(module_type=moduletype, name='Rear Port 2'),
  1120. RearPortTemplate(module_type=moduletype, name='Rear Port 3'),
  1121. )
  1122. RearPortTemplate.objects.bulk_create(rear_ports)
  1123. url = reverse('dcim:moduletype_rearports', kwargs={'pk': moduletype.pk})
  1124. self.assertHttpStatus(self.client.get(url), 200)
  1125. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1126. def test_moduletype_frontports(self):
  1127. moduletype = ModuleType.objects.first()
  1128. rear_ports = (
  1129. RearPortTemplate(module_type=moduletype, name='Rear Port 1'),
  1130. RearPortTemplate(module_type=moduletype, name='Rear Port 2'),
  1131. RearPortTemplate(module_type=moduletype, name='Rear Port 3'),
  1132. )
  1133. RearPortTemplate.objects.bulk_create(rear_ports)
  1134. front_ports = (
  1135. FrontPortTemplate(
  1136. module_type=moduletype, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1
  1137. ),
  1138. FrontPortTemplate(
  1139. module_type=moduletype, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1
  1140. ),
  1141. FrontPortTemplate(
  1142. module_type=moduletype, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1
  1143. ),
  1144. )
  1145. FrontPortTemplate.objects.bulk_create(front_ports)
  1146. url = reverse('dcim:moduletype_frontports', kwargs={'pk': moduletype.pk})
  1147. self.assertHttpStatus(self.client.get(url), 200)
  1148. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1149. def test_import_objects(self):
  1150. """
  1151. Custom import test for YAML-based imports (versus CSV)
  1152. """
  1153. IMPORT_DATA = """
  1154. manufacturer: Generic
  1155. model: TEST-1000
  1156. weight: 10
  1157. weight_unit: lb
  1158. comments: Test comment
  1159. console-ports:
  1160. - name: Console Port 1
  1161. type: de-9
  1162. - name: Console Port 2
  1163. type: de-9
  1164. - name: Console Port 3
  1165. type: de-9
  1166. console-server-ports:
  1167. - name: Console Server Port 1
  1168. type: rj-45
  1169. - name: Console Server Port 2
  1170. type: rj-45
  1171. - name: Console Server Port 3
  1172. type: rj-45
  1173. power-ports:
  1174. - name: Power Port 1
  1175. type: iec-60320-c14
  1176. - name: Power Port 2
  1177. type: iec-60320-c14
  1178. - name: Power Port 3
  1179. type: iec-60320-c14
  1180. power-outlets:
  1181. - name: Power Outlet 1
  1182. type: iec-60320-c13
  1183. power_port: Power Port 1
  1184. feed_leg: A
  1185. - name: Power Outlet 2
  1186. type: iec-60320-c13
  1187. power_port: Power Port 1
  1188. feed_leg: A
  1189. - name: Power Outlet 3
  1190. type: iec-60320-c13
  1191. power_port: Power Port 1
  1192. feed_leg: A
  1193. interfaces:
  1194. - name: Interface 1
  1195. type: 1000base-t
  1196. mgmt_only: true
  1197. - name: Interface 2
  1198. type: 1000base-t
  1199. - name: Interface 3
  1200. type: 1000base-t
  1201. rear-ports:
  1202. - name: Rear Port 1
  1203. type: 8p8c
  1204. - name: Rear Port 2
  1205. type: 8p8c
  1206. - name: Rear Port 3
  1207. type: 8p8c
  1208. front-ports:
  1209. - name: Front Port 1
  1210. type: 8p8c
  1211. rear_port: Rear Port 1
  1212. - name: Front Port 2
  1213. type: 8p8c
  1214. rear_port: Rear Port 2
  1215. - name: Front Port 3
  1216. type: 8p8c
  1217. rear_port: Rear Port 3
  1218. module-bays:
  1219. - name: Module Bay 1
  1220. position: 1
  1221. - name: Module Bay 2
  1222. position: 2
  1223. - name: Module Bay 3
  1224. position: 3
  1225. """
  1226. # Create the manufacturer
  1227. manufacturer = Manufacturer(name='Generic', slug='generic')
  1228. manufacturer.save()
  1229. # Add all required permissions to the test user
  1230. self.add_permissions(
  1231. 'dcim.view_moduletype',
  1232. 'dcim.add_moduletype',
  1233. 'dcim.add_consoleporttemplate',
  1234. 'dcim.add_consoleserverporttemplate',
  1235. 'dcim.add_powerporttemplate',
  1236. 'dcim.add_poweroutlettemplate',
  1237. 'dcim.add_interfacetemplate',
  1238. 'dcim.add_frontporttemplate',
  1239. 'dcim.add_rearporttemplate',
  1240. 'dcim.add_modulebaytemplate',
  1241. )
  1242. form_data = {
  1243. 'data': IMPORT_DATA,
  1244. 'format': 'yaml'
  1245. }
  1246. response = self.client.post(reverse('dcim:moduletype_bulk_import'), data=form_data, follow=True)
  1247. self.assertHttpStatus(response, 200)
  1248. module_type = ModuleType.objects.get(model='TEST-1000')
  1249. self.assertEqual(module_type.manufacturer.pk, manufacturer.pk)
  1250. self.assertEqual(module_type.weight, 10)
  1251. self.assertEqual(module_type.weight_unit, WeightUnitChoices.UNIT_POUND)
  1252. self.assertEqual(module_type.comments, 'Test comment')
  1253. # Verify all the components were created
  1254. self.assertEqual(module_type.consoleporttemplates.count(), 3)
  1255. cp1 = ConsolePortTemplate.objects.first()
  1256. self.assertEqual(cp1.name, 'Console Port 1')
  1257. self.assertEqual(cp1.type, ConsolePortTypeChoices.TYPE_DE9)
  1258. self.assertEqual(module_type.consoleserverporttemplates.count(), 3)
  1259. csp1 = ConsoleServerPortTemplate.objects.first()
  1260. self.assertEqual(csp1.name, 'Console Server Port 1')
  1261. self.assertEqual(csp1.type, ConsolePortTypeChoices.TYPE_RJ45)
  1262. self.assertEqual(module_type.powerporttemplates.count(), 3)
  1263. pp1 = PowerPortTemplate.objects.first()
  1264. self.assertEqual(pp1.name, 'Power Port 1')
  1265. self.assertEqual(pp1.type, PowerPortTypeChoices.TYPE_IEC_C14)
  1266. self.assertEqual(module_type.poweroutlettemplates.count(), 3)
  1267. po1 = PowerOutletTemplate.objects.first()
  1268. self.assertEqual(po1.name, 'Power Outlet 1')
  1269. self.assertEqual(po1.type, PowerOutletTypeChoices.TYPE_IEC_C13)
  1270. self.assertEqual(po1.power_port, pp1)
  1271. self.assertEqual(po1.feed_leg, PowerOutletFeedLegChoices.FEED_LEG_A)
  1272. self.assertEqual(module_type.interfacetemplates.count(), 3)
  1273. iface1 = InterfaceTemplate.objects.first()
  1274. self.assertEqual(iface1.name, 'Interface 1')
  1275. self.assertEqual(iface1.type, InterfaceTypeChoices.TYPE_1GE_FIXED)
  1276. self.assertTrue(iface1.mgmt_only)
  1277. self.assertEqual(module_type.rearporttemplates.count(), 3)
  1278. rp1 = RearPortTemplate.objects.first()
  1279. self.assertEqual(rp1.name, 'Rear Port 1')
  1280. self.assertEqual(module_type.frontporttemplates.count(), 3)
  1281. fp1 = FrontPortTemplate.objects.first()
  1282. self.assertEqual(fp1.name, 'Front Port 1')
  1283. self.assertEqual(fp1.rear_port, rp1)
  1284. self.assertEqual(fp1.rear_port_position, 1)
  1285. self.assertEqual(module_type.modulebaytemplates.count(), 3)
  1286. mb1 = ModuleBayTemplate.objects.first()
  1287. self.assertEqual(mb1.name, 'Module Bay 1')
  1288. self.assertEqual(mb1.position, '1')
  1289. def test_export_objects(self):
  1290. url = reverse('dcim:moduletype_list')
  1291. self.add_permissions('dcim.view_moduletype')
  1292. # Test default YAML export
  1293. response = self.client.get(f'{url}?export')
  1294. self.assertEqual(response.status_code, 200)
  1295. data = list(yaml.load_all(response.content, Loader=yaml.SafeLoader))
  1296. self.assertEqual(len(data), 3)
  1297. self.assertEqual(data[0]['manufacturer'], 'Manufacturer 1')
  1298. self.assertEqual(data[0]['model'], 'Module Type 1')
  1299. # Test table-based export
  1300. response = self.client.get(f'{url}?export=table')
  1301. self.assertHttpStatus(response, 200)
  1302. self.assertEqual(response.get('Content-Type'), 'text/csv; charset=utf-8')
  1303. class ModuleTypeProfileTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  1304. model = ModuleTypeProfile
  1305. SCHEMAS = [
  1306. {
  1307. "properties": {
  1308. "foo": {
  1309. "type": "string"
  1310. }
  1311. }
  1312. },
  1313. {
  1314. "properties": {
  1315. "foo": {
  1316. "type": "integer"
  1317. }
  1318. }
  1319. },
  1320. {
  1321. "properties": {
  1322. "foo": {
  1323. "type": "boolean"
  1324. }
  1325. }
  1326. },
  1327. ]
  1328. @classmethod
  1329. def setUpTestData(cls):
  1330. module_type_profiles = (
  1331. ModuleTypeProfile(
  1332. name='Module Type Profile 1',
  1333. schema=cls.SCHEMAS[0]
  1334. ),
  1335. ModuleTypeProfile(
  1336. name='Module Type Profile 2',
  1337. schema=cls.SCHEMAS[1]
  1338. ),
  1339. ModuleTypeProfile(
  1340. name='Module Type Profile 3',
  1341. schema=cls.SCHEMAS[2]
  1342. ),
  1343. )
  1344. ModuleTypeProfile.objects.bulk_create(module_type_profiles)
  1345. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  1346. cls.form_data = {
  1347. 'name': 'Module Type Profile X',
  1348. 'description': 'A new profile',
  1349. 'schema': json.dumps(cls.SCHEMAS[0]),
  1350. 'tags': [t.pk for t in tags],
  1351. }
  1352. cls.csv_data = (
  1353. "name,schema",
  1354. f"Module Type Profile 4,{json.dumps(cls.SCHEMAS[0])}",
  1355. f"Module Type Profile 5,{json.dumps(cls.SCHEMAS[1])}",
  1356. f"Module Type Profile 6,{json.dumps(cls.SCHEMAS[2])}",
  1357. )
  1358. cls.csv_update_data = (
  1359. "id,description",
  1360. f"{module_type_profiles[0].pk},New description",
  1361. f"{module_type_profiles[1].pk},New description",
  1362. f"{module_type_profiles[2].pk},New description",
  1363. )
  1364. cls.bulk_edit_data = {
  1365. 'description': 'New description',
  1366. }
  1367. #
  1368. # DeviceType components
  1369. #
  1370. class ConsolePortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1371. model = ConsolePortTemplate
  1372. validation_excluded_fields = ('name', 'label')
  1373. @classmethod
  1374. def setUpTestData(cls):
  1375. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1376. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1377. ConsolePortTemplate.objects.bulk_create((
  1378. ConsolePortTemplate(device_type=devicetype, name='Console Port Template 1'),
  1379. ConsolePortTemplate(device_type=devicetype, name='Console Port Template 2'),
  1380. ConsolePortTemplate(device_type=devicetype, name='Console Port Template 3'),
  1381. ))
  1382. cls.form_data = {
  1383. 'device_type': devicetype.pk,
  1384. 'name': 'Console Port Template X',
  1385. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  1386. }
  1387. cls.bulk_create_data = {
  1388. 'device_type': devicetype.pk,
  1389. 'name': 'Console Port Template [4-6]',
  1390. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  1391. }
  1392. cls.bulk_edit_data = {
  1393. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  1394. }
  1395. class ConsoleServerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1396. model = ConsoleServerPortTemplate
  1397. validation_excluded_fields = ('name', 'label')
  1398. @classmethod
  1399. def setUpTestData(cls):
  1400. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1401. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1402. ConsoleServerPortTemplate.objects.bulk_create((
  1403. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 1'),
  1404. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 2'),
  1405. ConsoleServerPortTemplate(device_type=devicetype, name='Console Server Port Template 3'),
  1406. ))
  1407. cls.form_data = {
  1408. 'device_type': devicetype.pk,
  1409. 'name': 'Console Server Port Template X',
  1410. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  1411. }
  1412. cls.bulk_create_data = {
  1413. 'device_type': devicetype.pk,
  1414. 'name': 'Console Server Port Template [4-6]',
  1415. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  1416. }
  1417. cls.bulk_edit_data = {
  1418. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  1419. }
  1420. class PowerPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1421. model = PowerPortTemplate
  1422. validation_excluded_fields = ('name', 'label')
  1423. @classmethod
  1424. def setUpTestData(cls):
  1425. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1426. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1427. PowerPortTemplate.objects.bulk_create((
  1428. PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
  1429. PowerPortTemplate(device_type=devicetype, name='Power Port Template 2'),
  1430. PowerPortTemplate(device_type=devicetype, name='Power Port Template 3'),
  1431. ))
  1432. cls.form_data = {
  1433. 'device_type': devicetype.pk,
  1434. 'name': 'Power Port Template X',
  1435. 'type': PowerPortTypeChoices.TYPE_IEC_C14,
  1436. 'maximum_draw': 100,
  1437. 'allocated_draw': 50,
  1438. }
  1439. cls.bulk_create_data = {
  1440. 'device_type': devicetype.pk,
  1441. 'name': 'Power Port Template [4-6]',
  1442. 'type': PowerPortTypeChoices.TYPE_IEC_C14,
  1443. 'maximum_draw': 100,
  1444. 'allocated_draw': 50,
  1445. }
  1446. cls.bulk_edit_data = {
  1447. 'type': PowerPortTypeChoices.TYPE_IEC_C14,
  1448. 'maximum_draw': 100,
  1449. 'allocated_draw': 50,
  1450. }
  1451. class PowerOutletTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1452. model = PowerOutletTemplate
  1453. validation_excluded_fields = ('name', 'label')
  1454. @classmethod
  1455. def setUpTestData(cls):
  1456. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1457. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1458. PowerOutletTemplate.objects.bulk_create((
  1459. PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 1'),
  1460. PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 2'),
  1461. PowerOutletTemplate(device_type=devicetype, name='Power Outlet Template 3'),
  1462. ))
  1463. powerports = (
  1464. PowerPortTemplate(device_type=devicetype, name='Power Port Template 1'),
  1465. )
  1466. PowerPortTemplate.objects.bulk_create(powerports)
  1467. cls.form_data = {
  1468. 'device_type': devicetype.pk,
  1469. 'name': 'Power Outlet Template X',
  1470. 'type': PowerOutletTypeChoices.TYPE_IEC_C13,
  1471. 'power_port': powerports[0].pk,
  1472. 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
  1473. }
  1474. cls.bulk_create_data = {
  1475. 'device_type': devicetype.pk,
  1476. 'name': 'Power Outlet Template [4-6]',
  1477. 'type': PowerOutletTypeChoices.TYPE_IEC_C13,
  1478. 'power_port': powerports[0].pk,
  1479. 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
  1480. }
  1481. cls.bulk_edit_data = {
  1482. 'type': PowerOutletTypeChoices.TYPE_IEC_C13,
  1483. 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
  1484. }
  1485. class InterfaceTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1486. model = InterfaceTemplate
  1487. validation_excluded_fields = ('name', 'label')
  1488. @classmethod
  1489. def setUpTestData(cls):
  1490. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1491. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1492. InterfaceTemplate.objects.bulk_create((
  1493. InterfaceTemplate(device_type=devicetype, name='Interface Template 1'),
  1494. InterfaceTemplate(device_type=devicetype, name='Interface Template 2'),
  1495. InterfaceTemplate(device_type=devicetype, name='Interface Template 3'),
  1496. ))
  1497. cls.form_data = {
  1498. 'device_type': devicetype.pk,
  1499. 'name': 'Interface Template X',
  1500. 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
  1501. 'mgmt_only': True,
  1502. }
  1503. cls.bulk_create_data = {
  1504. 'device_type': devicetype.pk,
  1505. 'name': 'Interface Template [4-6]',
  1506. # Test that a label can be applied to each generated interface templates
  1507. 'label': 'Interface Template Label [3-5]',
  1508. 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
  1509. 'mgmt_only': True,
  1510. }
  1511. cls.bulk_edit_data = {
  1512. 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
  1513. 'mgmt_only': True,
  1514. }
  1515. class FrontPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1516. model = FrontPortTemplate
  1517. validation_excluded_fields = ('name', 'label', 'rear_port')
  1518. @classmethod
  1519. def setUpTestData(cls):
  1520. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1521. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1522. rearports = (
  1523. RearPortTemplate(device_type=devicetype, name='Rear Port Template 1'),
  1524. RearPortTemplate(device_type=devicetype, name='Rear Port Template 2'),
  1525. RearPortTemplate(device_type=devicetype, name='Rear Port Template 3'),
  1526. RearPortTemplate(device_type=devicetype, name='Rear Port Template 4'),
  1527. RearPortTemplate(device_type=devicetype, name='Rear Port Template 5'),
  1528. RearPortTemplate(device_type=devicetype, name='Rear Port Template 6'),
  1529. )
  1530. RearPortTemplate.objects.bulk_create(rearports)
  1531. FrontPortTemplate.objects.bulk_create(
  1532. (
  1533. FrontPortTemplate(
  1534. device_type=devicetype, name='Front Port Template 1', rear_port=rearports[0], rear_port_position=1
  1535. ),
  1536. FrontPortTemplate(
  1537. device_type=devicetype, name='Front Port Template 2', rear_port=rearports[1], rear_port_position=1
  1538. ),
  1539. FrontPortTemplate(
  1540. device_type=devicetype, name='Front Port Template 3', rear_port=rearports[2], rear_port_position=1
  1541. ),
  1542. )
  1543. )
  1544. cls.form_data = {
  1545. 'device_type': devicetype.pk,
  1546. 'name': 'Front Port X',
  1547. 'type': PortTypeChoices.TYPE_8P8C,
  1548. 'rear_port': rearports[3].pk,
  1549. 'rear_port_position': 1,
  1550. }
  1551. cls.bulk_create_data = {
  1552. 'device_type': devicetype.pk,
  1553. 'name': 'Front Port [4-6]',
  1554. 'type': PortTypeChoices.TYPE_8P8C,
  1555. 'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
  1556. }
  1557. cls.bulk_edit_data = {
  1558. 'type': PortTypeChoices.TYPE_8P8C,
  1559. }
  1560. class RearPortTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1561. model = RearPortTemplate
  1562. validation_excluded_fields = ('name', 'label')
  1563. @classmethod
  1564. def setUpTestData(cls):
  1565. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1566. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1567. RearPortTemplate.objects.bulk_create((
  1568. RearPortTemplate(device_type=devicetype, name='Rear Port Template 1'),
  1569. RearPortTemplate(device_type=devicetype, name='Rear Port Template 2'),
  1570. RearPortTemplate(device_type=devicetype, name='Rear Port Template 3'),
  1571. ))
  1572. cls.form_data = {
  1573. 'device_type': devicetype.pk,
  1574. 'name': 'Rear Port Template X',
  1575. 'type': PortTypeChoices.TYPE_8P8C,
  1576. 'positions': 2,
  1577. }
  1578. cls.bulk_create_data = {
  1579. 'device_type': devicetype.pk,
  1580. 'name': 'Rear Port Template [4-6]',
  1581. 'type': PortTypeChoices.TYPE_8P8C,
  1582. 'positions': 2,
  1583. }
  1584. cls.bulk_edit_data = {
  1585. 'type': PortTypeChoices.TYPE_8P8C,
  1586. }
  1587. class ModuleBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1588. model = ModuleBayTemplate
  1589. validation_excluded_fields = ('name', 'label')
  1590. @classmethod
  1591. def setUpTestData(cls):
  1592. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1593. devicetype = DeviceType.objects.create(manufacturer=manufacturer, model='Device Type 1', slug='device-type-1')
  1594. ModuleBayTemplate.objects.bulk_create((
  1595. ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 1'),
  1596. ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 2'),
  1597. ModuleBayTemplate(device_type=devicetype, name='Module Bay Template 3'),
  1598. ))
  1599. cls.form_data = {
  1600. 'device_type': devicetype.pk,
  1601. 'name': 'Module Bay Template X',
  1602. }
  1603. cls.bulk_create_data = {
  1604. 'device_type': devicetype.pk,
  1605. 'name': 'Module Bay Template [4-6]',
  1606. }
  1607. cls.bulk_edit_data = {
  1608. 'description': 'Foo bar',
  1609. }
  1610. class DeviceBayTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1611. model = DeviceBayTemplate
  1612. validation_excluded_fields = ('name', 'label')
  1613. @classmethod
  1614. def setUpTestData(cls):
  1615. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1616. devicetype = DeviceType.objects.create(
  1617. manufacturer=manufacturer,
  1618. model='Device Type 1',
  1619. slug='device-type-1',
  1620. subdevice_role=SubdeviceRoleChoices.ROLE_PARENT
  1621. )
  1622. DeviceBayTemplate.objects.bulk_create((
  1623. DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 1'),
  1624. DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 2'),
  1625. DeviceBayTemplate(device_type=devicetype, name='Device Bay Template 3'),
  1626. ))
  1627. cls.form_data = {
  1628. 'device_type': devicetype.pk,
  1629. 'name': 'Device Bay Template X',
  1630. }
  1631. cls.bulk_create_data = {
  1632. 'device_type': devicetype.pk,
  1633. 'name': 'Device Bay Template [4-6]',
  1634. }
  1635. cls.bulk_edit_data = {
  1636. 'description': 'Foo bar',
  1637. }
  1638. class InventoryItemTemplateTestCase(ViewTestCases.DeviceComponentTemplateViewTestCase):
  1639. model = InventoryItemTemplate
  1640. validation_excluded_fields = ('name', 'label')
  1641. @classmethod
  1642. def setUpTestData(cls):
  1643. manufacturers = (
  1644. Manufacturer(name='Manufacturer 1', slug='manufacturer-1'),
  1645. Manufacturer(name='Manufacturer 2', slug='manufacturer-2'),
  1646. )
  1647. Manufacturer.objects.bulk_create(manufacturers)
  1648. devicetype = DeviceType.objects.create(
  1649. manufacturer=manufacturers[0], model='Device Type 1', slug='device-type-1'
  1650. )
  1651. inventory_item_templates = (
  1652. InventoryItemTemplate(
  1653. device_type=devicetype, name='Inventory Item Template 1', manufacturer=manufacturers[0]
  1654. ),
  1655. InventoryItemTemplate(
  1656. device_type=devicetype, name='Inventory Item Template 2', manufacturer=manufacturers[0]
  1657. ),
  1658. InventoryItemTemplate(
  1659. device_type=devicetype, name='Inventory Item Template 3', manufacturer=manufacturers[0]
  1660. ),
  1661. )
  1662. for item in inventory_item_templates:
  1663. item.save()
  1664. cls.form_data = {
  1665. 'device_type': devicetype.pk,
  1666. 'name': 'Inventory Item Template X',
  1667. 'manufacturer': manufacturers[1].pk,
  1668. }
  1669. cls.bulk_create_data = {
  1670. 'device_type': devicetype.pk,
  1671. 'name': 'Inventory Item Template [4-6]',
  1672. 'manufacturer': manufacturers[1].pk,
  1673. }
  1674. cls.bulk_edit_data = {
  1675. 'description': 'Foo bar',
  1676. }
  1677. class DeviceRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  1678. model = DeviceRole
  1679. @classmethod
  1680. def setUpTestData(cls):
  1681. roles = [
  1682. DeviceRole(name='Device Role 1', slug='device-role-1'),
  1683. DeviceRole(name='Device Role 2', slug='device-role-2'),
  1684. DeviceRole(name='Device Role 3', slug='device-role-3'),
  1685. DeviceRole(name='Device Role 4', slug='device-role-4'),
  1686. ]
  1687. for role in roles:
  1688. role.save()
  1689. roles.append(DeviceRole.objects.create(name='Device Role 5', slug='device-role-5', parent=roles[3]))
  1690. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  1691. cls.form_data = {
  1692. 'name': 'Device Role X',
  1693. 'slug': 'device-role-x',
  1694. 'color': 'c0c0c0',
  1695. 'vm_role': False,
  1696. 'description': 'New device role',
  1697. 'tags': [t.pk for t in tags],
  1698. }
  1699. cls.csv_data = (
  1700. "name,slug,color",
  1701. "Device Role 6,device-role-6,ff0000",
  1702. "Device Role 7,device-role-7,00ff00",
  1703. "Device Role 8,device-role-8,0000ff",
  1704. )
  1705. cls.csv_update_data = (
  1706. "id,name,description",
  1707. f"{roles[0].pk},Device Role 7,New description7",
  1708. f"{roles[1].pk},Device Role 8,New description8",
  1709. f"{roles[2].pk},Device Role 9,New description9",
  1710. f"{roles[4].pk},Device Role 10,New description10",
  1711. )
  1712. cls.bulk_edit_data = {
  1713. 'color': '00ff00',
  1714. 'description': 'New description',
  1715. }
  1716. class PlatformTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  1717. model = Platform
  1718. @classmethod
  1719. def setUpTestData(cls):
  1720. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1721. platforms = (
  1722. Platform(name='Platform 1', slug='platform-1', manufacturer=manufacturer),
  1723. Platform(name='Platform 2', slug='platform-2', manufacturer=manufacturer),
  1724. Platform(name='Platform 3', slug='platform-3', manufacturer=manufacturer),
  1725. )
  1726. for platform in platforms:
  1727. platform.save()
  1728. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  1729. cls.form_data = {
  1730. 'name': 'Platform X',
  1731. 'slug': 'platform-x',
  1732. 'manufacturer': manufacturer.pk,
  1733. 'description': 'A new platform',
  1734. 'tags': [t.pk for t in tags],
  1735. }
  1736. cls.csv_data = (
  1737. "name,slug,description",
  1738. "Platform 4,platform-4,Fourth platform",
  1739. "Platform 5,platform-5,Fifth platform",
  1740. "Platform 6,platform-6,Sixth platform",
  1741. )
  1742. cls.csv_update_data = (
  1743. "id,name,description",
  1744. f"{platforms[0].pk},Foo,New description",
  1745. f"{platforms[1].pk},Bar,New description",
  1746. f"{platforms[2].pk},Baz,New description",
  1747. )
  1748. cls.bulk_edit_data = {
  1749. 'description': 'New description',
  1750. }
  1751. class DeviceTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  1752. model = Device
  1753. @classmethod
  1754. def setUpTestData(cls):
  1755. sites = (
  1756. Site(name='Site 1', slug='site-1'),
  1757. Site(name='Site 2', slug='site-2'),
  1758. )
  1759. Site.objects.bulk_create(sites)
  1760. location = Location(site=sites[0], name='Location 1', slug='location-1')
  1761. location.save()
  1762. racks = (
  1763. Rack(name='Rack 1', site=sites[0], location=location),
  1764. Rack(name='Rack 2', site=sites[1]),
  1765. )
  1766. Rack.objects.bulk_create(racks)
  1767. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  1768. devicetypes = (
  1769. DeviceType(model='Device Type 1', slug='device-type-1', manufacturer=manufacturer),
  1770. DeviceType(model='Device Type 2', slug='device-type-2', manufacturer=manufacturer),
  1771. )
  1772. DeviceType.objects.bulk_create(devicetypes)
  1773. roles = (
  1774. DeviceRole(name='Device Role 1', slug='device-role-1'),
  1775. DeviceRole(name='Device Role 2', slug='device-role-2'),
  1776. )
  1777. for role in roles:
  1778. role.save()
  1779. platforms = (
  1780. Platform(name='Platform 1', slug='platform-1'),
  1781. Platform(name='Platform 2', slug='platform-2'),
  1782. )
  1783. for platform in platforms:
  1784. platform.save()
  1785. devices = (
  1786. Device(
  1787. name='Device 1',
  1788. site=sites[0],
  1789. rack=racks[0],
  1790. device_type=devicetypes[0],
  1791. role=roles[0],
  1792. platform=platforms[0],
  1793. ),
  1794. Device(
  1795. name='Device 2',
  1796. site=sites[0],
  1797. rack=racks[0],
  1798. device_type=devicetypes[0],
  1799. role=roles[0],
  1800. platform=platforms[0],
  1801. ),
  1802. Device(
  1803. name='Device 3',
  1804. site=sites[0],
  1805. rack=racks[0],
  1806. device_type=devicetypes[0],
  1807. role=roles[0],
  1808. platform=platforms[0],
  1809. ),
  1810. )
  1811. Device.objects.bulk_create(devices)
  1812. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  1813. VirtualChassis.objects.create(name='Virtual Chassis 1')
  1814. cls.form_data = {
  1815. 'device_type': devicetypes[1].pk,
  1816. 'role': roles[1].pk,
  1817. 'tenant': None,
  1818. 'platform': platforms[1].pk,
  1819. 'name': 'Device X',
  1820. 'serial': '123456',
  1821. 'asset_tag': 'ABCDEF',
  1822. 'site': sites[1].pk,
  1823. 'rack': racks[1].pk,
  1824. 'position': 1,
  1825. 'face': DeviceFaceChoices.FACE_FRONT,
  1826. 'latitude': Decimal('35.780000'),
  1827. 'longitude': Decimal('-78.642000'),
  1828. 'status': DeviceStatusChoices.STATUS_PLANNED,
  1829. 'primary_ip4': None,
  1830. 'primary_ip6': None,
  1831. 'cluster': None,
  1832. 'virtual_chassis': None,
  1833. 'vc_position': None,
  1834. 'vc_priority': None,
  1835. 'comments': 'A new device',
  1836. 'tags': [t.pk for t in tags],
  1837. 'local_context_data': None,
  1838. }
  1839. cls.csv_data = (
  1840. (
  1841. "role,manufacturer,device_type,status,name,site,location,rack,position,face,virtual_chassis,"
  1842. "vc_position,vc_priority"
  1843. ),
  1844. (
  1845. "Device Role 1,Manufacturer 1,Device Type 1,active,Device 4,Site 1,Location 1,Rack 1,10,front,"
  1846. "Virtual Chassis 1,1,10"
  1847. ),
  1848. (
  1849. "Device Role 1,Manufacturer 1,Device Type 1,active,Device 5,Site 1,Location 1,Rack 1,20,front,"
  1850. "Virtual Chassis 1,2,20"
  1851. ),
  1852. (
  1853. "Device Role 1,Manufacturer 1,Device Type 1,active,Device 6,Site 1,Location 1,Rack 1,30,front,"
  1854. "Virtual Chassis 1,3,30"
  1855. ),
  1856. )
  1857. cls.csv_update_data = (
  1858. "id,status",
  1859. f"{devices[0].pk},{DeviceStatusChoices.STATUS_DECOMMISSIONING}",
  1860. f"{devices[1].pk},{DeviceStatusChoices.STATUS_DECOMMISSIONING}",
  1861. f"{devices[2].pk},{DeviceStatusChoices.STATUS_DECOMMISSIONING}",
  1862. )
  1863. cls.bulk_edit_data = {
  1864. 'device_type': devicetypes[1].pk,
  1865. 'role': roles[1].pk,
  1866. 'tenant': None,
  1867. 'platform': platforms[1].pk,
  1868. 'serial': '123456',
  1869. 'status': DeviceStatusChoices.STATUS_DECOMMISSIONING,
  1870. }
  1871. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1872. def test_device_consoleports(self):
  1873. device = Device.objects.first()
  1874. console_ports = (
  1875. ConsolePort(device=device, name='Console Port 1'),
  1876. ConsolePort(device=device, name='Console Port 2'),
  1877. ConsolePort(device=device, name='Console Port 3'),
  1878. )
  1879. ConsolePort.objects.bulk_create(console_ports)
  1880. url = reverse('dcim:device_consoleports', kwargs={'pk': device.pk})
  1881. self.assertHttpStatus(self.client.get(url), 200)
  1882. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1883. def test_device_consoleserverports(self):
  1884. device = Device.objects.first()
  1885. console_server_ports = (
  1886. ConsoleServerPort(device=device, name='Console Server Port 1'),
  1887. ConsoleServerPort(device=device, name='Console Server Port 2'),
  1888. ConsoleServerPort(device=device, name='Console Server Port 3'),
  1889. )
  1890. ConsoleServerPort.objects.bulk_create(console_server_ports)
  1891. url = reverse('dcim:device_consoleserverports', kwargs={'pk': device.pk})
  1892. self.assertHttpStatus(self.client.get(url), 200)
  1893. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1894. def test_device_powerports(self):
  1895. device = Device.objects.first()
  1896. power_ports = (
  1897. PowerPort(device=device, name='Power Port 1'),
  1898. PowerPort(device=device, name='Power Port 2'),
  1899. PowerPort(device=device, name='Power Port 3'),
  1900. )
  1901. PowerPort.objects.bulk_create(power_ports)
  1902. url = reverse('dcim:device_powerports', kwargs={'pk': device.pk})
  1903. self.assertHttpStatus(self.client.get(url), 200)
  1904. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1905. def test_device_poweroutlets(self):
  1906. device = Device.objects.first()
  1907. power_outlets = (
  1908. PowerOutlet(device=device, name='Power Outlet 1'),
  1909. PowerOutlet(device=device, name='Power Outlet 2'),
  1910. PowerOutlet(device=device, name='Power Outlet 3'),
  1911. )
  1912. PowerOutlet.objects.bulk_create(power_outlets)
  1913. url = reverse('dcim:device_poweroutlets', kwargs={'pk': device.pk})
  1914. self.assertHttpStatus(self.client.get(url), 200)
  1915. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1916. def test_device_interfaces(self):
  1917. device = Device.objects.first()
  1918. interfaces = (
  1919. Interface(device=device, name='Interface 1'),
  1920. Interface(device=device, name='Interface 2'),
  1921. Interface(device=device, name='Interface 3'),
  1922. )
  1923. Interface.objects.bulk_create(interfaces)
  1924. url = reverse('dcim:device_interfaces', kwargs={'pk': device.pk})
  1925. self.assertHttpStatus(self.client.get(url), 200)
  1926. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1927. def test_device_rearports(self):
  1928. device = Device.objects.first()
  1929. rear_ports = (
  1930. RearPort(device=device, name='Rear Port 1'),
  1931. RearPort(device=device, name='Rear Port 2'),
  1932. RearPort(device=device, name='Rear Port 3'),
  1933. )
  1934. RearPort.objects.bulk_create(rear_ports)
  1935. url = reverse('dcim:device_rearports', kwargs={'pk': device.pk})
  1936. self.assertHttpStatus(self.client.get(url), 200)
  1937. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1938. def test_device_frontports(self):
  1939. device = Device.objects.first()
  1940. rear_ports = (
  1941. RearPort(device=device, name='Rear Port 1'),
  1942. RearPort(device=device, name='Rear Port 2'),
  1943. RearPort(device=device, name='Rear Port 3'),
  1944. )
  1945. RearPort.objects.bulk_create(rear_ports)
  1946. front_ports = (
  1947. FrontPort(device=device, name='Front Port 1', rear_port=rear_ports[0], rear_port_position=1),
  1948. FrontPort(device=device, name='Front Port 2', rear_port=rear_ports[1], rear_port_position=1),
  1949. FrontPort(device=device, name='Front Port 3', rear_port=rear_ports[2], rear_port_position=1),
  1950. )
  1951. FrontPort.objects.bulk_create(front_ports)
  1952. url = reverse('dcim:device_frontports', kwargs={'pk': device.pk})
  1953. self.assertHttpStatus(self.client.get(url), 200)
  1954. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1955. def test_device_modulebays(self):
  1956. device = Device.objects.first()
  1957. ModuleBay.objects.create(device=device, name='Module Bay 1')
  1958. ModuleBay.objects.create(device=device, name='Module Bay 2')
  1959. ModuleBay.objects.create(device=device, name='Module Bay 3')
  1960. url = reverse('dcim:device_modulebays', kwargs={'pk': device.pk})
  1961. self.assertHttpStatus(self.client.get(url), 200)
  1962. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1963. def test_device_devicebays(self):
  1964. device = Device.objects.first()
  1965. device_bays = (
  1966. DeviceBay(device=device, name='Device Bay 1'),
  1967. DeviceBay(device=device, name='Device Bay 2'),
  1968. DeviceBay(device=device, name='Device Bay 3'),
  1969. )
  1970. DeviceBay.objects.bulk_create(device_bays)
  1971. url = reverse('dcim:device_devicebays', kwargs={'pk': device.pk})
  1972. self.assertHttpStatus(self.client.get(url), 200)
  1973. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  1974. def test_device_inventory(self):
  1975. device = Device.objects.first()
  1976. inventory_items = (
  1977. InventoryItem(device=device, name='Inventory Item 1'),
  1978. InventoryItem(device=device, name='Inventory Item 2'),
  1979. InventoryItem(device=device, name='Inventory Item 3'),
  1980. )
  1981. for item in inventory_items:
  1982. item.save()
  1983. url = reverse('dcim:device_inventory', kwargs={'pk': device.pk})
  1984. self.assertHttpStatus(self.client.get(url), 200)
  1985. class ModuleTestCase(
  1986. # Module does not support bulk renaming (no name field) or
  1987. # bulk creation (need to specify module bays)
  1988. ViewTestCases.GetObjectViewTestCase,
  1989. ViewTestCases.GetObjectChangelogViewTestCase,
  1990. ViewTestCases.CreateObjectViewTestCase,
  1991. ViewTestCases.EditObjectViewTestCase,
  1992. ViewTestCases.DeleteObjectViewTestCase,
  1993. ViewTestCases.ListObjectsViewTestCase,
  1994. ViewTestCases.BulkImportObjectsViewTestCase,
  1995. ViewTestCases.BulkEditObjectsViewTestCase,
  1996. ViewTestCases.BulkDeleteObjectsViewTestCase,
  1997. ):
  1998. model = Module
  1999. @classmethod
  2000. def setUpTestData(cls):
  2001. manufacturer = Manufacturer.objects.create(name='Generic', slug='generic')
  2002. devices = (
  2003. create_test_device('Device 1'),
  2004. create_test_device('Device 2'),
  2005. )
  2006. module_types = (
  2007. ModuleType(manufacturer=manufacturer, model='Module Type 1'),
  2008. ModuleType(manufacturer=manufacturer, model='Module Type 2'),
  2009. ModuleType(manufacturer=manufacturer, model='Module Type 3'),
  2010. ModuleType(manufacturer=manufacturer, model='Module Type 4'),
  2011. )
  2012. ModuleType.objects.bulk_create(module_types)
  2013. module_bays = (
  2014. ModuleBay(device=devices[0], name='Module Bay 1'),
  2015. ModuleBay(device=devices[0], name='Module Bay 2'),
  2016. ModuleBay(device=devices[0], name='Module Bay 3'),
  2017. ModuleBay(device=devices[0], name='Module Bay 4'),
  2018. ModuleBay(device=devices[0], name='Module Bay 5'),
  2019. ModuleBay(device=devices[1], name='Module Bay 1'),
  2020. ModuleBay(device=devices[1], name='Module Bay 2'),
  2021. ModuleBay(device=devices[1], name='Module Bay 3'),
  2022. ModuleBay(device=devices[1], name='Module Bay 4'),
  2023. ModuleBay(device=devices[1], name='Module Bay 5'),
  2024. )
  2025. for module_bay in module_bays:
  2026. module_bay.save()
  2027. modules = (
  2028. Module(device=devices[0], module_bay=module_bays[0], module_type=module_types[0]),
  2029. Module(device=devices[0], module_bay=module_bays[1], module_type=module_types[1]),
  2030. Module(device=devices[0], module_bay=module_bays[2], module_type=module_types[2]),
  2031. )
  2032. Module.objects.bulk_create(modules)
  2033. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2034. cls.form_data = {
  2035. 'device': devices[0].pk,
  2036. 'module_bay': module_bays[3].pk,
  2037. 'module_type': module_types[0].pk,
  2038. 'status': ModuleStatusChoices.STATUS_ACTIVE,
  2039. 'serial': 'A',
  2040. 'tags': [t.pk for t in tags],
  2041. }
  2042. cls.bulk_edit_data = {
  2043. 'module_type': module_types[3].pk,
  2044. 'status': ModuleStatusChoices.STATUS_PLANNED,
  2045. }
  2046. cls.csv_data = (
  2047. "device,module_bay,module_type,status,serial,asset_tag",
  2048. "Device 2,Module Bay 1,Module Type 1,active,A,A",
  2049. "Device 2,Module Bay 2,Module Type 2,planned,B,B",
  2050. "Device 2,Module Bay 3,Module Type 3,failed,C,C",
  2051. )
  2052. cls.csv_update_data = (
  2053. "id,status,serial",
  2054. f"{modules[0].pk},offline,Serial 2",
  2055. f"{modules[1].pk},offline,Serial 3",
  2056. f"{modules[2].pk},offline,Serial 1",
  2057. )
  2058. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2059. def test_module_component_replication(self):
  2060. self.add_permissions('dcim.add_module')
  2061. # Add 5 InterfaceTemplates to a ModuleType
  2062. module_type = ModuleType.objects.first()
  2063. interface_templates = [
  2064. InterfaceTemplate(module_type=module_type, name=f'Interface {i}') for i in range(1, 6)
  2065. ]
  2066. InterfaceTemplate.objects.bulk_create(interface_templates)
  2067. form_data = self.form_data.copy()
  2068. device = Device.objects.get(pk=form_data['device'])
  2069. # Create a module *without* replicating components
  2070. form_data['replicate_components'] = False
  2071. request = {
  2072. 'path': self._get_url('add'),
  2073. 'data': post_data(form_data),
  2074. }
  2075. self.assertHttpStatus(self.client.post(**request), 302)
  2076. self.assertEqual(Interface.objects.filter(device=device).count(), 0)
  2077. # Create a second module (in the next bay) with replicated components
  2078. form_data['module_bay'] += 1
  2079. form_data['replicate_components'] = True
  2080. request = {
  2081. 'path': self._get_url('add'),
  2082. 'data': post_data(form_data),
  2083. }
  2084. self.assertHttpStatus(self.client.post(**request), 302)
  2085. self.assertEqual(Interface.objects.filter(device=device).count(), 5)
  2086. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2087. def test_module_bulk_replication(self):
  2088. self.add_permissions('dcim.add_module')
  2089. # Add 5 InterfaceTemplates to a ModuleType
  2090. module_type = ModuleType.objects.first()
  2091. interface_templates = [
  2092. InterfaceTemplate(module_type=module_type, name=f'Interface {i}')
  2093. for i in range(1, 6)
  2094. ]
  2095. InterfaceTemplate.objects.bulk_create(interface_templates)
  2096. # Create a module *without* replicating components
  2097. device = Device.objects.get(name='Device 2')
  2098. module_bay = ModuleBay.objects.get(device=device, name='Module Bay 4')
  2099. csv_data = [
  2100. "device,module_bay,module_type,status,replicate_components",
  2101. f"{device.name},{module_bay.name},{module_type.model},active,false"
  2102. ]
  2103. request = {
  2104. 'path': self._get_url('bulk_import'),
  2105. 'data': {
  2106. 'data': '\n'.join(csv_data),
  2107. 'format': ImportFormatChoices.CSV,
  2108. 'csv_delimiter': CSVDelimiterChoices.AUTO,
  2109. }
  2110. }
  2111. initial_count = Module.objects.count()
  2112. self.assertHttpStatus(self.client.post(**request), 302)
  2113. self.assertEqual(Module.objects.count(), initial_count + len(csv_data) - 1)
  2114. self.assertEqual(Interface.objects.filter(device=device).count(), 0)
  2115. # Create a second module (in the next bay) with replicated components
  2116. module_bay = ModuleBay.objects.get(device=device, name='Module Bay 5')
  2117. csv_data[1] = f"{device.name},{module_bay.name},{module_type.model},active,true"
  2118. request = {
  2119. 'path': self._get_url('bulk_import'),
  2120. 'data': {
  2121. 'data': '\n'.join(csv_data),
  2122. 'format': ImportFormatChoices.CSV,
  2123. 'csv_delimiter': CSVDelimiterChoices.AUTO,
  2124. }
  2125. }
  2126. initial_count = Module.objects.count()
  2127. self.assertHttpStatus(self.client.post(**request), 302)
  2128. self.assertEqual(Module.objects.count(), initial_count + len(csv_data) - 1)
  2129. self.assertEqual(Interface.objects.filter(device=device).count(), 5)
  2130. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2131. def test_module_component_adoption(self):
  2132. self.add_permissions('dcim.add_module')
  2133. interface_name = "Interface-1"
  2134. # Add an interface to the ModuleType
  2135. module_type = ModuleType.objects.first()
  2136. InterfaceTemplate(module_type=module_type, name=interface_name).save()
  2137. form_data = self.form_data.copy()
  2138. device = Device.objects.get(pk=form_data['device'])
  2139. # Create an interface to be adopted
  2140. interface = Interface(device=device, name=interface_name, type=InterfaceTypeChoices.TYPE_10GE_FIXED)
  2141. interface.save()
  2142. # Ensure that interface is created with no module
  2143. self.assertIsNone(interface.module)
  2144. # Create a module with adopted components
  2145. form_data['module_type'] = module_type
  2146. form_data['replicate_components'] = False
  2147. form_data['adopt_components'] = True
  2148. request = {
  2149. 'path': self._get_url('add'),
  2150. 'data': post_data(form_data),
  2151. }
  2152. self.assertHttpStatus(self.client.post(**request), 302)
  2153. # Re-retrieve interface to get new module id
  2154. interface.refresh_from_db()
  2155. # Check that the Interface now has a module
  2156. self.assertIsNotNone(interface.module)
  2157. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2158. def test_module_bulk_adoption(self):
  2159. self.add_permissions('dcim.add_module')
  2160. interface_name = "Interface-1"
  2161. # Add an interface to the ModuleType
  2162. module_type = ModuleType.objects.first()
  2163. InterfaceTemplate(module_type=module_type, name=interface_name).save()
  2164. form_data = self.form_data.copy()
  2165. device = Device.objects.get(pk=form_data['device'])
  2166. # Create an interface to be adopted
  2167. interface = Interface(device=device, name=interface_name, type=InterfaceTypeChoices.TYPE_10GE_FIXED)
  2168. interface.save()
  2169. # Ensure that interface is created with no module
  2170. self.assertIsNone(interface.module)
  2171. # Create a module with adopted components
  2172. module_bay = ModuleBay.objects.get(device=device, name='Module Bay 4')
  2173. csv_data = [
  2174. "device,module_bay,module_type,status,replicate_components,adopt_components",
  2175. f"{device.name},{module_bay.name},{module_type.model},active,false,true"
  2176. ]
  2177. request = {
  2178. 'path': self._get_url('bulk_import'),
  2179. 'data': {
  2180. 'data': '\n'.join(csv_data),
  2181. 'format': ImportFormatChoices.CSV,
  2182. 'csv_delimiter': CSVDelimiterChoices.AUTO,
  2183. }
  2184. }
  2185. initial_count = self._get_queryset().count()
  2186. self.assertHttpStatus(self.client.post(**request), 302)
  2187. self.assertEqual(self._get_queryset().count(), initial_count + len(csv_data) - 1)
  2188. # Re-retrieve interface to get new module id
  2189. interface.refresh_from_db()
  2190. # Check that the Interface now has a module
  2191. self.assertIsNotNone(interface.module)
  2192. class ConsolePortTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2193. model = ConsolePort
  2194. validation_excluded_fields = ('name', 'label')
  2195. @classmethod
  2196. def setUpTestData(cls):
  2197. device = create_test_device('Device 1')
  2198. console_ports = (
  2199. ConsolePort(device=device, name='Console Port 1'),
  2200. ConsolePort(device=device, name='Console Port 2'),
  2201. ConsolePort(device=device, name='Console Port 3'),
  2202. )
  2203. ConsolePort.objects.bulk_create(console_ports)
  2204. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2205. cls.form_data = {
  2206. 'device': device.pk,
  2207. 'name': 'Console Port X',
  2208. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  2209. 'description': 'A console port',
  2210. 'tags': sorted([t.pk for t in tags]),
  2211. }
  2212. cls.bulk_create_data = {
  2213. 'device': device.pk,
  2214. 'name': 'Console Port [4-6]',
  2215. # Test that a label can be applied to each generated console ports
  2216. 'label': 'Serial[3-5]',
  2217. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  2218. 'description': 'A console port',
  2219. 'tags': sorted([t.pk for t in tags]),
  2220. }
  2221. cls.bulk_edit_data = {
  2222. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  2223. 'description': 'New description',
  2224. }
  2225. cls.csv_data = (
  2226. "device,name",
  2227. "Device 1,Console Port 4",
  2228. "Device 1,Console Port 5",
  2229. "Device 1,Console Port 6",
  2230. )
  2231. cls.csv_update_data = (
  2232. "id,name,description",
  2233. f"{console_ports[0].pk},Console Port 7,New description7",
  2234. f"{console_ports[1].pk},Console Port 8,New description8",
  2235. f"{console_ports[2].pk},Console Port 9,New description9",
  2236. )
  2237. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2238. def test_trace(self):
  2239. consoleport = ConsolePort.objects.first()
  2240. consoleserverport = ConsoleServerPort.objects.create(
  2241. device=consoleport.device,
  2242. name='Console Server Port 1'
  2243. )
  2244. Cable(a_terminations=[consoleport], b_terminations=[consoleserverport]).save()
  2245. response = self.client.get(reverse('dcim:consoleport_trace', kwargs={'pk': consoleport.pk}))
  2246. self.assertHttpStatus(response, 200)
  2247. class ConsoleServerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2248. model = ConsoleServerPort
  2249. validation_excluded_fields = ('name', 'label')
  2250. @classmethod
  2251. def setUpTestData(cls):
  2252. device = create_test_device('Device 1')
  2253. console_server_ports = (
  2254. ConsoleServerPort(device=device, name='Console Server Port 1'),
  2255. ConsoleServerPort(device=device, name='Console Server Port 2'),
  2256. ConsoleServerPort(device=device, name='Console Server Port 3'),
  2257. )
  2258. ConsoleServerPort.objects.bulk_create(console_server_ports)
  2259. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2260. cls.form_data = {
  2261. 'device': device.pk,
  2262. 'name': 'Console Server Port X',
  2263. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  2264. 'description': 'A console server port',
  2265. 'tags': [t.pk for t in tags],
  2266. }
  2267. cls.bulk_create_data = {
  2268. 'device': device.pk,
  2269. 'name': 'Console Server Port [4-6]',
  2270. 'type': ConsolePortTypeChoices.TYPE_RJ45,
  2271. 'description': 'A console server port',
  2272. 'tags': [t.pk for t in tags],
  2273. }
  2274. cls.bulk_edit_data = {
  2275. 'type': ConsolePortTypeChoices.TYPE_RJ11,
  2276. 'description': 'New description',
  2277. }
  2278. cls.csv_data = (
  2279. "device,name",
  2280. "Device 1,Console Server Port 4",
  2281. "Device 1,Console Server Port 5",
  2282. "Device 1,Console Server Port 6",
  2283. )
  2284. cls.csv_update_data = (
  2285. "id,name,description",
  2286. f"{console_server_ports[0].pk},Console Server Port 7,New description 7",
  2287. f"{console_server_ports[1].pk},Console Server Port 8,New description 8",
  2288. f"{console_server_ports[2].pk},Console Server Port 9,New description 9",
  2289. )
  2290. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2291. def test_trace(self):
  2292. consoleserverport = ConsoleServerPort.objects.first()
  2293. consoleport = ConsolePort.objects.create(
  2294. device=consoleserverport.device,
  2295. name='Console Port 1'
  2296. )
  2297. Cable(a_terminations=[consoleserverport], b_terminations=[consoleport]).save()
  2298. response = self.client.get(reverse('dcim:consoleserverport_trace', kwargs={'pk': consoleserverport.pk}))
  2299. self.assertHttpStatus(response, 200)
  2300. class PowerPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2301. model = PowerPort
  2302. validation_excluded_fields = ('name', 'label')
  2303. @classmethod
  2304. def setUpTestData(cls):
  2305. device = create_test_device('Device 1')
  2306. power_ports = (
  2307. PowerPort(device=device, name='Power Port 1'),
  2308. PowerPort(device=device, name='Power Port 2'),
  2309. PowerPort(device=device, name='Power Port 3'),
  2310. )
  2311. PowerPort.objects.bulk_create(power_ports)
  2312. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2313. cls.form_data = {
  2314. 'device': device.pk,
  2315. 'name': 'Power Port X',
  2316. 'type': PowerPortTypeChoices.TYPE_IEC_C14,
  2317. 'maximum_draw': 100,
  2318. 'allocated_draw': 50,
  2319. 'description': 'A power port',
  2320. 'tags': [t.pk for t in tags],
  2321. }
  2322. cls.bulk_create_data = {
  2323. 'device': device.pk,
  2324. 'name': 'Power Port [4-6]]',
  2325. 'type': PowerPortTypeChoices.TYPE_IEC_C14,
  2326. 'maximum_draw': 100,
  2327. 'allocated_draw': 50,
  2328. 'description': 'A power port',
  2329. 'tags': [t.pk for t in tags],
  2330. }
  2331. cls.bulk_edit_data = {
  2332. 'type': PowerPortTypeChoices.TYPE_IEC_C14,
  2333. 'maximum_draw': 100,
  2334. 'allocated_draw': 50,
  2335. 'description': 'New description',
  2336. }
  2337. cls.csv_data = (
  2338. "device,name",
  2339. "Device 1,Power Port 4",
  2340. "Device 1,Power Port 5",
  2341. "Device 1,Power Port 6",
  2342. )
  2343. cls.csv_update_data = (
  2344. "id,name,description",
  2345. f"{power_ports[0].pk},Power Port 7,New description7",
  2346. f"{power_ports[1].pk},Power Port 8,New description8",
  2347. f"{power_ports[2].pk},Power Port 9,New description9",
  2348. )
  2349. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2350. def test_trace(self):
  2351. powerport = PowerPort.objects.first()
  2352. poweroutlet = PowerOutlet.objects.create(
  2353. device=powerport.device,
  2354. name='Power Outlet 1'
  2355. )
  2356. Cable(a_terminations=[powerport], b_terminations=[poweroutlet]).save()
  2357. response = self.client.get(reverse('dcim:powerport_trace', kwargs={'pk': powerport.pk}))
  2358. self.assertHttpStatus(response, 200)
  2359. class PowerOutletTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2360. model = PowerOutlet
  2361. validation_excluded_fields = ('name', 'label')
  2362. @classmethod
  2363. def setUpTestData(cls):
  2364. device = create_test_device('Device 1')
  2365. powerports = (
  2366. PowerPort(device=device, name='Power Port 1'),
  2367. PowerPort(device=device, name='Power Port 2'),
  2368. )
  2369. PowerPort.objects.bulk_create(powerports)
  2370. power_outlets = (
  2371. PowerOutlet(device=device, name='Power Outlet 1', power_port=powerports[0]),
  2372. PowerOutlet(device=device, name='Power Outlet 2', power_port=powerports[0]),
  2373. PowerOutlet(device=device, name='Power Outlet 3', power_port=powerports[0]),
  2374. )
  2375. PowerOutlet.objects.bulk_create(power_outlets)
  2376. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2377. cls.form_data = {
  2378. 'device': device.pk,
  2379. 'name': 'Power Outlet X',
  2380. 'type': PowerOutletTypeChoices.TYPE_IEC_C13,
  2381. 'status': PowerOutletStatusChoices.STATUS_ENABLED,
  2382. 'power_port': powerports[1].pk,
  2383. 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
  2384. 'description': 'A power outlet',
  2385. 'tags': [t.pk for t in tags],
  2386. }
  2387. cls.bulk_create_data = {
  2388. 'device': device.pk,
  2389. 'name': 'Power Outlet [4-6]',
  2390. 'type': PowerOutletTypeChoices.TYPE_IEC_C13,
  2391. 'status': PowerOutletStatusChoices.STATUS_ENABLED,
  2392. 'power_port': powerports[1].pk,
  2393. 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
  2394. 'description': 'A power outlet',
  2395. 'tags': [t.pk for t in tags],
  2396. }
  2397. cls.bulk_edit_data = {
  2398. 'type': PowerOutletTypeChoices.TYPE_IEC_C15,
  2399. 'status': PowerOutletStatusChoices.STATUS_ENABLED,
  2400. 'power_port': powerports[1].pk,
  2401. 'feed_leg': PowerOutletFeedLegChoices.FEED_LEG_B,
  2402. 'description': 'New description',
  2403. }
  2404. cls.csv_data = (
  2405. "device,name",
  2406. "Device 1,Power Outlet 4",
  2407. "Device 1,Power Outlet 5",
  2408. "Device 1,Power Outlet 6",
  2409. )
  2410. cls.csv_update_data = (
  2411. "id,name,description",
  2412. f"{power_outlets[0].pk},Power Outlet 7,New description7",
  2413. f"{power_outlets[1].pk},Power Outlet 8,New description8",
  2414. f"{power_outlets[2].pk},Power Outlet 9,New description9",
  2415. )
  2416. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2417. def test_trace(self):
  2418. poweroutlet = PowerOutlet.objects.first()
  2419. powerport = PowerPort.objects.first()
  2420. Cable(a_terminations=[poweroutlet], b_terminations=[powerport]).save()
  2421. response = self.client.get(reverse('dcim:poweroutlet_trace', kwargs={'pk': poweroutlet.pk}))
  2422. self.assertHttpStatus(response, 200)
  2423. class InterfaceTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2424. model = Interface
  2425. validation_excluded_fields = ('name', 'label')
  2426. @classmethod
  2427. def setUpTestData(cls):
  2428. device = create_test_device('Device 1')
  2429. interfaces = (
  2430. Interface(device=device, name='Interface 1'),
  2431. Interface(device=device, name='Interface 2'),
  2432. Interface(device=device, name='Interface 3'),
  2433. Interface(device=device, name='LAG', type=InterfaceTypeChoices.TYPE_LAG),
  2434. Interface(device=device, name='_BRIDGE', type=InterfaceTypeChoices.TYPE_VIRTUAL), # Must be ordered last
  2435. )
  2436. Interface.objects.bulk_create(interfaces)
  2437. vlans = (
  2438. VLAN(vid=1, name='VLAN1', site=device.site),
  2439. VLAN(vid=101, name='VLAN101', site=device.site),
  2440. VLAN(vid=102, name='VLAN102', site=device.site),
  2441. VLAN(vid=103, name='VLAN103', site=device.site),
  2442. )
  2443. VLAN.objects.bulk_create(vlans)
  2444. wireless_lans = (
  2445. WirelessLAN(ssid='WLAN1'),
  2446. WirelessLAN(ssid='WLAN2'),
  2447. )
  2448. WirelessLAN.objects.bulk_create(wireless_lans)
  2449. vrfs = (
  2450. VRF(name='VRF 1'),
  2451. VRF(name='VRF 2'),
  2452. VRF(name='VRF 3'),
  2453. )
  2454. VRF.objects.bulk_create(vrfs)
  2455. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2456. cls.form_data = {
  2457. 'device': device.pk,
  2458. 'name': 'Interface X',
  2459. 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
  2460. 'enabled': False,
  2461. 'bridge': interfaces[4].pk,
  2462. 'lag': interfaces[3].pk,
  2463. 'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
  2464. 'mtu': 65000,
  2465. 'speed': 1000000,
  2466. 'duplex': 'full',
  2467. 'mgmt_only': True,
  2468. 'description': 'A front port',
  2469. 'mode': InterfaceModeChoices.MODE_TAGGED,
  2470. 'tx_power': 10,
  2471. 'poe_mode': InterfacePoEModeChoices.MODE_PSE,
  2472. 'poe_type': InterfacePoETypeChoices.TYPE_1_8023AF,
  2473. 'untagged_vlan': vlans[0].pk,
  2474. 'tagged_vlans': [v.pk for v in vlans[1:4]],
  2475. 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
  2476. 'vrf': vrfs[0].pk,
  2477. 'tags': [t.pk for t in tags],
  2478. }
  2479. cls.bulk_create_data = {
  2480. 'device': device.pk,
  2481. 'name': 'Interface [4-6]',
  2482. 'type': InterfaceTypeChoices.TYPE_1GE_GBIC,
  2483. 'enabled': False,
  2484. 'bridge': interfaces[4].pk,
  2485. 'lag': interfaces[3].pk,
  2486. 'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
  2487. 'mtu': 2000,
  2488. 'speed': 100000,
  2489. 'duplex': 'half',
  2490. 'mgmt_only': True,
  2491. 'description': 'A front port',
  2492. 'poe_mode': InterfacePoEModeChoices.MODE_PSE,
  2493. 'poe_type': InterfacePoETypeChoices.TYPE_1_8023AF,
  2494. 'mode': InterfaceModeChoices.MODE_TAGGED,
  2495. 'untagged_vlan': vlans[0].pk,
  2496. 'tagged_vlans': [v.pk for v in vlans[1:4]],
  2497. 'wireless_lans': [wireless_lans[0].pk, wireless_lans[1].pk],
  2498. 'vrf': vrfs[0].pk,
  2499. 'tags': [t.pk for t in tags],
  2500. }
  2501. cls.bulk_edit_data = {
  2502. 'type': InterfaceTypeChoices.TYPE_1GE_FIXED,
  2503. 'enabled': True,
  2504. 'lag': interfaces[3].pk,
  2505. 'wwn': EUI('01:02:03:04:05:06:07:08', version=64),
  2506. 'mtu': 2000,
  2507. 'speed': 1000000,
  2508. 'duplex': 'full',
  2509. 'mgmt_only': True,
  2510. 'description': 'New description',
  2511. 'poe_mode': InterfacePoEModeChoices.MODE_PD,
  2512. 'poe_type': InterfacePoETypeChoices.TYPE_2_8023AT,
  2513. 'mode': InterfaceModeChoices.MODE_TAGGED,
  2514. 'tx_power': 10,
  2515. 'untagged_vlan': vlans[0].pk,
  2516. 'tagged_vlans': [v.pk for v in vlans[1:4]],
  2517. 'vrf': vrfs[1].pk,
  2518. }
  2519. cls.csv_data = (
  2520. "device,name,type,vrf.pk,poe_mode,poe_type,mode,untagged_vlan,tagged_vlans",
  2521. (
  2522. f"Device 1,Interface 4,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
  2523. f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
  2524. ),
  2525. (
  2526. f"Device 1,Interface 5,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
  2527. f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
  2528. ),
  2529. (
  2530. f"Device 1,Interface 6,1000base-t,{vrfs[0].pk},pse,type1-ieee802.3af,"
  2531. f"tagged,{vlans[0].vid},'{','.join([str(v.vid) for v in vlans[1:4]])}'"
  2532. ),
  2533. )
  2534. cls.csv_update_data = (
  2535. "id,name,description",
  2536. f"{interfaces[0].pk},Interface 7,New description7",
  2537. f"{interfaces[1].pk},Interface 8,New description8",
  2538. f"{interfaces[2].pk},Interface 9,New description9",
  2539. )
  2540. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2541. def test_trace(self):
  2542. interface1, interface2 = Interface.objects.all()[:2]
  2543. Cable(a_terminations=[interface1], b_terminations=[interface2]).save()
  2544. response = self.client.get(reverse('dcim:interface_trace', kwargs={'pk': interface1.pk}))
  2545. self.assertHttpStatus(response, 200)
  2546. def test_bulk_delete_child_interfaces(self):
  2547. interface1 = Interface.objects.get(name='Interface 1')
  2548. device = interface1.device
  2549. self.add_permissions('dcim.delete_interface')
  2550. # Create a child interface
  2551. child = Interface.objects.create(
  2552. device=device,
  2553. name='Interface 1A',
  2554. type=InterfaceTypeChoices.TYPE_VIRTUAL,
  2555. parent=interface1
  2556. )
  2557. self.assertEqual(device.interfaces.count(), 6)
  2558. # Attempt to delete only the parent interface
  2559. data = {
  2560. 'confirm': True,
  2561. }
  2562. self.client.post(self._get_url('delete', interface1), data)
  2563. self.assertEqual(device.interfaces.count(), 6) # Parent was not deleted
  2564. # Attempt to bulk delete parent & child together
  2565. data = {
  2566. 'pk': [interface1.pk, child.pk],
  2567. 'confirm': True,
  2568. '_confirm': True, # Form button
  2569. }
  2570. self.client.post(self._get_url('bulk_delete'), data)
  2571. self.assertEqual(device.interfaces.count(), 4) # Child & parent were both deleted
  2572. def test_rename_select_all_spans_pages(self):
  2573. """
  2574. Tests the bulk rename functionality for interfaces spanning multiple pages in the UI.
  2575. """
  2576. device_name = 'DeviceRename'
  2577. device = create_test_device(device_name)
  2578. # Create > default page size (25) so selection spans multiple pages
  2579. for i in range(37):
  2580. Interface.objects.create(device=device, name=f'eth{i}')
  2581. self.add_permissions('dcim.change_interface')
  2582. # Filter to this device's interfaces to simulate a real list filter
  2583. get_qs = {'device_id': Device.objects.get(name=device_name).pk}
  2584. post_url = f'{self._get_url("bulk_rename")}?device_id={get_qs["device_id"]}'
  2585. # Preview step: ensure 37 selected (not just one page)
  2586. data = {'_preview': '1', '_all': '1', 'find': 'eth', 'replace': 'xe'}
  2587. response = self.client.post(post_url, data=data)
  2588. self.assertHttpStatus(response, 200)
  2589. self.assertEqual(len(response.context['selected_objects']), 37)
  2590. # Extract pk[] just like the browser would submit on Apply
  2591. # (either from the form's initial, or from selected_objects)
  2592. pk_list = response.context['form'].initial.get('pk')
  2593. if not pk_list:
  2594. pk_list = [obj.pk for obj in response.context['selected_objects']]
  2595. pk_list = [str(pk) for pk in pk_list]
  2596. # Apply step: include pk[] in the POST
  2597. apply_data = {'_apply': '1', '_all': '1', 'find': 'eth', 'replace': 'xe', 'pk': pk_list}
  2598. response = self.client.post(post_url, data=apply_data)
  2599. # On success the view redirects back to the return URL
  2600. self.assertHttpStatus(response, 302)
  2601. self.assertEqual(Interface.objects.filter(device=device, name__startswith='xe').count(), 37)
  2602. class FrontPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2603. model = FrontPort
  2604. validation_excluded_fields = ('name', 'label', 'rear_port')
  2605. @classmethod
  2606. def setUpTestData(cls):
  2607. device = create_test_device('Device 1')
  2608. rearports = (
  2609. RearPort(device=device, name='Rear Port 1'),
  2610. RearPort(device=device, name='Rear Port 2'),
  2611. RearPort(device=device, name='Rear Port 3'),
  2612. RearPort(device=device, name='Rear Port 4'),
  2613. RearPort(device=device, name='Rear Port 5'),
  2614. RearPort(device=device, name='Rear Port 6'),
  2615. )
  2616. RearPort.objects.bulk_create(rearports)
  2617. front_ports = (
  2618. FrontPort(device=device, name='Front Port 1', rear_port=rearports[0]),
  2619. FrontPort(device=device, name='Front Port 2', rear_port=rearports[1]),
  2620. FrontPort(device=device, name='Front Port 3', rear_port=rearports[2]),
  2621. )
  2622. FrontPort.objects.bulk_create(front_ports)
  2623. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2624. cls.form_data = {
  2625. 'device': device.pk,
  2626. 'name': 'Front Port X',
  2627. 'type': PortTypeChoices.TYPE_8P8C,
  2628. 'rear_port': rearports[3].pk,
  2629. 'rear_port_position': 1,
  2630. 'description': 'New description',
  2631. 'tags': [t.pk for t in tags],
  2632. }
  2633. cls.bulk_create_data = {
  2634. 'device': device.pk,
  2635. 'name': 'Front Port [4-6]',
  2636. 'type': PortTypeChoices.TYPE_8P8C,
  2637. 'rear_port': [f'{rp.pk}:1' for rp in rearports[3:6]],
  2638. 'description': 'New description',
  2639. 'tags': [t.pk for t in tags],
  2640. }
  2641. cls.bulk_edit_data = {
  2642. 'type': PortTypeChoices.TYPE_8P8C,
  2643. 'description': 'New description',
  2644. }
  2645. cls.csv_data = (
  2646. "device,name,type,rear_port,rear_port_position",
  2647. "Device 1,Front Port 4,8p8c,Rear Port 4,1",
  2648. "Device 1,Front Port 5,8p8c,Rear Port 5,1",
  2649. "Device 1,Front Port 6,8p8c,Rear Port 6,1",
  2650. )
  2651. cls.csv_update_data = (
  2652. "id,name,description",
  2653. f"{front_ports[0].pk},Front Port 7,New description7",
  2654. f"{front_ports[1].pk},Front Port 8,New description8",
  2655. f"{front_ports[2].pk},Front Port 9,New description9",
  2656. )
  2657. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2658. def test_trace(self):
  2659. frontport = FrontPort.objects.first()
  2660. interface = Interface.objects.create(
  2661. device=frontport.device,
  2662. name='Interface 1'
  2663. )
  2664. Cable(a_terminations=[frontport], b_terminations=[interface]).save()
  2665. response = self.client.get(reverse('dcim:frontport_trace', kwargs={'pk': frontport.pk}))
  2666. self.assertHttpStatus(response, 200)
  2667. class RearPortTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2668. model = RearPort
  2669. validation_excluded_fields = ('name', 'label')
  2670. @classmethod
  2671. def setUpTestData(cls):
  2672. device = create_test_device('Device 1')
  2673. rear_ports = (
  2674. RearPort(device=device, name='Rear Port 1'),
  2675. RearPort(device=device, name='Rear Port 2'),
  2676. RearPort(device=device, name='Rear Port 3'),
  2677. )
  2678. RearPort.objects.bulk_create(rear_ports)
  2679. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2680. cls.form_data = {
  2681. 'device': device.pk,
  2682. 'name': 'Rear Port X',
  2683. 'type': PortTypeChoices.TYPE_8P8C,
  2684. 'positions': 3,
  2685. 'description': 'A rear port',
  2686. 'tags': [t.pk for t in tags],
  2687. }
  2688. cls.bulk_create_data = {
  2689. 'device': device.pk,
  2690. 'name': 'Rear Port [4-6]',
  2691. 'type': PortTypeChoices.TYPE_8P8C,
  2692. 'positions': 3,
  2693. 'description': 'A rear port',
  2694. 'tags': [t.pk for t in tags],
  2695. }
  2696. cls.bulk_edit_data = {
  2697. 'type': PortTypeChoices.TYPE_8P8C,
  2698. 'description': 'New description',
  2699. }
  2700. cls.csv_data = (
  2701. "device,name,type,positions",
  2702. "Device 1,Rear Port 4,8p8c,1",
  2703. "Device 1,Rear Port 5,8p8c,1",
  2704. "Device 1,Rear Port 6,8p8c,1",
  2705. )
  2706. cls.csv_update_data = (
  2707. "id,name,description",
  2708. f"{rear_ports[0].pk},Rear Port 7,New description7",
  2709. f"{rear_ports[1].pk},Rear Port 8,New description8",
  2710. f"{rear_ports[2].pk},Rear Port 9,New description9",
  2711. )
  2712. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  2713. def test_trace(self):
  2714. rearport = RearPort.objects.first()
  2715. interface = Interface.objects.create(
  2716. device=rearport.device,
  2717. name='Interface 1'
  2718. )
  2719. Cable(a_terminations=[rearport], b_terminations=[interface]).save()
  2720. response = self.client.get(reverse('dcim:rearport_trace', kwargs={'pk': rearport.pk}))
  2721. self.assertHttpStatus(response, 200)
  2722. class ModuleBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2723. model = ModuleBay
  2724. validation_excluded_fields = ('name', 'label')
  2725. @classmethod
  2726. def setUpTestData(cls):
  2727. device = create_test_device('Device 1')
  2728. module_bays = (
  2729. ModuleBay(device=device, name='Module Bay 1'),
  2730. ModuleBay(device=device, name='Module Bay 2'),
  2731. ModuleBay(device=device, name='Module Bay 3'),
  2732. )
  2733. for module_bay in module_bays:
  2734. module_bay.save()
  2735. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2736. cls.form_data = {
  2737. 'device': device.pk,
  2738. 'name': 'Module Bay X',
  2739. 'description': 'A device bay',
  2740. 'tags': [t.pk for t in tags],
  2741. }
  2742. cls.bulk_create_data = {
  2743. 'device': device.pk,
  2744. 'name': 'Module Bay [4-6]',
  2745. 'description': 'A module bay',
  2746. 'tags': [t.pk for t in tags],
  2747. }
  2748. cls.bulk_edit_data = {
  2749. 'description': 'New description',
  2750. }
  2751. cls.csv_data = (
  2752. "device,name",
  2753. "Device 1,Module Bay 4",
  2754. "Device 1,Module Bay 5",
  2755. "Device 1,Module Bay 6",
  2756. )
  2757. cls.csv_update_data = (
  2758. "id,name,description",
  2759. f"{module_bays[0].pk},Module Bay 7,New description7",
  2760. f"{module_bays[1].pk},Module Bay 8,New description8",
  2761. f"{module_bays[2].pk},Module Bay 9,New description9",
  2762. )
  2763. class DeviceBayTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2764. model = DeviceBay
  2765. validation_excluded_fields = ('name', 'label')
  2766. @classmethod
  2767. def setUpTestData(cls):
  2768. device = create_test_device('Device 1')
  2769. # Update the DeviceType subdevice role to allow adding DeviceBays
  2770. DeviceType.objects.update(subdevice_role=SubdeviceRoleChoices.ROLE_PARENT)
  2771. device_bays = (
  2772. DeviceBay(device=device, name='Device Bay 1'),
  2773. DeviceBay(device=device, name='Device Bay 2'),
  2774. DeviceBay(device=device, name='Device Bay 3'),
  2775. )
  2776. DeviceBay.objects.bulk_create(device_bays)
  2777. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2778. cls.form_data = {
  2779. 'device': device.pk,
  2780. 'name': 'Device Bay X',
  2781. 'description': 'A device bay',
  2782. 'tags': [t.pk for t in tags],
  2783. }
  2784. cls.bulk_create_data = {
  2785. 'device': device.pk,
  2786. 'name': 'Device Bay [4-6]',
  2787. 'description': 'A device bay',
  2788. 'tags': [t.pk for t in tags],
  2789. }
  2790. cls.bulk_edit_data = {
  2791. 'description': 'New description',
  2792. }
  2793. cls.csv_data = (
  2794. "device,name",
  2795. "Device 1,Device Bay 4",
  2796. "Device 1,Device Bay 5",
  2797. "Device 1,Device Bay 6",
  2798. )
  2799. cls.csv_update_data = (
  2800. "id,name,description",
  2801. f"{device_bays[0].pk},Device Bay 7,New description7",
  2802. f"{device_bays[1].pk},Device Bay 8,New description8",
  2803. f"{device_bays[2].pk},Device Bay 9,New description9",
  2804. )
  2805. class InventoryItemTestCase(ViewTestCases.DeviceComponentViewTestCase):
  2806. model = InventoryItem
  2807. validation_excluded_fields = ('name', 'label')
  2808. @classmethod
  2809. def setUpTestData(cls):
  2810. device = create_test_device('Device 1')
  2811. manufacturer, _ = Manufacturer.objects.get_or_create(name='Manufacturer 1', slug='manufacturer-1')
  2812. roles = (
  2813. InventoryItemRole(name='Inventory Item Role 1', slug='inventory-item-role-1'),
  2814. InventoryItemRole(name='Inventory Item Role 2', slug='inventory-item-role-2'),
  2815. )
  2816. InventoryItemRole.objects.bulk_create(roles)
  2817. inventory_item1 = InventoryItem.objects.create(
  2818. device=device, name='Inventory Item 1', role=roles[0], manufacturer=manufacturer
  2819. )
  2820. inventory_item2 = InventoryItem.objects.create(
  2821. device=device, name='Inventory Item 2', role=roles[0], manufacturer=manufacturer
  2822. )
  2823. inventory_item3 = InventoryItem.objects.create(
  2824. device=device, name='Inventory Item 3', role=roles[0], manufacturer=manufacturer
  2825. )
  2826. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2827. cls.form_data = {
  2828. 'device': device.pk,
  2829. 'role': roles[1].pk,
  2830. 'manufacturer': manufacturer.pk,
  2831. 'name': 'Inventory Item X',
  2832. 'parent': None,
  2833. 'discovered': False,
  2834. 'part_id': '123456',
  2835. 'serial': '123ABC',
  2836. 'asset_tag': 'ABC123',
  2837. 'status': InventoryItemStatusChoices.STATUS_ACTIVE,
  2838. 'description': 'An inventory item',
  2839. 'tags': [t.pk for t in tags],
  2840. }
  2841. cls.bulk_create_data = {
  2842. 'device': device.pk,
  2843. 'name': 'Inventory Item [4-6]',
  2844. 'role': roles[1].pk,
  2845. 'manufacturer': manufacturer.pk,
  2846. 'parent': None,
  2847. 'discovered': False,
  2848. 'part_id': '123456',
  2849. 'serial': '123ABC',
  2850. 'status': InventoryItemStatusChoices.STATUS_ACTIVE,
  2851. 'description': 'An inventory item',
  2852. 'tags': [t.pk for t in tags],
  2853. }
  2854. cls.bulk_edit_data = {
  2855. 'role': roles[1].pk,
  2856. 'part_id': '123456',
  2857. 'description': 'New description',
  2858. }
  2859. cls.csv_data = (
  2860. "device,name,parent,status",
  2861. "Device 1,Inventory Item 4,Inventory Item 1,active",
  2862. "Device 1,Inventory Item 5,Inventory Item 2,planned",
  2863. "Device 1,Inventory Item 6,Inventory Item 3,failed",
  2864. )
  2865. cls.csv_update_data = (
  2866. "id,name,description",
  2867. f"{inventory_item1.pk},Inventory Item 7,New description7",
  2868. f"{inventory_item2.pk},Inventory Item 8,New description8",
  2869. f"{inventory_item3.pk},Inventory Item 9,New description9",
  2870. )
  2871. class InventoryItemRoleTestCase(ViewTestCases.OrganizationalObjectViewTestCase):
  2872. model = InventoryItemRole
  2873. @classmethod
  2874. def setUpTestData(cls):
  2875. inventory_item_roles = (
  2876. InventoryItemRole(name='Inventory Item Role 1', slug='inventory-item-role-1'),
  2877. InventoryItemRole(name='Inventory Item Role 2', slug='inventory-item-role-2'),
  2878. InventoryItemRole(name='Inventory Item Role 3', slug='inventory-item-role-3'),
  2879. )
  2880. InventoryItemRole.objects.bulk_create(inventory_item_roles)
  2881. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2882. cls.form_data = {
  2883. 'name': 'Inventory Item Role X',
  2884. 'slug': 'inventory-item-role-x',
  2885. 'color': 'c0c0c0',
  2886. 'description': 'New inventory item role',
  2887. 'tags': [t.pk for t in tags],
  2888. }
  2889. cls.csv_data = (
  2890. "name,slug,color",
  2891. "Inventory Item Role 4,inventory-item-role-4,ff0000",
  2892. "Inventory Item Role 5,inventory-item-role-5,00ff00",
  2893. "Inventory Item Role 6,inventory-item-role-6,0000ff",
  2894. )
  2895. cls.csv_update_data = (
  2896. "id,name,description",
  2897. f"{inventory_item_roles[0].pk},Inventory Item Role 7,New description7",
  2898. f"{inventory_item_roles[1].pk},Inventory Item Role 8,New description8",
  2899. f"{inventory_item_roles[2].pk},Inventory Item Role 9,New description9",
  2900. )
  2901. cls.bulk_edit_data = {
  2902. 'color': '00ff00',
  2903. 'description': 'New description',
  2904. }
  2905. # TODO: Change base class to PrimaryObjectViewTestCase
  2906. # Blocked by lack of common creation view for cables (termination A must be initialized)
  2907. class CableTestCase(
  2908. ViewTestCases.GetObjectViewTestCase,
  2909. ViewTestCases.GetObjectChangelogViewTestCase,
  2910. ViewTestCases.EditObjectViewTestCase,
  2911. ViewTestCases.DeleteObjectViewTestCase,
  2912. ViewTestCases.ListObjectsViewTestCase,
  2913. ViewTestCases.BulkImportObjectsViewTestCase,
  2914. ViewTestCases.BulkEditObjectsViewTestCase,
  2915. ViewTestCases.BulkDeleteObjectsViewTestCase
  2916. ):
  2917. model = Cable
  2918. @classmethod
  2919. def setUpTestData(cls):
  2920. sites = (
  2921. Site(name='Site 1', slug='site-1'),
  2922. Site(name='Site 2', slug='site-2'),
  2923. )
  2924. Site.objects.bulk_create(sites)
  2925. manufacturer = Manufacturer.objects.create(name='Manufacturer 1', slug='manufacturer-1')
  2926. devicetype = DeviceType.objects.create(model='Device Type 1', manufacturer=manufacturer)
  2927. role = DeviceRole.objects.create(name='Device Role 1', slug='device-role-1')
  2928. vc = VirtualChassis.objects.create(name='Virtual Chassis')
  2929. # NOTE: By design, NetBox now allows for the creation of devices with the same name if they belong to
  2930. # different sites.
  2931. # The CSV test below demonstrates that devices with identical names on different sites can be created
  2932. # and referenced successfully.
  2933. devices = (
  2934. # Create 'Device 1' assigned to 'Site 1'
  2935. Device(name='Device 1', site=sites[0], device_type=devicetype, role=role),
  2936. Device(name='Device 2', site=sites[0], device_type=devicetype, role=role),
  2937. Device(name='Device 3', site=sites[0], device_type=devicetype, role=role),
  2938. Device(name='Device 4', site=sites[0], device_type=devicetype, role=role),
  2939. # Create 'Device 1' assigned to 'Site 2' (allowed since the site is different)
  2940. Device(name='Device 1', site=sites[1], device_type=devicetype, role=role),
  2941. Device(name='Device 5', site=sites[1], device_type=devicetype, role=role),
  2942. )
  2943. Device.objects.bulk_create(devices)
  2944. vc.members.set((devices[0], devices[1], devices[2]))
  2945. vc.master = devices[0]
  2946. vc.save()
  2947. interfaces = (
  2948. # Device 1, Site 1
  2949. Interface(device=devices[0], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2950. Interface(device=devices[0], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2951. Interface(device=devices[0], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2952. # Device 2, Site 1
  2953. Interface(device=devices[1], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2954. Interface(device=devices[1], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2955. Interface(device=devices[1], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2956. # Device 3, Site 1
  2957. Interface(device=devices[2], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2958. Interface(device=devices[2], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2959. Interface(device=devices[2], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2960. # Device 3, Site 1
  2961. Interface(device=devices[3], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2962. Interface(device=devices[3], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2963. Interface(device=devices[3], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2964. # Device 1, Site 2
  2965. Interface(device=devices[4], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2966. Interface(device=devices[4], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2967. Interface(device=devices[4], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2968. # Device 1, Site 2
  2969. Interface(device=devices[5], name='Interface 1', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2970. Interface(device=devices[5], name='Interface 2', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2971. Interface(device=devices[5], name='Interface 3', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2972. Interface(device=devices[1], name='Device 2 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2973. Interface(device=devices[2], name='Device 3 Interface', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2974. Interface(device=devices[4], name='Interface 4', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2975. Interface(device=devices[4], name='Interface 5', type=InterfaceTypeChoices.TYPE_1GE_FIXED),
  2976. )
  2977. Interface.objects.bulk_create(interfaces)
  2978. cable1 = Cable(a_terminations=[interfaces[0]], b_terminations=[interfaces[3]], type=CableTypeChoices.TYPE_CAT6)
  2979. cable1.save()
  2980. cable2 = Cable(a_terminations=[interfaces[1]], b_terminations=[interfaces[4]], type=CableTypeChoices.TYPE_CAT6)
  2981. cable2.save()
  2982. cable3 = Cable(a_terminations=[interfaces[2]], b_terminations=[interfaces[5]], type=CableTypeChoices.TYPE_CAT6)
  2983. cable3.save()
  2984. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  2985. cls.form_data = {
  2986. # TODO: Revisit this limitation
  2987. # Changing terminations not supported when editing an existing Cable
  2988. 'a_terminations': [interfaces[0].pk],
  2989. 'b_terminations': [interfaces[3].pk],
  2990. 'type': CableTypeChoices.TYPE_CAT6,
  2991. 'status': LinkStatusChoices.STATUS_PLANNED,
  2992. 'label': 'Label',
  2993. 'color': 'c0c0c0',
  2994. 'length': 100,
  2995. 'length_unit': CableLengthUnitChoices.UNIT_FOOT,
  2996. 'tags': [t.pk for t in tags],
  2997. }
  2998. cls.csv_data = {
  2999. 'default': (
  3000. "side_a_device,side_a_type,side_a_name,side_b_device,side_b_type,side_b_name",
  3001. "Device 4,dcim.interface,Interface 1,Device 5,dcim.interface,Interface 1",
  3002. "Device 3,dcim.interface,Interface 2,Device 4,dcim.interface,Interface 2",
  3003. "Device 3,dcim.interface,Interface 3,Device 4,dcim.interface,Interface 3",
  3004. # The following is no longer possible in this scenario, because there are multiple
  3005. # devices named "Device 1" across multiple sites. See the "site-filtering" scenario
  3006. # below for how to specify a site for non-unique device names.
  3007. # "Device 1,dcim.interface,Device 3 Interface,Device 4,dcim.interface,Interface 5",
  3008. ),
  3009. 'site-filtering': (
  3010. # Ensure that CSV bulk import supports assigning terminations from parent devices
  3011. # that share the same device name, provided those devices belong to different sites.
  3012. "side_a_site,side_a_device,side_a_type,side_a_name,side_b_site,side_b_device,side_b_type,side_b_name",
  3013. "Site 1,Device 3,dcim.interface,Interface 1,Site 2,Device 1,dcim.interface,Interface 1",
  3014. "Site 1,Device 3,dcim.interface,Interface 2,Site 2,Device 1,dcim.interface,Interface 2",
  3015. "Site 1,Device 3,dcim.interface,Interface 3,Site 2,Device 1,dcim.interface,Interface 3",
  3016. "Site 1,Device 1,dcim.interface,Device 2 Interface,Site 2,Device 1,dcim.interface,Interface 4",
  3017. "Site 1,Device 1,dcim.interface,Device 3 Interface,Site 2,Device 1,dcim.interface,Interface 5",
  3018. )
  3019. }
  3020. cls.csv_update_data = (
  3021. "id,label,color",
  3022. f"{cable1.pk},New label7,00ff00",
  3023. f"{cable2.pk},New label8,00ff00",
  3024. f"{cable3.pk},New label9,00ff00",
  3025. )
  3026. cls.bulk_edit_data = {
  3027. 'type': CableTypeChoices.TYPE_CAT5E,
  3028. 'status': LinkStatusChoices.STATUS_CONNECTED,
  3029. 'label': 'New label',
  3030. 'color': '00ff00',
  3031. 'length': 50,
  3032. 'length_unit': CableLengthUnitChoices.UNIT_METER,
  3033. }
  3034. def model_to_dict(self, *args, **kwargs):
  3035. data = super().model_to_dict(*args, **kwargs)
  3036. # Serialize termination objects
  3037. if 'a_terminations' in data:
  3038. data['a_terminations'] = [obj.pk for obj in data['a_terminations']]
  3039. if 'b_terminations' in data:
  3040. data['b_terminations'] = [obj.pk for obj in data['b_terminations']]
  3041. return data
  3042. class VirtualChassisTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  3043. model = VirtualChassis
  3044. @classmethod
  3045. def setUpTestData(cls):
  3046. site = Site.objects.create(name='Site 1', slug='site-1')
  3047. manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer-1')
  3048. device_type = DeviceType.objects.create(
  3049. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  3050. )
  3051. role = DeviceRole.objects.create(
  3052. name='Device Role', slug='device-role-1'
  3053. )
  3054. devices = (
  3055. Device(device_type=device_type, role=role, name='Device 1', site=site),
  3056. Device(device_type=device_type, role=role, name='Device 2', site=site),
  3057. Device(device_type=device_type, role=role, name='Device 3', site=site),
  3058. Device(device_type=device_type, role=role, name='Device 4', site=site),
  3059. Device(device_type=device_type, role=role, name='Device 5', site=site),
  3060. Device(device_type=device_type, role=role, name='Device 6', site=site),
  3061. Device(device_type=device_type, role=role, name='Device 7', site=site),
  3062. Device(device_type=device_type, role=role, name='Device 8', site=site),
  3063. Device(device_type=device_type, role=role, name='Device 9', site=site),
  3064. Device(device_type=device_type, role=role, name='Device 10', site=site),
  3065. Device(device_type=device_type, role=role, name='Device 11', site=site),
  3066. Device(device_type=device_type, role=role, name='Device 12', site=site),
  3067. )
  3068. Device.objects.bulk_create(devices)
  3069. # Create three VirtualChassis with three members each
  3070. vc1 = VirtualChassis.objects.create(name='VC1', master=devices[0], domain='domain-1')
  3071. Device.objects.filter(pk=devices[0].pk).update(virtual_chassis=vc1, vc_position=1)
  3072. Device.objects.filter(pk=devices[1].pk).update(virtual_chassis=vc1, vc_position=2)
  3073. Device.objects.filter(pk=devices[2].pk).update(virtual_chassis=vc1, vc_position=3)
  3074. vc2 = VirtualChassis.objects.create(name='VC2', master=devices[3], domain='domain-2')
  3075. Device.objects.filter(pk=devices[3].pk).update(virtual_chassis=vc2, vc_position=1)
  3076. Device.objects.filter(pk=devices[4].pk).update(virtual_chassis=vc2, vc_position=2)
  3077. Device.objects.filter(pk=devices[5].pk).update(virtual_chassis=vc2, vc_position=3)
  3078. vc3 = VirtualChassis.objects.create(name='VC3', master=devices[6], domain='domain-3')
  3079. Device.objects.filter(pk=devices[6].pk).update(virtual_chassis=vc3, vc_position=1)
  3080. Device.objects.filter(pk=devices[7].pk).update(virtual_chassis=vc3, vc_position=2)
  3081. Device.objects.filter(pk=devices[8].pk).update(virtual_chassis=vc3, vc_position=3)
  3082. cls.form_data = {
  3083. 'name': 'VC4',
  3084. 'domain': 'domain-4',
  3085. # Management form data for VC members
  3086. 'form-TOTAL_FORMS': 0,
  3087. 'form-INITIAL_FORMS': 3,
  3088. 'form-MIN_NUM_FORMS': 0,
  3089. 'form-MAX_NUM_FORMS': 1000,
  3090. }
  3091. cls.csv_data = (
  3092. "name,domain,master",
  3093. "VC4,Domain 4,Device 10",
  3094. "VC5,Domain 5,Device 11",
  3095. "VC6,Domain 6,Device 12",
  3096. )
  3097. cls.csv_update_data = (
  3098. "id,name,domain",
  3099. f"{vc1.pk},VC7,Domain 7",
  3100. f"{vc2.pk},VC8,Domain 8",
  3101. f"{vc3.pk},VC9,Domain 9",
  3102. )
  3103. cls.bulk_edit_data = {
  3104. 'domain': 'domain-x',
  3105. }
  3106. class PowerPanelTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  3107. model = PowerPanel
  3108. @classmethod
  3109. def setUpTestData(cls):
  3110. sites = (
  3111. Site(name='Site 1', slug='site-1'),
  3112. Site(name='Site 2', slug='site-2'),
  3113. )
  3114. Site.objects.bulk_create(sites)
  3115. locations = (
  3116. Location(name='Location 1', slug='location-1', site=sites[0]),
  3117. Location(name='Location 2', slug='location-2', site=sites[1]),
  3118. )
  3119. for location in locations:
  3120. location.save()
  3121. power_panels = (
  3122. PowerPanel(site=sites[0], location=locations[0], name='Power Panel 1'),
  3123. PowerPanel(site=sites[0], location=locations[0], name='Power Panel 2'),
  3124. PowerPanel(site=sites[0], location=locations[0], name='Power Panel 3'),
  3125. )
  3126. PowerPanel.objects.bulk_create(power_panels)
  3127. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  3128. cls.form_data = {
  3129. 'site': sites[1].pk,
  3130. 'location': locations[1].pk,
  3131. 'name': 'Power Panel X',
  3132. 'tags': [t.pk for t in tags],
  3133. }
  3134. cls.csv_data = (
  3135. "site,location,name",
  3136. "Site 1,Location 1,Power Panel 4",
  3137. "Site 1,Location 1,Power Panel 5",
  3138. "Site 1,Location 1,Power Panel 6",
  3139. )
  3140. cls.csv_update_data = (
  3141. "id,name",
  3142. f"{power_panels[0].pk},Power Panel 7",
  3143. f"{power_panels[1].pk},Power Panel 8",
  3144. f"{power_panels[2].pk},Power Panel 9",
  3145. )
  3146. cls.bulk_edit_data = {
  3147. 'site': sites[1].pk,
  3148. 'location': locations[1].pk,
  3149. }
  3150. class PowerFeedTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  3151. model = PowerFeed
  3152. @classmethod
  3153. def setUpTestData(cls):
  3154. site = Site.objects.create(name='Site 1', slug='site-1')
  3155. powerpanels = (
  3156. PowerPanel(site=site, name='Power Panel 1'),
  3157. PowerPanel(site=site, name='Power Panel 2'),
  3158. )
  3159. PowerPanel.objects.bulk_create(powerpanels)
  3160. racks = (
  3161. Rack(site=site, name='Rack 1'),
  3162. Rack(site=site, name='Rack 2'),
  3163. )
  3164. Rack.objects.bulk_create(racks)
  3165. power_feeds = (
  3166. PowerFeed(name='Power Feed 1', power_panel=powerpanels[0], rack=racks[0]),
  3167. PowerFeed(name='Power Feed 2', power_panel=powerpanels[0], rack=racks[0]),
  3168. PowerFeed(name='Power Feed 3', power_panel=powerpanels[0], rack=racks[0]),
  3169. )
  3170. PowerFeed.objects.bulk_create(power_feeds)
  3171. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  3172. cls.form_data = {
  3173. 'name': 'Power Feed X',
  3174. 'power_panel': powerpanels[1].pk,
  3175. 'rack': racks[1].pk,
  3176. 'status': PowerFeedStatusChoices.STATUS_PLANNED,
  3177. 'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
  3178. 'supply': PowerFeedSupplyChoices.SUPPLY_DC,
  3179. 'phase': PowerFeedPhaseChoices.PHASE_3PHASE,
  3180. 'voltage': 100,
  3181. 'amperage': 100,
  3182. 'max_utilization': 50,
  3183. 'comments': 'New comments',
  3184. 'tags': [t.pk for t in tags],
  3185. }
  3186. cls.csv_data = (
  3187. "site,power_panel,name,status,type,supply,phase,voltage,amperage,max_utilization",
  3188. "Site 1,Power Panel 1,Power Feed 4,active,primary,ac,single-phase,120,20,80",
  3189. "Site 1,Power Panel 1,Power Feed 5,active,primary,ac,single-phase,120,20,80",
  3190. "Site 1,Power Panel 1,Power Feed 6,active,primary,ac,single-phase,120,20,80",
  3191. )
  3192. cls.csv_update_data = (
  3193. "id,name,status",
  3194. f"{power_feeds[0].pk},Power Feed 7,{PowerFeedStatusChoices.STATUS_PLANNED}",
  3195. f"{power_feeds[1].pk},Power Feed 8,{PowerFeedStatusChoices.STATUS_PLANNED}",
  3196. f"{power_feeds[2].pk},Power Feed 9,{PowerFeedStatusChoices.STATUS_PLANNED}",
  3197. )
  3198. cls.bulk_edit_data = {
  3199. 'power_panel': powerpanels[1].pk,
  3200. 'rack': racks[1].pk,
  3201. 'status': PowerFeedStatusChoices.STATUS_PLANNED,
  3202. 'type': PowerFeedTypeChoices.TYPE_REDUNDANT,
  3203. 'supply': PowerFeedSupplyChoices.SUPPLY_DC,
  3204. 'phase': PowerFeedPhaseChoices.PHASE_3PHASE,
  3205. 'voltage': 100,
  3206. 'amperage': 100,
  3207. 'max_utilization': 50,
  3208. 'comments': 'New comments',
  3209. }
  3210. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'])
  3211. def test_trace(self):
  3212. manufacturer = Manufacturer.objects.create(name='Manufacturer', slug='manufacturer-1')
  3213. device_type = DeviceType.objects.create(
  3214. manufacturer=manufacturer, model='Device Type 1', slug='device-type-1'
  3215. )
  3216. role = DeviceRole.objects.create(
  3217. name='Device Role', slug='device-role-1'
  3218. )
  3219. device = Device.objects.create(
  3220. site=Site.objects.first(), device_type=device_type, role=role
  3221. )
  3222. powerfeed = PowerFeed.objects.first()
  3223. powerport = PowerPort.objects.create(
  3224. device=device,
  3225. name='Power Port 1'
  3226. )
  3227. Cable(a_terminations=[powerfeed], b_terminations=[powerport]).save()
  3228. response = self.client.get(reverse('dcim:powerfeed_trace', kwargs={'pk': powerfeed.pk}))
  3229. self.assertHttpStatus(response, 200)
  3230. class VirtualDeviceContextTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  3231. model = VirtualDeviceContext
  3232. @classmethod
  3233. def setUpTestData(cls):
  3234. devices = [create_test_device(name='Device 1')]
  3235. vdcs = (
  3236. VirtualDeviceContext(name='VDC 1', identifier=1, device=devices[0], status='active'),
  3237. VirtualDeviceContext(name='VDC 2', identifier=2, device=devices[0], status='active'),
  3238. VirtualDeviceContext(name='VDC 3', identifier=3, device=devices[0], status='active'),
  3239. )
  3240. VirtualDeviceContext.objects.bulk_create(vdcs)
  3241. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  3242. cls.form_data = {
  3243. 'device': devices[0].pk,
  3244. 'status': 'active',
  3245. 'name': 'VDC 4',
  3246. 'identifier': 4,
  3247. 'primary_ip4': None,
  3248. 'primary_ip6': None,
  3249. 'tags': [t.pk for t in tags],
  3250. }
  3251. cls.csv_data = (
  3252. "device,status,name,identifier",
  3253. "Device 1,active,VDC 5,5",
  3254. "Device 1,active,VDC 6,6",
  3255. "Device 1,active,VDC 7,7",
  3256. )
  3257. cls.csv_update_data = (
  3258. "id,status",
  3259. f"{vdcs[0].pk},{VirtualDeviceContextStatusChoices.STATUS_PLANNED}",
  3260. f"{vdcs[1].pk},{VirtualDeviceContextStatusChoices.STATUS_PLANNED}",
  3261. f"{vdcs[2].pk},{VirtualDeviceContextStatusChoices.STATUS_PLANNED}",
  3262. )
  3263. cls.bulk_edit_data = {
  3264. 'status': VirtualDeviceContextStatusChoices.STATUS_OFFLINE,
  3265. }
  3266. class MACAddressTestCase(ViewTestCases.PrimaryObjectViewTestCase):
  3267. model = MACAddress
  3268. @classmethod
  3269. def setUpTestData(cls):
  3270. device = create_test_device(name='Device 1')
  3271. interfaces = (
  3272. Interface(device=device, name='Interface 1', type='1000base-t'),
  3273. Interface(device=device, name='Interface 2', type='1000base-t'),
  3274. Interface(device=device, name='Interface 3', type='1000base-t'),
  3275. Interface(device=device, name='Interface 4', type='1000base-t'),
  3276. Interface(device=device, name='Interface 5', type='1000base-t'),
  3277. Interface(device=device, name='Interface 6', type='1000base-t'),
  3278. )
  3279. Interface.objects.bulk_create(interfaces)
  3280. mac_addresses = (
  3281. MACAddress(mac_address='00:00:00:00:00:01', assigned_object=interfaces[0]),
  3282. MACAddress(mac_address='00:00:00:00:00:02', assigned_object=interfaces[1]),
  3283. MACAddress(mac_address='00:00:00:00:00:03', assigned_object=interfaces[2]),
  3284. )
  3285. MACAddress.objects.bulk_create(mac_addresses)
  3286. tags = create_tags('Alpha', 'Bravo', 'Charlie')
  3287. cls.form_data = {
  3288. 'mac_address': EUI('00:00:00:00:00:04'),
  3289. 'description': 'New MAC address',
  3290. 'interface_id': interfaces[3].pk,
  3291. 'tags': [t.pk for t in tags],
  3292. }
  3293. cls.csv_data = (
  3294. "mac_address,device,interface",
  3295. "00:00:00:00:00:04,Device 1,Interface 4",
  3296. "00:00:00:00:00:05,Device 1,Interface 5",
  3297. "00:00:00:00:00:06,Device 1,Interface 6",
  3298. )
  3299. cls.csv_update_data = (
  3300. "id,mac_address",
  3301. f"{mac_addresses[0].pk},00:00:00:00:00:0a",
  3302. f"{mac_addresses[1].pk},00:00:00:00:00:0b",
  3303. f"{mac_addresses[2].pk},00:00:00:00:00:0c",
  3304. )
  3305. cls.bulk_edit_data = {
  3306. 'description': 'New description',
  3307. }
  3308. @tag('regression') # Issue #20542
  3309. @override_settings(EXEMPT_VIEW_PERMISSIONS=['*'], EXEMPT_EXCLUDE_MODELS=[])
  3310. def test_create_macaddress_via_quickadd(self):
  3311. """
  3312. Test creating a MAC address via quick-add modal (e.g., from Interface form).
  3313. Regression test for issue #20542 where form prefix was missing in POST handler.
  3314. """
  3315. obj_perm = ObjectPermission(name='Test permission', actions=['add'])
  3316. obj_perm.save()
  3317. obj_perm.users.add(self.user)
  3318. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  3319. # Simulate quick-add form submission with 'quickadd-' prefix
  3320. formatted_data = post_data(self.form_data)
  3321. quickadd_data = {f'quickadd-{k}': v for k, v in formatted_data.items()}
  3322. quickadd_data['_quickadd'] = 'True'
  3323. initial_count = self._get_queryset().count()
  3324. url = f"{self._get_url('add')}?_quickadd=True&target=id_primary_mac_address"
  3325. response = self.client.post(url, data=quickadd_data)
  3326. # Should successfully create the MAC address and return the quick_add_created template
  3327. self.assertHttpStatus(response, 200)
  3328. self.assertIn(b'quick-add-object', response.content)
  3329. self.assertEqual(initial_count + 1, self._get_queryset().count())