views.py 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375
  1. from collections import OrderedDict
  2. import re
  3. from django.conf import settings
  4. from django.contrib import messages
  5. from django.contrib.auth.mixins import PermissionRequiredMixin
  6. from django.contrib.contenttypes.models import ContentType
  7. from django.core.paginator import EmptyPage, PageNotAnInteger
  8. from django.db import transaction
  9. from django.db.models import Count, F
  10. from django.forms import modelformset_factory
  11. from django.shortcuts import get_object_or_404, redirect, render
  12. from django.urls import reverse
  13. from django.utils.html import escape
  14. from django.utils.http import is_safe_url
  15. from django.utils.safestring import mark_safe
  16. from django.views.generic import View
  17. from circuits.models import Circuit
  18. from extras.constants import GRAPH_TYPE_DEVICE, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
  19. from extras.models import Graph, TopologyMap
  20. from extras.views import ObjectConfigContextView
  21. from ipam.models import Prefix, VLAN
  22. from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
  23. from utilities.forms import ConfirmationForm
  24. from utilities.paginator import EnhancedPaginator
  25. from utilities.utils import csv_format
  26. from utilities.views import (
  27. BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin,
  28. ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
  29. )
  30. from virtualization.models import VirtualMachine
  31. from . import filters, forms, tables
  32. from .models import (
  33. Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
  34. DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
  35. InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
  36. PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
  37. VirtualChassis,
  38. )
  39. class BulkRenameView(GetReturnURLMixin, View):
  40. """
  41. An extendable view for renaming device components in bulk.
  42. """
  43. queryset = None
  44. form = None
  45. template_name = 'dcim/bulk_rename.html'
  46. def post(self, request):
  47. model = self.queryset.model
  48. if '_preview' in request.POST or '_apply' in request.POST:
  49. form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
  50. selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
  51. if form.is_valid():
  52. for obj in selected_objects:
  53. find = form.cleaned_data['find']
  54. replace = form.cleaned_data['replace']
  55. if form.cleaned_data['use_regex']:
  56. try:
  57. obj.new_name = re.sub(find, replace, obj.name)
  58. # Catch regex group reference errors
  59. except re.error:
  60. obj.new_name = obj.name
  61. else:
  62. obj.new_name = obj.name.replace(find, replace)
  63. if '_apply' in request.POST:
  64. for obj in selected_objects:
  65. obj.name = obj.new_name
  66. obj.save()
  67. messages.success(request, "Renamed {} {}".format(
  68. len(selected_objects),
  69. model._meta.verbose_name_plural
  70. ))
  71. return redirect(self.get_return_url(request))
  72. else:
  73. form = self.form(initial={'pk': request.POST.getlist('pk')})
  74. selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
  75. return render(request, self.template_name, {
  76. 'form': form,
  77. 'obj_type_plural': model._meta.verbose_name_plural,
  78. 'selected_objects': selected_objects,
  79. 'return_url': self.get_return_url(request),
  80. })
  81. class BulkDisconnectView(GetReturnURLMixin, View):
  82. """
  83. An extendable view for disconnection console/power/interface components in bulk.
  84. """
  85. model = None
  86. form = None
  87. template_name = 'dcim/bulk_disconnect.html'
  88. def post(self, request):
  89. selected_objects = []
  90. return_url = self.get_return_url(request)
  91. if '_confirm' in request.POST:
  92. form = self.form(request.POST)
  93. if form.is_valid():
  94. with transaction.atomic():
  95. count = 0
  96. for obj in self.model.objects.filter(pk__in=form.cleaned_data['pk']):
  97. if obj.cable is None:
  98. continue
  99. obj.cable.delete()
  100. count += 1
  101. messages.success(request, "Disconnected {} {}".format(
  102. count, self.model._meta.verbose_name_plural
  103. ))
  104. return redirect(return_url)
  105. else:
  106. form = self.form(initial={'pk': request.POST.getlist('pk')})
  107. selected_objects = self.model.objects.filter(pk__in=form.initial['pk'])
  108. return render(request, self.template_name, {
  109. 'form': form,
  110. 'obj_type_plural': self.model._meta.verbose_name_plural,
  111. 'selected_objects': selected_objects,
  112. 'return_url': return_url,
  113. })
  114. #
  115. # Regions
  116. #
  117. class RegionListView(PermissionRequiredMixin, ObjectListView):
  118. permission_required = 'dcim.view_region'
  119. queryset = Region.objects.add_related_count(
  120. Region.objects.all(),
  121. Site,
  122. 'region',
  123. 'site_count',
  124. cumulative=True
  125. )
  126. filter = filters.RegionFilter
  127. filter_form = forms.RegionFilterForm
  128. table = tables.RegionTable
  129. template_name = 'dcim/region_list.html'
  130. class RegionCreateView(PermissionRequiredMixin, ObjectEditView):
  131. permission_required = 'dcim.add_region'
  132. model = Region
  133. model_form = forms.RegionForm
  134. default_return_url = 'dcim:region_list'
  135. class RegionEditView(RegionCreateView):
  136. permission_required = 'dcim.change_region'
  137. class RegionBulkImportView(PermissionRequiredMixin, BulkImportView):
  138. permission_required = 'dcim.add_region'
  139. model_form = forms.RegionCSVForm
  140. table = tables.RegionTable
  141. default_return_url = 'dcim:region_list'
  142. class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  143. permission_required = 'dcim.delete_region'
  144. queryset = Region.objects.all()
  145. filter = filters.RegionFilter
  146. table = tables.RegionTable
  147. default_return_url = 'dcim:region_list'
  148. #
  149. # Sites
  150. #
  151. class SiteListView(PermissionRequiredMixin, ObjectListView):
  152. permission_required = 'dcim.view_site'
  153. queryset = Site.objects.prefetch_related('region', 'tenant')
  154. filter = filters.SiteFilter
  155. filter_form = forms.SiteFilterForm
  156. table = tables.SiteTable
  157. template_name = 'dcim/site_list.html'
  158. class SiteView(PermissionRequiredMixin, View):
  159. permission_required = 'dcim.view_site'
  160. def get(self, request, slug):
  161. site = get_object_or_404(Site.objects.prefetch_related('region', 'tenant__group'), slug=slug)
  162. stats = {
  163. 'rack_count': Rack.objects.filter(site=site).count(),
  164. 'device_count': Device.objects.filter(site=site).count(),
  165. 'prefix_count': Prefix.objects.filter(site=site).count(),
  166. 'vlan_count': VLAN.objects.filter(site=site).count(),
  167. 'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
  168. 'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(),
  169. }
  170. rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
  171. topology_maps = TopologyMap.objects.filter(site=site)
  172. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists()
  173. return render(request, 'dcim/site.html', {
  174. 'site': site,
  175. 'stats': stats,
  176. 'rack_groups': rack_groups,
  177. 'topology_maps': topology_maps,
  178. 'show_graphs': show_graphs,
  179. })
  180. class SiteCreateView(PermissionRequiredMixin, ObjectEditView):
  181. permission_required = 'dcim.add_site'
  182. model = Site
  183. model_form = forms.SiteForm
  184. template_name = 'dcim/site_edit.html'
  185. default_return_url = 'dcim:site_list'
  186. class SiteEditView(SiteCreateView):
  187. permission_required = 'dcim.change_site'
  188. class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  189. permission_required = 'dcim.delete_site'
  190. model = Site
  191. default_return_url = 'dcim:site_list'
  192. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  193. permission_required = 'dcim.add_site'
  194. model_form = forms.SiteCSVForm
  195. table = tables.SiteTable
  196. default_return_url = 'dcim:site_list'
  197. class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
  198. permission_required = 'dcim.change_site'
  199. queryset = Site.objects.prefetch_related('region', 'tenant')
  200. filter = filters.SiteFilter
  201. table = tables.SiteTable
  202. form = forms.SiteBulkEditForm
  203. default_return_url = 'dcim:site_list'
  204. class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  205. permission_required = 'dcim.delete_site'
  206. queryset = Site.objects.prefetch_related('region', 'tenant')
  207. filter = filters.SiteFilter
  208. table = tables.SiteTable
  209. default_return_url = 'dcim:site_list'
  210. #
  211. # Rack groups
  212. #
  213. class RackGroupListView(PermissionRequiredMixin, ObjectListView):
  214. permission_required = 'dcim.view_rackgroup'
  215. queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
  216. filter = filters.RackGroupFilter
  217. filter_form = forms.RackGroupFilterForm
  218. table = tables.RackGroupTable
  219. template_name = 'dcim/rackgroup_list.html'
  220. class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView):
  221. permission_required = 'dcim.add_rackgroup'
  222. model = RackGroup
  223. model_form = forms.RackGroupForm
  224. default_return_url = 'dcim:rackgroup_list'
  225. class RackGroupEditView(RackGroupCreateView):
  226. permission_required = 'dcim.change_rackgroup'
  227. class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
  228. permission_required = 'dcim.add_rackgroup'
  229. model_form = forms.RackGroupCSVForm
  230. table = tables.RackGroupTable
  231. default_return_url = 'dcim:rackgroup_list'
  232. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  233. permission_required = 'dcim.delete_rackgroup'
  234. queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
  235. filter = filters.RackGroupFilter
  236. table = tables.RackGroupTable
  237. default_return_url = 'dcim:rackgroup_list'
  238. #
  239. # Rack roles
  240. #
  241. class RackRoleListView(PermissionRequiredMixin, ObjectListView):
  242. permission_required = 'dcim.view_rackrole'
  243. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  244. table = tables.RackRoleTable
  245. template_name = 'dcim/rackrole_list.html'
  246. class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  247. permission_required = 'dcim.add_rackrole'
  248. model = RackRole
  249. model_form = forms.RackRoleForm
  250. default_return_url = 'dcim:rackrole_list'
  251. class RackRoleEditView(RackRoleCreateView):
  252. permission_required = 'dcim.change_rackrole'
  253. class RackRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  254. permission_required = 'dcim.add_rackrole'
  255. model_form = forms.RackRoleCSVForm
  256. table = tables.RackRoleTable
  257. default_return_url = 'dcim:rackrole_list'
  258. class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  259. permission_required = 'dcim.delete_rackrole'
  260. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  261. table = tables.RackRoleTable
  262. default_return_url = 'dcim:rackrole_list'
  263. #
  264. # Racks
  265. #
  266. class RackListView(PermissionRequiredMixin, ObjectListView):
  267. permission_required = 'dcim.view_rack'
  268. queryset = Rack.objects.prefetch_related(
  269. 'site', 'group', 'tenant', 'role', 'devices__device_type'
  270. ).annotate(
  271. device_count=Count('devices')
  272. )
  273. filter = filters.RackFilter
  274. filter_form = forms.RackFilterForm
  275. table = tables.RackDetailTable
  276. template_name = 'dcim/rack_list.html'
  277. class RackElevationListView(PermissionRequiredMixin, View):
  278. """
  279. Display a set of rack elevations side-by-side.
  280. """
  281. permission_required = 'dcim.view_rack'
  282. def get(self, request):
  283. racks = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role', 'devices__device_type')
  284. racks = filters.RackFilter(request.GET, racks).qs
  285. total_count = racks.count()
  286. # Pagination
  287. per_page = request.GET.get('per_page', settings.PAGINATE_COUNT)
  288. page_number = request.GET.get('page', 1)
  289. paginator = EnhancedPaginator(racks, per_page)
  290. try:
  291. page = paginator.page(page_number)
  292. except PageNotAnInteger:
  293. page = paginator.page(1)
  294. except EmptyPage:
  295. page = paginator.page(paginator.num_pages)
  296. # Determine rack face
  297. if request.GET.get('face') == '1':
  298. face_id = 1
  299. else:
  300. face_id = 0
  301. return render(request, 'dcim/rack_elevation_list.html', {
  302. 'paginator': paginator,
  303. 'page': page,
  304. 'total_count': total_count,
  305. 'face_id': face_id,
  306. 'filter_form': forms.RackFilterForm(request.GET),
  307. })
  308. class RackView(PermissionRequiredMixin, View):
  309. permission_required = 'dcim.view_rack'
  310. def get(self, request, pk):
  311. rack = get_object_or_404(Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
  312. nonracked_devices = Device.objects.filter(
  313. rack=rack,
  314. position__isnull=True,
  315. parent_bay__isnull=True
  316. ).prefetch_related('device_type__manufacturer')
  317. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  318. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  319. reservations = RackReservation.objects.filter(rack=rack)
  320. power_feeds = PowerFeed.objects.filter(rack=rack).prefetch_related('power_panel')
  321. return render(request, 'dcim/rack.html', {
  322. 'rack': rack,
  323. 'reservations': reservations,
  324. 'power_feeds': power_feeds,
  325. 'nonracked_devices': nonracked_devices,
  326. 'next_rack': next_rack,
  327. 'prev_rack': prev_rack,
  328. 'front_elevation': rack.get_front_elevation(),
  329. 'rear_elevation': rack.get_rear_elevation(),
  330. })
  331. class RackCreateView(PermissionRequiredMixin, ObjectEditView):
  332. permission_required = 'dcim.add_rack'
  333. model = Rack
  334. model_form = forms.RackForm
  335. template_name = 'dcim/rack_edit.html'
  336. default_return_url = 'dcim:rack_list'
  337. class RackEditView(RackCreateView):
  338. permission_required = 'dcim.change_rack'
  339. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  340. permission_required = 'dcim.delete_rack'
  341. model = Rack
  342. default_return_url = 'dcim:rack_list'
  343. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  344. permission_required = 'dcim.add_rack'
  345. model_form = forms.RackCSVForm
  346. table = tables.RackTable
  347. default_return_url = 'dcim:rack_list'
  348. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  349. permission_required = 'dcim.change_rack'
  350. queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
  351. filter = filters.RackFilter
  352. table = tables.RackTable
  353. form = forms.RackBulkEditForm
  354. default_return_url = 'dcim:rack_list'
  355. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  356. permission_required = 'dcim.delete_rack'
  357. queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
  358. filter = filters.RackFilter
  359. table = tables.RackTable
  360. default_return_url = 'dcim:rack_list'
  361. #
  362. # Rack reservations
  363. #
  364. class RackReservationListView(PermissionRequiredMixin, ObjectListView):
  365. permission_required = 'dcim.view_rackreservation'
  366. queryset = RackReservation.objects.prefetch_related('rack__site')
  367. filter = filters.RackReservationFilter
  368. filter_form = forms.RackReservationFilterForm
  369. table = tables.RackReservationTable
  370. template_name = 'dcim/rackreservation_list.html'
  371. class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView):
  372. permission_required = 'dcim.add_rackreservation'
  373. model = RackReservation
  374. model_form = forms.RackReservationForm
  375. def alter_obj(self, obj, request, args, kwargs):
  376. if not obj.pk:
  377. obj.rack = get_object_or_404(Rack, pk=kwargs['rack'])
  378. obj.user = request.user
  379. return obj
  380. def get_return_url(self, request, obj):
  381. return obj.rack.get_absolute_url()
  382. class RackReservationEditView(RackReservationCreateView):
  383. permission_required = 'dcim.change_rackreservation'
  384. class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  385. permission_required = 'dcim.delete_rackreservation'
  386. model = RackReservation
  387. def get_return_url(self, request, obj):
  388. return obj.rack.get_absolute_url()
  389. class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
  390. permission_required = 'dcim.change_rackreservation'
  391. queryset = RackReservation.objects.prefetch_related('rack', 'user')
  392. filter = filters.RackReservationFilter
  393. table = tables.RackReservationTable
  394. form = forms.RackReservationBulkEditForm
  395. default_return_url = 'dcim:rackreservation_list'
  396. class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  397. permission_required = 'dcim.delete_rackreservation'
  398. queryset = RackReservation.objects.prefetch_related('rack', 'user')
  399. filter = filters.RackReservationFilter
  400. table = tables.RackReservationTable
  401. default_return_url = 'dcim:rackreservation_list'
  402. #
  403. # Manufacturers
  404. #
  405. class ManufacturerListView(PermissionRequiredMixin, ObjectListView):
  406. permission_required = 'dcim.view_manufacturer'
  407. queryset = Manufacturer.objects.annotate(
  408. devicetype_count=Count('device_types', distinct=True),
  409. inventoryitem_count=Count('inventory_items', distinct=True),
  410. platform_count=Count('platforms', distinct=True),
  411. )
  412. table = tables.ManufacturerTable
  413. template_name = 'dcim/manufacturer_list.html'
  414. class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView):
  415. permission_required = 'dcim.add_manufacturer'
  416. model = Manufacturer
  417. model_form = forms.ManufacturerForm
  418. default_return_url = 'dcim:manufacturer_list'
  419. class ManufacturerEditView(ManufacturerCreateView):
  420. permission_required = 'dcim.change_manufacturer'
  421. class ManufacturerBulkImportView(PermissionRequiredMixin, BulkImportView):
  422. permission_required = 'dcim.add_manufacturer'
  423. model_form = forms.ManufacturerCSVForm
  424. table = tables.ManufacturerTable
  425. default_return_url = 'dcim:manufacturer_list'
  426. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  427. permission_required = 'dcim.delete_manufacturer'
  428. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  429. table = tables.ManufacturerTable
  430. default_return_url = 'dcim:manufacturer_list'
  431. #
  432. # Device types
  433. #
  434. class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
  435. permission_required = 'dcim.view_devicetype'
  436. queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
  437. filter = filters.DeviceTypeFilter
  438. filter_form = forms.DeviceTypeFilterForm
  439. table = tables.DeviceTypeTable
  440. template_name = 'dcim/devicetype_list.html'
  441. class DeviceTypeView(PermissionRequiredMixin, View):
  442. permission_required = 'dcim.view_devicetype'
  443. def get(self, request, pk):
  444. devicetype = get_object_or_404(DeviceType, pk=pk)
  445. # Component tables
  446. consoleport_table = tables.ConsolePortTemplateTable(
  447. ConsolePortTemplate.objects.filter(device_type=devicetype),
  448. orderable=False
  449. )
  450. consoleserverport_table = tables.ConsoleServerPortTemplateTable(
  451. ConsoleServerPortTemplate.objects.filter(device_type=devicetype),
  452. orderable=False
  453. )
  454. powerport_table = tables.PowerPortTemplateTable(
  455. PowerPortTemplate.objects.filter(device_type=devicetype),
  456. orderable=False
  457. )
  458. poweroutlet_table = tables.PowerOutletTemplateTable(
  459. PowerOutletTemplate.objects.filter(device_type=devicetype),
  460. orderable=False
  461. )
  462. interface_table = tables.InterfaceTemplateTable(
  463. list(InterfaceTemplate.objects.filter(device_type=devicetype)),
  464. orderable=False
  465. )
  466. front_port_table = tables.FrontPortTemplateTable(
  467. FrontPortTemplate.objects.filter(device_type=devicetype),
  468. orderable=False
  469. )
  470. rear_port_table = tables.RearPortTemplateTable(
  471. RearPortTemplate.objects.filter(device_type=devicetype),
  472. orderable=False
  473. )
  474. devicebay_table = tables.DeviceBayTemplateTable(
  475. DeviceBayTemplate.objects.filter(device_type=devicetype),
  476. orderable=False
  477. )
  478. if request.user.has_perm('dcim.change_devicetype'):
  479. consoleport_table.columns.show('pk')
  480. consoleserverport_table.columns.show('pk')
  481. powerport_table.columns.show('pk')
  482. poweroutlet_table.columns.show('pk')
  483. interface_table.columns.show('pk')
  484. front_port_table.columns.show('pk')
  485. rear_port_table.columns.show('pk')
  486. devicebay_table.columns.show('pk')
  487. return render(request, 'dcim/devicetype.html', {
  488. 'devicetype': devicetype,
  489. 'consoleport_table': consoleport_table,
  490. 'consoleserverport_table': consoleserverport_table,
  491. 'powerport_table': powerport_table,
  492. 'poweroutlet_table': poweroutlet_table,
  493. 'interface_table': interface_table,
  494. 'front_port_table': front_port_table,
  495. 'rear_port_table': rear_port_table,
  496. 'devicebay_table': devicebay_table,
  497. })
  498. class DeviceTypeCreateView(PermissionRequiredMixin, ObjectEditView):
  499. permission_required = 'dcim.add_devicetype'
  500. model = DeviceType
  501. model_form = forms.DeviceTypeForm
  502. template_name = 'dcim/devicetype_edit.html'
  503. default_return_url = 'dcim:devicetype_list'
  504. class DeviceTypeEditView(DeviceTypeCreateView):
  505. permission_required = 'dcim.change_devicetype'
  506. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  507. permission_required = 'dcim.delete_devicetype'
  508. model = DeviceType
  509. default_return_url = 'dcim:devicetype_list'
  510. class DeviceTypeImportView(PermissionRequiredMixin, ObjectImportView):
  511. permission_required = 'dcim.add_devicetype'
  512. model = DeviceType
  513. model_form = forms.DeviceTypeImportForm
  514. related_object_forms = OrderedDict((
  515. ('console-ports', forms.ConsolePortTemplateImportForm),
  516. ('console-server-ports', forms.ConsoleServerPortTemplateImportForm),
  517. ('power-ports', forms.PowerPortTemplateImportForm),
  518. ('power-outlets', forms.PowerOutletTemplateImportForm),
  519. ('interfaces', forms.InterfaceTemplateImportForm),
  520. ('rear-ports', forms.RearPortTemplateImportForm),
  521. ('front-ports', forms.FrontPortTemplateImportForm),
  522. ('device-bays', forms.DeviceBayTemplateImportForm),
  523. ))
  524. default_return_url = 'dcim:devicetype_import'
  525. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  526. permission_required = 'dcim.change_devicetype'
  527. queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
  528. filter = filters.DeviceTypeFilter
  529. table = tables.DeviceTypeTable
  530. form = forms.DeviceTypeBulkEditForm
  531. default_return_url = 'dcim:devicetype_list'
  532. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  533. permission_required = 'dcim.delete_devicetype'
  534. queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
  535. filter = filters.DeviceTypeFilter
  536. table = tables.DeviceTypeTable
  537. default_return_url = 'dcim:devicetype_list'
  538. #
  539. # Device type components
  540. #
  541. class ConsolePortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  542. permission_required = 'dcim.add_consoleporttemplate'
  543. parent_model = DeviceType
  544. parent_field = 'device_type'
  545. model = ConsolePortTemplate
  546. form = forms.ConsolePortTemplateCreateForm
  547. model_form = forms.ConsolePortTemplateForm
  548. template_name = 'dcim/device_component_add.html'
  549. class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  550. permission_required = 'dcim.delete_consoleporttemplate'
  551. queryset = ConsolePortTemplate.objects.all()
  552. parent_model = DeviceType
  553. table = tables.ConsolePortTemplateTable
  554. class ConsoleServerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  555. permission_required = 'dcim.add_consoleserverporttemplate'
  556. parent_model = DeviceType
  557. parent_field = 'device_type'
  558. model = ConsoleServerPortTemplate
  559. form = forms.ConsoleServerPortTemplateCreateForm
  560. model_form = forms.ConsoleServerPortTemplateForm
  561. template_name = 'dcim/device_component_add.html'
  562. class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  563. permission_required = 'dcim.delete_consoleserverporttemplate'
  564. queryset = ConsoleServerPortTemplate.objects.all()
  565. parent_model = DeviceType
  566. table = tables.ConsoleServerPortTemplateTable
  567. class PowerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  568. permission_required = 'dcim.add_powerporttemplate'
  569. parent_model = DeviceType
  570. parent_field = 'device_type'
  571. model = PowerPortTemplate
  572. form = forms.PowerPortTemplateCreateForm
  573. model_form = forms.PowerPortTemplateForm
  574. template_name = 'dcim/device_component_add.html'
  575. class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  576. permission_required = 'dcim.delete_powerporttemplate'
  577. queryset = PowerPortTemplate.objects.all()
  578. parent_model = DeviceType
  579. table = tables.PowerPortTemplateTable
  580. class PowerOutletTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  581. permission_required = 'dcim.add_poweroutlettemplate'
  582. parent_model = DeviceType
  583. parent_field = 'device_type'
  584. model = PowerOutletTemplate
  585. form = forms.PowerOutletTemplateCreateForm
  586. model_form = forms.PowerOutletTemplateForm
  587. template_name = 'dcim/device_component_add.html'
  588. class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  589. permission_required = 'dcim.delete_poweroutlettemplate'
  590. queryset = PowerOutletTemplate.objects.all()
  591. parent_model = DeviceType
  592. table = tables.PowerOutletTemplateTable
  593. class InterfaceTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  594. permission_required = 'dcim.add_interfacetemplate'
  595. parent_model = DeviceType
  596. parent_field = 'device_type'
  597. model = InterfaceTemplate
  598. form = forms.InterfaceTemplateCreateForm
  599. model_form = forms.InterfaceTemplateForm
  600. template_name = 'dcim/device_component_add.html'
  601. class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  602. permission_required = 'dcim.change_interfacetemplate'
  603. queryset = InterfaceTemplate.objects.all()
  604. parent_model = DeviceType
  605. table = tables.InterfaceTemplateTable
  606. form = forms.InterfaceTemplateBulkEditForm
  607. class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  608. permission_required = 'dcim.delete_interfacetemplate'
  609. queryset = InterfaceTemplate.objects.all()
  610. parent_model = DeviceType
  611. table = tables.InterfaceTemplateTable
  612. class FrontPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  613. permission_required = 'dcim.add_frontporttemplate'
  614. parent_model = DeviceType
  615. parent_field = 'device_type'
  616. model = FrontPortTemplate
  617. form = forms.FrontPortTemplateCreateForm
  618. model_form = forms.FrontPortTemplateForm
  619. template_name = 'dcim/device_component_add.html'
  620. class FrontPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  621. permission_required = 'dcim.delete_frontporttemplate'
  622. queryset = FrontPortTemplate.objects.all()
  623. parent_model = DeviceType
  624. table = tables.FrontPortTemplateTable
  625. class RearPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  626. permission_required = 'dcim.add_rearporttemplate'
  627. parent_model = DeviceType
  628. parent_field = 'device_type'
  629. model = RearPortTemplate
  630. form = forms.RearPortTemplateCreateForm
  631. model_form = forms.RearPortTemplateForm
  632. template_name = 'dcim/device_component_add.html'
  633. class RearPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  634. permission_required = 'dcim.delete_rearporttemplate'
  635. queryset = RearPortTemplate.objects.all()
  636. parent_model = DeviceType
  637. table = tables.RearPortTemplateTable
  638. class DeviceBayTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  639. permission_required = 'dcim.add_devicebaytemplate'
  640. parent_model = DeviceType
  641. parent_field = 'device_type'
  642. model = DeviceBayTemplate
  643. form = forms.DeviceBayTemplateCreateForm
  644. model_form = forms.DeviceBayTemplateForm
  645. template_name = 'dcim/device_component_add.html'
  646. class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  647. permission_required = 'dcim.delete_devicebaytemplate'
  648. queryset = DeviceBayTemplate.objects.all()
  649. parent_model = DeviceType
  650. table = tables.DeviceBayTemplateTable
  651. #
  652. # Device roles
  653. #
  654. class DeviceRoleListView(PermissionRequiredMixin, ObjectListView):
  655. permission_required = 'dcim.view_devicerole'
  656. queryset = DeviceRole.objects.all()
  657. table = tables.DeviceRoleTable
  658. template_name = 'dcim/devicerole_list.html'
  659. class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  660. permission_required = 'dcim.add_devicerole'
  661. model = DeviceRole
  662. model_form = forms.DeviceRoleForm
  663. default_return_url = 'dcim:devicerole_list'
  664. class DeviceRoleEditView(DeviceRoleCreateView):
  665. permission_required = 'dcim.change_devicerole'
  666. class DeviceRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  667. permission_required = 'dcim.add_devicerole'
  668. model_form = forms.DeviceRoleCSVForm
  669. table = tables.DeviceRoleTable
  670. default_return_url = 'dcim:devicerole_list'
  671. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  672. permission_required = 'dcim.delete_devicerole'
  673. queryset = DeviceRole.objects.all()
  674. table = tables.DeviceRoleTable
  675. default_return_url = 'dcim:devicerole_list'
  676. #
  677. # Platforms
  678. #
  679. class PlatformListView(PermissionRequiredMixin, ObjectListView):
  680. permission_required = 'dcim.view_platform'
  681. queryset = Platform.objects.all()
  682. table = tables.PlatformTable
  683. template_name = 'dcim/platform_list.html'
  684. class PlatformCreateView(PermissionRequiredMixin, ObjectEditView):
  685. permission_required = 'dcim.add_platform'
  686. model = Platform
  687. model_form = forms.PlatformForm
  688. default_return_url = 'dcim:platform_list'
  689. class PlatformEditView(PlatformCreateView):
  690. permission_required = 'dcim.change_platform'
  691. class PlatformBulkImportView(PermissionRequiredMixin, BulkImportView):
  692. permission_required = 'dcim.add_platform'
  693. model_form = forms.PlatformCSVForm
  694. table = tables.PlatformTable
  695. default_return_url = 'dcim:platform_list'
  696. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  697. permission_required = 'dcim.delete_platform'
  698. queryset = Platform.objects.all()
  699. table = tables.PlatformTable
  700. default_return_url = 'dcim:platform_list'
  701. #
  702. # Devices
  703. #
  704. class DeviceListView(PermissionRequiredMixin, ObjectListView):
  705. permission_required = 'dcim.view_device'
  706. queryset = Device.objects.prefetch_related(
  707. 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
  708. )
  709. filter = filters.DeviceFilter
  710. filter_form = forms.DeviceFilterForm
  711. table = tables.DeviceDetailTable
  712. template_name = 'dcim/device_list.html'
  713. class DeviceView(PermissionRequiredMixin, View):
  714. permission_required = 'dcim.view_device'
  715. def get(self, request, pk):
  716. device = get_object_or_404(Device.objects.prefetch_related(
  717. 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
  718. ), pk=pk)
  719. # VirtualChassis members
  720. if device.virtual_chassis is not None:
  721. vc_members = Device.objects.filter(
  722. virtual_chassis=device.virtual_chassis
  723. ).order_by('vc_position')
  724. else:
  725. vc_members = []
  726. # Console ports
  727. console_ports = device.consoleports.prefetch_related('connected_endpoint__device', 'cable')
  728. # Console server ports
  729. consoleserverports = device.consoleserverports.prefetch_related('connected_endpoint__device', 'cable')
  730. # Power ports
  731. power_ports = device.powerports.prefetch_related('_connected_poweroutlet__device', 'cable')
  732. # Power outlets
  733. poweroutlets = device.poweroutlets.prefetch_related('connected_endpoint__device', 'cable', 'power_port')
  734. # Interfaces
  735. interfaces = device.vc_interfaces.prefetch_related(
  736. 'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable',
  737. 'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags'
  738. )
  739. # Front ports
  740. front_ports = device.frontports.prefetch_related('rear_port', 'cable')
  741. # Rear ports
  742. rear_ports = device.rearports.prefetch_related('cable')
  743. # Device bays
  744. device_bays = device.device_bays.prefetch_related('installed_device__device_type__manufacturer')
  745. # Services
  746. services = device.services.all()
  747. # Secrets
  748. secrets = device.secrets.all()
  749. # Find up to ten devices in the same site with the same functional role for quick reference.
  750. related_devices = Device.objects.filter(
  751. site=device.site, device_role=device.device_role
  752. ).exclude(
  753. pk=device.pk
  754. ).prefetch_related(
  755. 'rack', 'device_type__manufacturer'
  756. )[:10]
  757. return render(request, 'dcim/device.html', {
  758. 'device': device,
  759. 'console_ports': console_ports,
  760. 'consoleserverports': consoleserverports,
  761. 'power_ports': power_ports,
  762. 'poweroutlets': poweroutlets,
  763. 'interfaces': interfaces,
  764. 'device_bays': device_bays,
  765. 'front_ports': front_ports,
  766. 'rear_ports': rear_ports,
  767. 'services': services,
  768. 'secrets': secrets,
  769. 'vc_members': vc_members,
  770. 'related_devices': related_devices,
  771. 'show_graphs': Graph.objects.filter(type=GRAPH_TYPE_DEVICE).exists(),
  772. 'show_interface_graphs': Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists(),
  773. })
  774. class DeviceInventoryView(PermissionRequiredMixin, View):
  775. permission_required = 'dcim.view_device'
  776. def get(self, request, pk):
  777. device = get_object_or_404(Device, pk=pk)
  778. inventory_items = InventoryItem.objects.filter(
  779. device=device, parent=None
  780. ).prefetch_related(
  781. 'manufacturer', 'child_items'
  782. )
  783. return render(request, 'dcim/device_inventory.html', {
  784. 'device': device,
  785. 'inventory_items': inventory_items,
  786. 'active_tab': 'inventory',
  787. })
  788. class DeviceStatusView(PermissionRequiredMixin, View):
  789. permission_required = ('dcim.view_device', 'dcim.napalm_read')
  790. def get(self, request, pk):
  791. device = get_object_or_404(Device, pk=pk)
  792. return render(request, 'dcim/device_status.html', {
  793. 'device': device,
  794. 'active_tab': 'status',
  795. })
  796. class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
  797. permission_required = ('dcim.view_device', 'dcim.napalm_read')
  798. def get(self, request, pk):
  799. device = get_object_or_404(Device, pk=pk)
  800. interfaces = device.vc_interfaces.connectable().prefetch_related(
  801. '_connected_interface__device'
  802. )
  803. return render(request, 'dcim/device_lldp_neighbors.html', {
  804. 'device': device,
  805. 'interfaces': interfaces,
  806. 'active_tab': 'lldp-neighbors',
  807. })
  808. class DeviceConfigView(PermissionRequiredMixin, View):
  809. permission_required = ('dcim.view_device', 'dcim.napalm_read')
  810. def get(self, request, pk):
  811. device = get_object_or_404(Device, pk=pk)
  812. return render(request, 'dcim/device_config.html', {
  813. 'device': device,
  814. 'active_tab': 'config',
  815. })
  816. class DeviceConfigContextView(PermissionRequiredMixin, ObjectConfigContextView):
  817. permission_required = 'dcim.view_device'
  818. object_class = Device
  819. base_template = 'dcim/device.html'
  820. class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
  821. permission_required = 'dcim.add_device'
  822. model = Device
  823. model_form = forms.DeviceForm
  824. template_name = 'dcim/device_edit.html'
  825. default_return_url = 'dcim:device_list'
  826. class DeviceEditView(DeviceCreateView):
  827. permission_required = 'dcim.change_device'
  828. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  829. permission_required = 'dcim.delete_device'
  830. model = Device
  831. default_return_url = 'dcim:device_list'
  832. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  833. permission_required = 'dcim.add_device'
  834. model_form = forms.DeviceCSVForm
  835. table = tables.DeviceImportTable
  836. template_name = 'dcim/device_import.html'
  837. default_return_url = 'dcim:device_list'
  838. class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  839. permission_required = 'dcim.add_device'
  840. model_form = forms.ChildDeviceCSVForm
  841. table = tables.DeviceImportTable
  842. template_name = 'dcim/device_import_child.html'
  843. default_return_url = 'dcim:device_list'
  844. def _save_obj(self, obj_form):
  845. obj = obj_form.save()
  846. # Save the reverse relation to the parent device bay
  847. device_bay = obj.parent_bay
  848. device_bay.installed_device = obj
  849. device_bay.save()
  850. return obj
  851. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  852. permission_required = 'dcim.change_device'
  853. queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  854. filter = filters.DeviceFilter
  855. table = tables.DeviceTable
  856. form = forms.DeviceBulkEditForm
  857. default_return_url = 'dcim:device_list'
  858. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  859. permission_required = 'dcim.delete_device'
  860. queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  861. filter = filters.DeviceFilter
  862. table = tables.DeviceTable
  863. default_return_url = 'dcim:device_list'
  864. #
  865. # Console ports
  866. #
  867. class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView):
  868. permission_required = 'dcim.add_consoleport'
  869. parent_model = Device
  870. parent_field = 'device'
  871. model = ConsolePort
  872. form = forms.ConsolePortCreateForm
  873. model_form = forms.ConsolePortForm
  874. template_name = 'dcim/device_component_add.html'
  875. class ConsolePortEditView(PermissionRequiredMixin, ObjectEditView):
  876. permission_required = 'dcim.change_consoleport'
  877. model = ConsolePort
  878. model_form = forms.ConsolePortForm
  879. class ConsolePortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  880. permission_required = 'dcim.delete_consoleport'
  881. model = ConsolePort
  882. class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  883. permission_required = 'dcim.delete_consoleport'
  884. queryset = ConsolePort.objects.all()
  885. parent_model = Device
  886. table = tables.ConsolePortTable
  887. #
  888. # Console server ports
  889. #
  890. class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  891. permission_required = 'dcim.add_consoleserverport'
  892. parent_model = Device
  893. parent_field = 'device'
  894. model = ConsoleServerPort
  895. form = forms.ConsoleServerPortCreateForm
  896. model_form = forms.ConsoleServerPortForm
  897. template_name = 'dcim/device_component_add.html'
  898. class ConsoleServerPortEditView(PermissionRequiredMixin, ObjectEditView):
  899. permission_required = 'dcim.change_consoleserverport'
  900. model = ConsoleServerPort
  901. model_form = forms.ConsoleServerPortForm
  902. class ConsoleServerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  903. permission_required = 'dcim.delete_consoleserverport'
  904. model = ConsoleServerPort
  905. class ConsoleServerPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  906. permission_required = 'dcim.change_consoleserverport'
  907. queryset = ConsoleServerPort.objects.all()
  908. parent_model = Device
  909. table = tables.ConsoleServerPortTable
  910. form = forms.ConsoleServerPortBulkEditForm
  911. class ConsoleServerPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  912. permission_required = 'dcim.change_consoleserverport'
  913. queryset = ConsoleServerPort.objects.all()
  914. form = forms.ConsoleServerPortBulkRenameForm
  915. class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  916. permission_required = 'dcim.change_consoleserverport'
  917. model = ConsoleServerPort
  918. form = forms.ConsoleServerPortBulkDisconnectForm
  919. class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  920. permission_required = 'dcim.delete_consoleserverport'
  921. queryset = ConsoleServerPort.objects.all()
  922. parent_model = Device
  923. table = tables.ConsoleServerPortTable
  924. #
  925. # Power ports
  926. #
  927. class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  928. permission_required = 'dcim.add_powerport'
  929. parent_model = Device
  930. parent_field = 'device'
  931. model = PowerPort
  932. form = forms.PowerPortCreateForm
  933. model_form = forms.PowerPortForm
  934. template_name = 'dcim/device_component_add.html'
  935. class PowerPortEditView(PermissionRequiredMixin, ObjectEditView):
  936. permission_required = 'dcim.change_powerport'
  937. model = PowerPort
  938. model_form = forms.PowerPortForm
  939. class PowerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  940. permission_required = 'dcim.delete_powerport'
  941. model = PowerPort
  942. class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  943. permission_required = 'dcim.delete_powerport'
  944. queryset = PowerPort.objects.all()
  945. parent_model = Device
  946. table = tables.PowerPortTable
  947. #
  948. # Power outlets
  949. #
  950. class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView):
  951. permission_required = 'dcim.add_poweroutlet'
  952. parent_model = Device
  953. parent_field = 'device'
  954. model = PowerOutlet
  955. form = forms.PowerOutletCreateForm
  956. model_form = forms.PowerOutletForm
  957. template_name = 'dcim/device_component_add.html'
  958. class PowerOutletEditView(PermissionRequiredMixin, ObjectEditView):
  959. permission_required = 'dcim.change_poweroutlet'
  960. model = PowerOutlet
  961. model_form = forms.PowerOutletForm
  962. class PowerOutletDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  963. permission_required = 'dcim.delete_poweroutlet'
  964. model = PowerOutlet
  965. class PowerOutletBulkEditView(PermissionRequiredMixin, BulkEditView):
  966. permission_required = 'dcim.change_poweroutlet'
  967. queryset = PowerOutlet.objects.all()
  968. parent_model = Device
  969. table = tables.PowerOutletTable
  970. form = forms.PowerOutletBulkEditForm
  971. class PowerOutletBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  972. permission_required = 'dcim.change_poweroutlet'
  973. queryset = PowerOutlet.objects.all()
  974. form = forms.PowerOutletBulkRenameForm
  975. class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  976. permission_required = 'dcim.change_poweroutlet'
  977. model = PowerOutlet
  978. form = forms.PowerOutletBulkDisconnectForm
  979. class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  980. permission_required = 'dcim.delete_poweroutlet'
  981. queryset = PowerOutlet.objects.all()
  982. parent_model = Device
  983. table = tables.PowerOutletTable
  984. #
  985. # Interfaces
  986. #
  987. class InterfaceView(PermissionRequiredMixin, View):
  988. permission_required = 'dcim.view_interface'
  989. def get(self, request, pk):
  990. interface = get_object_or_404(Interface, pk=pk)
  991. # Get assigned IP addresses
  992. ipaddress_table = InterfaceIPAddressTable(
  993. data=interface.ip_addresses.prefetch_related('vrf', 'tenant'),
  994. orderable=False
  995. )
  996. # Get assigned VLANs and annotate whether each is tagged or untagged
  997. vlans = []
  998. if interface.untagged_vlan is not None:
  999. vlans.append(interface.untagged_vlan)
  1000. vlans[0].tagged = False
  1001. for vlan in interface.tagged_vlans.prefetch_related('site', 'group', 'tenant', 'role'):
  1002. vlan.tagged = True
  1003. vlans.append(vlan)
  1004. vlan_table = InterfaceVLANTable(
  1005. interface=interface,
  1006. data=vlans,
  1007. orderable=False
  1008. )
  1009. return render(request, 'dcim/interface.html', {
  1010. 'interface': interface,
  1011. 'connected_interface': interface._connected_interface,
  1012. 'connected_circuittermination': interface._connected_circuittermination,
  1013. 'ipaddress_table': ipaddress_table,
  1014. 'vlan_table': vlan_table,
  1015. })
  1016. class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
  1017. permission_required = 'dcim.add_interface'
  1018. parent_model = Device
  1019. parent_field = 'device'
  1020. model = Interface
  1021. form = forms.InterfaceCreateForm
  1022. model_form = forms.InterfaceForm
  1023. template_name = 'dcim/device_component_add.html'
  1024. class InterfaceEditView(PermissionRequiredMixin, ObjectEditView):
  1025. permission_required = 'dcim.change_interface'
  1026. model = Interface
  1027. model_form = forms.InterfaceForm
  1028. template_name = 'dcim/interface_edit.html'
  1029. class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1030. permission_required = 'dcim.delete_interface'
  1031. model = Interface
  1032. class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
  1033. permission_required = 'dcim.change_interface'
  1034. queryset = Interface.objects.all()
  1035. parent_model = Device
  1036. table = tables.InterfaceTable
  1037. form = forms.InterfaceBulkEditForm
  1038. class InterfaceBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1039. permission_required = 'dcim.change_interface'
  1040. queryset = Interface.objects.all()
  1041. form = forms.InterfaceBulkRenameForm
  1042. class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1043. permission_required = 'dcim.change_interface'
  1044. model = Interface
  1045. form = forms.InterfaceBulkDisconnectForm
  1046. class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1047. permission_required = 'dcim.delete_interface'
  1048. queryset = Interface.objects.all()
  1049. parent_model = Device
  1050. table = tables.InterfaceTable
  1051. #
  1052. # Front ports
  1053. #
  1054. class FrontPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  1055. permission_required = 'dcim.add_frontport'
  1056. parent_model = Device
  1057. parent_field = 'device'
  1058. model = FrontPort
  1059. form = forms.FrontPortCreateForm
  1060. model_form = forms.FrontPortForm
  1061. template_name = 'dcim/device_component_add.html'
  1062. class FrontPortEditView(PermissionRequiredMixin, ObjectEditView):
  1063. permission_required = 'dcim.change_frontport'
  1064. model = FrontPort
  1065. model_form = forms.FrontPortForm
  1066. class FrontPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1067. permission_required = 'dcim.delete_frontport'
  1068. model = FrontPort
  1069. class FrontPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  1070. permission_required = 'dcim.change_frontport'
  1071. queryset = FrontPort.objects.all()
  1072. parent_model = Device
  1073. table = tables.FrontPortTable
  1074. form = forms.FrontPortBulkEditForm
  1075. class FrontPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1076. permission_required = 'dcim.change_frontport'
  1077. queryset = FrontPort.objects.all()
  1078. form = forms.FrontPortBulkRenameForm
  1079. class FrontPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1080. permission_required = 'dcim.change_frontport'
  1081. model = FrontPort
  1082. form = forms.FrontPortBulkDisconnectForm
  1083. class FrontPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1084. permission_required = 'dcim.delete_frontport'
  1085. queryset = FrontPort.objects.all()
  1086. parent_model = Device
  1087. table = tables.FrontPortTable
  1088. #
  1089. # Rear ports
  1090. #
  1091. class RearPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  1092. permission_required = 'dcim.add_rearport'
  1093. parent_model = Device
  1094. parent_field = 'device'
  1095. model = RearPort
  1096. form = forms.RearPortCreateForm
  1097. model_form = forms.RearPortForm
  1098. template_name = 'dcim/device_component_add.html'
  1099. class RearPortEditView(PermissionRequiredMixin, ObjectEditView):
  1100. permission_required = 'dcim.change_rearport'
  1101. model = RearPort
  1102. model_form = forms.RearPortForm
  1103. class RearPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1104. permission_required = 'dcim.delete_rearport'
  1105. model = RearPort
  1106. class RearPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  1107. permission_required = 'dcim.change_rearport'
  1108. queryset = RearPort.objects.all()
  1109. parent_model = Device
  1110. table = tables.RearPortTable
  1111. form = forms.RearPortBulkEditForm
  1112. class RearPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1113. permission_required = 'dcim.change_rearport'
  1114. queryset = RearPort.objects.all()
  1115. form = forms.RearPortBulkRenameForm
  1116. class RearPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1117. permission_required = 'dcim.change_rearport'
  1118. model = RearPort
  1119. form = forms.RearPortBulkDisconnectForm
  1120. class RearPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1121. permission_required = 'dcim.delete_rearport'
  1122. queryset = RearPort.objects.all()
  1123. parent_model = Device
  1124. table = tables.RearPortTable
  1125. #
  1126. # Device bays
  1127. #
  1128. class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView):
  1129. permission_required = 'dcim.add_devicebay'
  1130. parent_model = Device
  1131. parent_field = 'device'
  1132. model = DeviceBay
  1133. form = forms.DeviceBayCreateForm
  1134. model_form = forms.DeviceBayForm
  1135. template_name = 'dcim/device_component_add.html'
  1136. class DeviceBayEditView(PermissionRequiredMixin, ObjectEditView):
  1137. permission_required = 'dcim.change_devicebay'
  1138. model = DeviceBay
  1139. model_form = forms.DeviceBayForm
  1140. class DeviceBayDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1141. permission_required = 'dcim.delete_devicebay'
  1142. model = DeviceBay
  1143. class DeviceBayPopulateView(PermissionRequiredMixin, View):
  1144. permission_required = 'dcim.change_devicebay'
  1145. def get(self, request, pk):
  1146. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1147. form = forms.PopulateDeviceBayForm(device_bay)
  1148. return render(request, 'dcim/devicebay_populate.html', {
  1149. 'device_bay': device_bay,
  1150. 'form': form,
  1151. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1152. })
  1153. def post(self, request, pk):
  1154. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1155. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  1156. if form.is_valid():
  1157. device_bay.installed_device = form.cleaned_data['installed_device']
  1158. device_bay.save()
  1159. messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
  1160. return redirect('dcim:device', pk=device_bay.device.pk)
  1161. return render(request, 'dcim/devicebay_populate.html', {
  1162. 'device_bay': device_bay,
  1163. 'form': form,
  1164. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1165. })
  1166. class DeviceBayDepopulateView(PermissionRequiredMixin, View):
  1167. permission_required = 'dcim.change_devicebay'
  1168. def get(self, request, pk):
  1169. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1170. form = ConfirmationForm()
  1171. return render(request, 'dcim/devicebay_depopulate.html', {
  1172. 'device_bay': device_bay,
  1173. 'form': form,
  1174. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1175. })
  1176. def post(self, request, pk):
  1177. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1178. form = ConfirmationForm(request.POST)
  1179. if form.is_valid():
  1180. removed_device = device_bay.installed_device
  1181. device_bay.installed_device = None
  1182. device_bay.save()
  1183. messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay))
  1184. return redirect('dcim:device', pk=device_bay.device.pk)
  1185. return render(request, 'dcim/devicebay_depopulate.html', {
  1186. 'device_bay': device_bay,
  1187. 'form': form,
  1188. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1189. })
  1190. class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1191. permission_required = 'dcim.change_devicebay'
  1192. queryset = DeviceBay.objects.all()
  1193. form = forms.DeviceBayBulkRenameForm
  1194. class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1195. permission_required = 'dcim.delete_devicebay'
  1196. queryset = DeviceBay.objects.all()
  1197. parent_model = Device
  1198. table = tables.DeviceBayTable
  1199. #
  1200. # Bulk Device component creation
  1201. #
  1202. class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateView):
  1203. permission_required = 'dcim.add_consoleport'
  1204. parent_model = Device
  1205. parent_field = 'device'
  1206. form = forms.DeviceBulkAddComponentForm
  1207. model = ConsolePort
  1208. model_form = forms.ConsolePortForm
  1209. filter = filters.DeviceFilter
  1210. table = tables.DeviceTable
  1211. default_return_url = 'dcim:device_list'
  1212. class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1213. permission_required = 'dcim.add_consoleserverport'
  1214. parent_model = Device
  1215. parent_field = 'device'
  1216. form = forms.DeviceBulkAddComponentForm
  1217. model = ConsoleServerPort
  1218. model_form = forms.ConsoleServerPortForm
  1219. filter = filters.DeviceFilter
  1220. table = tables.DeviceTable
  1221. default_return_url = 'dcim:device_list'
  1222. class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1223. permission_required = 'dcim.add_powerport'
  1224. parent_model = Device
  1225. parent_field = 'device'
  1226. form = forms.DeviceBulkAddComponentForm
  1227. model = PowerPort
  1228. model_form = forms.PowerPortForm
  1229. filter = filters.DeviceFilter
  1230. table = tables.DeviceTable
  1231. default_return_url = 'dcim:device_list'
  1232. class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateView):
  1233. permission_required = 'dcim.add_poweroutlet'
  1234. parent_model = Device
  1235. parent_field = 'device'
  1236. form = forms.DeviceBulkAddComponentForm
  1237. model = PowerOutlet
  1238. model_form = forms.PowerOutletForm
  1239. filter = filters.DeviceFilter
  1240. table = tables.DeviceTable
  1241. default_return_url = 'dcim:device_list'
  1242. class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView):
  1243. permission_required = 'dcim.add_interface'
  1244. parent_model = Device
  1245. parent_field = 'device'
  1246. form = forms.DeviceBulkAddInterfaceForm
  1247. model = Interface
  1248. model_form = forms.InterfaceForm
  1249. filter = filters.DeviceFilter
  1250. table = tables.DeviceTable
  1251. default_return_url = 'dcim:device_list'
  1252. class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateView):
  1253. permission_required = 'dcim.add_devicebay'
  1254. parent_model = Device
  1255. parent_field = 'device'
  1256. form = forms.DeviceBulkAddComponentForm
  1257. model = DeviceBay
  1258. model_form = forms.DeviceBayForm
  1259. filter = filters.DeviceFilter
  1260. table = tables.DeviceTable
  1261. default_return_url = 'dcim:device_list'
  1262. #
  1263. # Cables
  1264. #
  1265. class CableListView(PermissionRequiredMixin, ObjectListView):
  1266. permission_required = 'dcim.view_cable'
  1267. queryset = Cable.objects.prefetch_related(
  1268. 'termination_a', 'termination_b'
  1269. )
  1270. filter = filters.CableFilter
  1271. filter_form = forms.CableFilterForm
  1272. table = tables.CableTable
  1273. template_name = 'dcim/cable_list.html'
  1274. class CableView(PermissionRequiredMixin, View):
  1275. permission_required = 'dcim.view_cable'
  1276. def get(self, request, pk):
  1277. cable = get_object_or_404(Cable, pk=pk)
  1278. return render(request, 'dcim/cable.html', {
  1279. 'cable': cable,
  1280. })
  1281. class CableTraceView(PermissionRequiredMixin, View):
  1282. """
  1283. Trace a cable path beginning from the given termination.
  1284. """
  1285. permission_required = 'dcim.view_cable'
  1286. def get(self, request, model, pk):
  1287. obj = get_object_or_404(model, pk=pk)
  1288. return render(request, 'dcim/cable_trace.html', {
  1289. 'obj': obj,
  1290. 'trace': obj.trace(follow_circuits=True),
  1291. })
  1292. class CableCreateView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1293. permission_required = 'dcim.add_cable'
  1294. template_name = 'dcim/cable_connect.html'
  1295. def dispatch(self, request, *args, **kwargs):
  1296. termination_a_type = kwargs.get('termination_a_type')
  1297. termination_a_id = kwargs.get('termination_a_id')
  1298. termination_b_type_name = kwargs.get('termination_b_type')
  1299. self.termination_b_type = ContentType.objects.get(model=termination_b_type_name.replace('-', ''))
  1300. self.obj = Cable(
  1301. termination_a=termination_a_type.objects.get(pk=termination_a_id),
  1302. termination_b_type=self.termination_b_type
  1303. )
  1304. self.form_class = {
  1305. 'console-port': forms.ConnectCableToConsolePortForm,
  1306. 'console-server-port': forms.ConnectCableToConsoleServerPortForm,
  1307. 'power-port': forms.ConnectCableToPowerPortForm,
  1308. 'power-outlet': forms.ConnectCableToPowerOutletForm,
  1309. 'interface': forms.ConnectCableToInterfaceForm,
  1310. 'front-port': forms.ConnectCableToFrontPortForm,
  1311. 'rear-port': forms.ConnectCableToRearPortForm,
  1312. 'power-feed': forms.ConnectCableToPowerFeedForm,
  1313. 'circuit-termination': forms.ConnectCableToCircuitTerminationForm,
  1314. }[termination_b_type_name]
  1315. return super().dispatch(request, *args, **kwargs)
  1316. def get(self, request, *args, **kwargs):
  1317. # Parse initial data manually to avoid setting field values as lists
  1318. initial_data = {k: request.GET[k] for k in request.GET}
  1319. form = self.form_class(instance=self.obj, initial=initial_data)
  1320. return render(request, self.template_name, {
  1321. 'obj': self.obj,
  1322. 'obj_type': Cable._meta.verbose_name,
  1323. 'termination_b_type': self.termination_b_type.name,
  1324. 'form': form,
  1325. 'return_url': self.get_return_url(request, self.obj),
  1326. })
  1327. def post(self, request, *args, **kwargs):
  1328. form = self.form_class(request.POST, request.FILES, instance=self.obj)
  1329. if form.is_valid():
  1330. obj = form.save()
  1331. msg = 'Created cable <a href="{}">{}</a>'.format(
  1332. obj.get_absolute_url(),
  1333. escape(obj)
  1334. )
  1335. messages.success(request, mark_safe(msg))
  1336. if '_addanother' in request.POST:
  1337. return redirect(request.get_full_path())
  1338. return_url = form.cleaned_data.get('return_url')
  1339. if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
  1340. return redirect(return_url)
  1341. else:
  1342. return redirect(self.get_return_url(request, obj))
  1343. return render(request, self.template_name, {
  1344. 'obj': self.obj,
  1345. 'obj_type': Cable._meta.verbose_name,
  1346. 'termination_b_type': self.termination_b_type.name,
  1347. 'form': form,
  1348. 'return_url': self.get_return_url(request, self.obj),
  1349. })
  1350. class CableEditView(PermissionRequiredMixin, ObjectEditView):
  1351. permission_required = 'dcim.change_cable'
  1352. model = Cable
  1353. model_form = forms.CableForm
  1354. template_name = 'dcim/cable_edit.html'
  1355. default_return_url = 'dcim:cable_list'
  1356. class CableDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1357. permission_required = 'dcim.delete_cable'
  1358. model = Cable
  1359. default_return_url = 'dcim:cable_list'
  1360. class CableBulkImportView(PermissionRequiredMixin, BulkImportView):
  1361. permission_required = 'dcim.add_cable'
  1362. model_form = forms.CableCSVForm
  1363. table = tables.CableTable
  1364. default_return_url = 'dcim:cable_list'
  1365. class CableBulkEditView(PermissionRequiredMixin, BulkEditView):
  1366. permission_required = 'dcim.change_cable'
  1367. queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
  1368. filter = filters.CableFilter
  1369. table = tables.CableTable
  1370. form = forms.CableBulkEditForm
  1371. default_return_url = 'dcim:cable_list'
  1372. class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1373. permission_required = 'dcim.delete_cable'
  1374. queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
  1375. filter = filters.CableFilter
  1376. table = tables.CableTable
  1377. default_return_url = 'dcim:cable_list'
  1378. #
  1379. # Connections
  1380. #
  1381. class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
  1382. permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport')
  1383. queryset = ConsolePort.objects.prefetch_related(
  1384. 'device', 'connected_endpoint__device'
  1385. ).filter(
  1386. connected_endpoint__isnull=False
  1387. ).order_by(
  1388. 'cable', 'connected_endpoint__device__name', 'connected_endpoint__name'
  1389. )
  1390. filter = filters.ConsoleConnectionFilter
  1391. filter_form = forms.ConsoleConnectionFilterForm
  1392. table = tables.ConsoleConnectionTable
  1393. template_name = 'dcim/console_connections_list.html'
  1394. def queryset_to_csv(self):
  1395. csv_data = [
  1396. # Headers
  1397. ','.join(['console_server', 'port', 'device', 'console_port', 'connection_status'])
  1398. ]
  1399. for obj in self.queryset:
  1400. csv = csv_format([
  1401. obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
  1402. obj.connected_endpoint.name if obj.connected_endpoint else None,
  1403. obj.device.identifier,
  1404. obj.name,
  1405. obj.get_connection_status_display(),
  1406. ])
  1407. csv_data.append(csv)
  1408. return csv_data
  1409. class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
  1410. permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet')
  1411. queryset = PowerPort.objects.prefetch_related(
  1412. 'device', '_connected_poweroutlet__device'
  1413. ).filter(
  1414. _connected_poweroutlet__isnull=False
  1415. ).order_by(
  1416. 'cable', '_connected_poweroutlet__device__name', '_connected_poweroutlet__name'
  1417. )
  1418. filter = filters.PowerConnectionFilter
  1419. filter_form = forms.PowerConnectionFilterForm
  1420. table = tables.PowerConnectionTable
  1421. template_name = 'dcim/power_connections_list.html'
  1422. def queryset_to_csv(self):
  1423. csv_data = [
  1424. # Headers
  1425. ','.join(['pdu', 'outlet', 'device', 'power_port', 'connection_status'])
  1426. ]
  1427. for obj in self.queryset:
  1428. csv = csv_format([
  1429. obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
  1430. obj.connected_endpoint.name if obj.connected_endpoint else None,
  1431. obj.device.identifier,
  1432. obj.name,
  1433. obj.get_connection_status_display(),
  1434. ])
  1435. csv_data.append(csv)
  1436. return csv_data
  1437. class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
  1438. permission_required = 'dcim.view_interface'
  1439. queryset = Interface.objects.prefetch_related(
  1440. 'device', 'cable', '_connected_interface__device'
  1441. ).filter(
  1442. # Avoid duplicate connections by only selecting the lower PK in a connected pair
  1443. _connected_interface__isnull=False,
  1444. pk__lt=F('_connected_interface')
  1445. ).order_by(
  1446. 'device'
  1447. )
  1448. filter = filters.InterfaceConnectionFilter
  1449. filter_form = forms.InterfaceConnectionFilterForm
  1450. table = tables.InterfaceConnectionTable
  1451. template_name = 'dcim/interface_connections_list.html'
  1452. def queryset_to_csv(self):
  1453. csv_data = [
  1454. # Headers
  1455. ','.join([
  1456. 'device_a', 'interface_a', 'interface_a_description',
  1457. 'device_b', 'interface_b', 'interface_b_description',
  1458. 'connection_status'
  1459. ])
  1460. ]
  1461. for obj in self.queryset:
  1462. csv = csv_format([
  1463. obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
  1464. obj.connected_endpoint.name if obj.connected_endpoint else None,
  1465. obj.connected_endpoint.description if obj.connected_endpoint else None,
  1466. obj.device.identifier,
  1467. obj.name,
  1468. obj.description,
  1469. obj.get_connection_status_display(),
  1470. ])
  1471. csv_data.append(csv)
  1472. return csv_data
  1473. #
  1474. # Inventory items
  1475. #
  1476. class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
  1477. permission_required = 'dcim.view_inventoryitem'
  1478. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
  1479. filter = filters.InventoryItemFilter
  1480. filter_form = forms.InventoryItemFilterForm
  1481. table = tables.InventoryItemTable
  1482. template_name = 'dcim/inventoryitem_list.html'
  1483. class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView):
  1484. permission_required = 'dcim.change_inventoryitem'
  1485. model = InventoryItem
  1486. model_form = forms.InventoryItemForm
  1487. def alter_obj(self, obj, request, url_args, url_kwargs):
  1488. if 'device' in url_kwargs:
  1489. obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
  1490. return obj
  1491. def get_return_url(self, request, obj):
  1492. return reverse('dcim:device_inventory', kwargs={'pk': obj.device.pk})
  1493. class InventoryItemDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1494. permission_required = 'dcim.delete_inventoryitem'
  1495. model = InventoryItem
  1496. class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
  1497. permission_required = 'dcim.add_inventoryitem'
  1498. model_form = forms.InventoryItemCSVForm
  1499. table = tables.InventoryItemTable
  1500. default_return_url = 'dcim:inventoryitem_list'
  1501. class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
  1502. permission_required = 'dcim.change_inventoryitem'
  1503. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
  1504. filter = filters.InventoryItemFilter
  1505. table = tables.InventoryItemTable
  1506. form = forms.InventoryItemBulkEditForm
  1507. default_return_url = 'dcim:inventoryitem_list'
  1508. class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1509. permission_required = 'dcim.delete_inventoryitem'
  1510. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
  1511. table = tables.InventoryItemTable
  1512. template_name = 'dcim/inventoryitem_bulk_delete.html'
  1513. default_return_url = 'dcim:inventoryitem_list'
  1514. #
  1515. # Virtual chassis
  1516. #
  1517. class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
  1518. permission_required = 'dcim.view_virtualchassis'
  1519. queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members'))
  1520. table = tables.VirtualChassisTable
  1521. filter = filters.VirtualChassisFilter
  1522. filter_form = forms.VirtualChassisFilterForm
  1523. template_name = 'dcim/virtualchassis_list.html'
  1524. class VirtualChassisCreateView(PermissionRequiredMixin, View):
  1525. permission_required = 'dcim.add_virtualchassis'
  1526. def post(self, request):
  1527. # Get the list of devices being added to a VirtualChassis
  1528. pk_form = forms.DeviceSelectionForm(request.POST)
  1529. pk_form.full_clean()
  1530. if not pk_form.cleaned_data.get('pk'):
  1531. messages.warning(request, "No devices were selected.")
  1532. return redirect('dcim:device_list')
  1533. device_queryset = Device.objects.filter(
  1534. pk__in=pk_form.cleaned_data.get('pk')
  1535. ).prefetch_related('rack').order_by('vc_position')
  1536. VCMemberFormSet = modelformset_factory(
  1537. model=Device,
  1538. formset=forms.BaseVCMemberFormSet,
  1539. form=forms.DeviceVCMembershipForm,
  1540. extra=0
  1541. )
  1542. if '_create' in request.POST:
  1543. vc_form = forms.VirtualChassisForm(request.POST)
  1544. vc_form.fields['master'].queryset = device_queryset
  1545. formset = VCMemberFormSet(request.POST, queryset=device_queryset)
  1546. if vc_form.is_valid() and formset.is_valid():
  1547. with transaction.atomic():
  1548. # Assign each device to the VirtualChassis before saving
  1549. virtual_chassis = vc_form.save()
  1550. devices = formset.save(commit=False)
  1551. for device in devices:
  1552. device.virtual_chassis = virtual_chassis
  1553. device.save()
  1554. return redirect(vc_form.cleaned_data['master'].get_absolute_url())
  1555. else:
  1556. vc_form = forms.VirtualChassisForm()
  1557. vc_form.fields['master'].queryset = device_queryset
  1558. formset = VCMemberFormSet(queryset=device_queryset)
  1559. return render(request, 'dcim/virtualchassis_edit.html', {
  1560. 'pk_form': pk_form,
  1561. 'vc_form': vc_form,
  1562. 'formset': formset,
  1563. 'return_url': reverse('dcim:device_list'),
  1564. })
  1565. class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1566. permission_required = 'dcim.change_virtualchassis'
  1567. def get(self, request, pk):
  1568. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1569. VCMemberFormSet = modelformset_factory(
  1570. model=Device,
  1571. form=forms.DeviceVCMembershipForm,
  1572. formset=forms.BaseVCMemberFormSet,
  1573. extra=0
  1574. )
  1575. members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
  1576. vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
  1577. vc_form.fields['master'].queryset = members_queryset
  1578. formset = VCMemberFormSet(queryset=members_queryset)
  1579. return render(request, 'dcim/virtualchassis_edit.html', {
  1580. 'vc_form': vc_form,
  1581. 'formset': formset,
  1582. 'return_url': self.get_return_url(request, virtual_chassis),
  1583. })
  1584. def post(self, request, pk):
  1585. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1586. VCMemberFormSet = modelformset_factory(
  1587. model=Device,
  1588. form=forms.DeviceVCMembershipForm,
  1589. formset=forms.BaseVCMemberFormSet,
  1590. extra=0
  1591. )
  1592. members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
  1593. vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
  1594. vc_form.fields['master'].queryset = members_queryset
  1595. formset = VCMemberFormSet(request.POST, queryset=members_queryset)
  1596. if vc_form.is_valid() and formset.is_valid():
  1597. with transaction.atomic():
  1598. # Save the VirtualChassis
  1599. vc_form.save()
  1600. # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
  1601. # duplicate positions. Then save each member instance.
  1602. members = formset.save(commit=False)
  1603. devices = Device.objects.filter(pk__in=[m.pk for m in members])
  1604. for device in devices:
  1605. device.vc_position = None
  1606. device.save()
  1607. for member in members:
  1608. member.save()
  1609. return redirect(vc_form.cleaned_data['master'].get_absolute_url())
  1610. return render(request, 'dcim/virtualchassis_edit.html', {
  1611. 'vc_form': vc_form,
  1612. 'formset': formset,
  1613. 'return_url': self.get_return_url(request, virtual_chassis),
  1614. })
  1615. class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1616. permission_required = 'dcim.delete_virtualchassis'
  1617. model = VirtualChassis
  1618. default_return_url = 'dcim:device_list'
  1619. class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1620. permission_required = 'dcim.change_virtualchassis'
  1621. def get(self, request, pk):
  1622. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1623. initial_data = {k: request.GET[k] for k in request.GET}
  1624. member_select_form = forms.VCMemberSelectForm(initial=initial_data)
  1625. membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
  1626. return render(request, 'dcim/virtualchassis_add_member.html', {
  1627. 'virtual_chassis': virtual_chassis,
  1628. 'member_select_form': member_select_form,
  1629. 'membership_form': membership_form,
  1630. 'return_url': self.get_return_url(request, virtual_chassis),
  1631. })
  1632. def post(self, request, pk):
  1633. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1634. member_select_form = forms.VCMemberSelectForm(request.POST)
  1635. if member_select_form.is_valid():
  1636. device = member_select_form.cleaned_data['device']
  1637. device.virtual_chassis = virtual_chassis
  1638. data = {k: request.POST[k] for k in ['vc_position', 'vc_priority']}
  1639. membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
  1640. if membership_form.is_valid():
  1641. membership_form.save()
  1642. msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device))
  1643. messages.success(request, mark_safe(msg))
  1644. if '_addanother' in request.POST:
  1645. return redirect(request.get_full_path())
  1646. return redirect(self.get_return_url(request, device))
  1647. else:
  1648. membership_form = forms.DeviceVCMembershipForm(data=request.POST)
  1649. return render(request, 'dcim/virtualchassis_add_member.html', {
  1650. 'virtual_chassis': virtual_chassis,
  1651. 'member_select_form': member_select_form,
  1652. 'membership_form': membership_form,
  1653. 'return_url': self.get_return_url(request, virtual_chassis),
  1654. })
  1655. class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1656. permission_required = 'dcim.change_virtualchassis'
  1657. def get(self, request, pk):
  1658. device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
  1659. form = ConfirmationForm(initial=request.GET)
  1660. return render(request, 'dcim/virtualchassis_remove_member.html', {
  1661. 'device': device,
  1662. 'form': form,
  1663. 'return_url': self.get_return_url(request, device),
  1664. })
  1665. def post(self, request, pk):
  1666. device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
  1667. form = ConfirmationForm(request.POST)
  1668. # Protect master device from being removed
  1669. virtual_chassis = VirtualChassis.objects.filter(master=device).first()
  1670. if virtual_chassis is not None:
  1671. msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device))
  1672. messages.error(request, mark_safe(msg))
  1673. return redirect(device.get_absolute_url())
  1674. if form.is_valid():
  1675. devices = Device.objects.filter(pk=device.pk)
  1676. for device in devices:
  1677. device.virtual_chassis = None
  1678. device.vc_position = None
  1679. device.vc_priority = None
  1680. device.save()
  1681. msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
  1682. messages.success(request, msg)
  1683. return redirect(self.get_return_url(request, device))
  1684. return render(request, 'dcim/virtualchassis_remove_member.html', {
  1685. 'device': device,
  1686. 'form': form,
  1687. 'return_url': self.get_return_url(request, device),
  1688. })
  1689. #
  1690. # Power panels
  1691. #
  1692. class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
  1693. permission_required = 'dcim.view_powerpanel'
  1694. queryset = PowerPanel.objects.prefetch_related(
  1695. 'site', 'rack_group'
  1696. ).annotate(
  1697. powerfeed_count=Count('powerfeeds')
  1698. )
  1699. filter = filters.PowerPanelFilter
  1700. filter_form = forms.PowerPanelFilterForm
  1701. table = tables.PowerPanelTable
  1702. template_name = 'dcim/powerpanel_list.html'
  1703. class PowerPanelView(PermissionRequiredMixin, View):
  1704. permission_required = 'dcim.view_powerpanel'
  1705. def get(self, request, pk):
  1706. powerpanel = get_object_or_404(PowerPanel.objects.prefetch_related('site', 'rack_group'), pk=pk)
  1707. powerfeed_table = tables.PowerFeedTable(
  1708. data=PowerFeed.objects.filter(power_panel=powerpanel).prefetch_related('rack'),
  1709. orderable=False
  1710. )
  1711. powerfeed_table.exclude = ['power_panel']
  1712. return render(request, 'dcim/powerpanel.html', {
  1713. 'powerpanel': powerpanel,
  1714. 'powerfeed_table': powerfeed_table,
  1715. })
  1716. class PowerPanelCreateView(PermissionRequiredMixin, ObjectEditView):
  1717. permission_required = 'dcim.add_powerpanel'
  1718. model = PowerPanel
  1719. model_form = forms.PowerPanelForm
  1720. default_return_url = 'dcim:powerpanel_list'
  1721. class PowerPanelEditView(PowerPanelCreateView):
  1722. permission_required = 'dcim.change_powerpanel'
  1723. class PowerPanelDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1724. permission_required = 'dcim.delete_powerpanel'
  1725. model = PowerPanel
  1726. default_return_url = 'dcim:powerpanel_list'
  1727. class PowerPanelBulkImportView(PermissionRequiredMixin, BulkImportView):
  1728. permission_required = 'dcim.add_powerpanel'
  1729. model_form = forms.PowerPanelCSVForm
  1730. table = tables.PowerPanelTable
  1731. default_return_url = 'dcim:powerpanel_list'
  1732. class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1733. permission_required = 'dcim.delete_powerpanel'
  1734. queryset = PowerPanel.objects.prefetch_related(
  1735. 'site', 'rack_group'
  1736. ).annotate(
  1737. rack_count=Count('powerfeeds')
  1738. )
  1739. filter = filters.PowerPanelFilter
  1740. table = tables.PowerPanelTable
  1741. default_return_url = 'dcim:powerpanel_list'
  1742. #
  1743. # Power feeds
  1744. #
  1745. class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
  1746. permission_required = 'dcim.view_powerfeed'
  1747. queryset = PowerFeed.objects.prefetch_related(
  1748. 'power_panel', 'rack'
  1749. )
  1750. filter = filters.PowerFeedFilter
  1751. filter_form = forms.PowerFeedFilterForm
  1752. table = tables.PowerFeedTable
  1753. template_name = 'dcim/powerfeed_list.html'
  1754. class PowerFeedView(PermissionRequiredMixin, View):
  1755. permission_required = 'dcim.view_powerfeed'
  1756. def get(self, request, pk):
  1757. powerfeed = get_object_or_404(PowerFeed.objects.prefetch_related('power_panel', 'rack'), pk=pk)
  1758. return render(request, 'dcim/powerfeed.html', {
  1759. 'powerfeed': powerfeed,
  1760. })
  1761. class PowerFeedCreateView(PermissionRequiredMixin, ObjectEditView):
  1762. permission_required = 'dcim.add_powerfeed'
  1763. model = PowerFeed
  1764. model_form = forms.PowerFeedForm
  1765. template_name = 'dcim/powerfeed_edit.html'
  1766. default_return_url = 'dcim:powerfeed_list'
  1767. class PowerFeedEditView(PowerFeedCreateView):
  1768. permission_required = 'dcim.change_powerfeed'
  1769. class PowerFeedDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1770. permission_required = 'dcim.delete_powerfeed'
  1771. model = PowerFeed
  1772. default_return_url = 'dcim:powerfeed_list'
  1773. class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
  1774. permission_required = 'dcim.add_powerfeed'
  1775. model_form = forms.PowerFeedCSVForm
  1776. table = tables.PowerFeedTable
  1777. default_return_url = 'dcim:powerfeed_list'
  1778. class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
  1779. permission_required = 'dcim.change_powerfeed'
  1780. queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
  1781. filter = filters.PowerFeedFilter
  1782. table = tables.PowerFeedTable
  1783. form = forms.PowerFeedBulkEditForm
  1784. default_return_url = 'dcim:powerfeed_list'
  1785. class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1786. permission_required = 'dcim.delete_powerfeed'
  1787. queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
  1788. filter = filters.PowerFeedFilter
  1789. table = tables.PowerFeedTable
  1790. default_return_url = 'dcim:powerfeed_list'