2
0

views.py 61 KB

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