views.py 81 KB

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