views.py 61 KB

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