views.py 78 KB

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