views.py 78 KB

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