views.py 80 KB

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