views.py 81 KB

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