views.py 69 KB

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