views.py 53 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591
  1. from copy import deepcopy
  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.urlresolvers import reverse
  9. from django.db.models import Count
  10. from django.http import HttpResponseRedirect
  11. from django.shortcuts import get_object_or_404, redirect, render
  12. from django.utils.http import urlencode
  13. from django.views.generic import View
  14. from ipam.models import Prefix, IPAddress, Service, VLAN
  15. from circuits.models import Circuit
  16. from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
  17. from utilities.forms import ConfirmationForm
  18. from utilities.views import (
  19. BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
  20. )
  21. from . import filters, forms, tables
  22. from .models import (
  23. CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
  24. DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
  25. Manufacturer, Module, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
  26. RackReservation, RackRole, Region, Site,
  27. )
  28. EXPANSION_PATTERN = '\[(\d+-\d+)\]'
  29. def xstr(s):
  30. """
  31. Replace None with an empty string (for CSV export)
  32. """
  33. return '' if s is None else str(s)
  34. def expand_pattern(string):
  35. """
  36. Expand a numeric pattern into a list of strings. Examples:
  37. 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3']
  38. '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']
  39. """
  40. lead, pattern, remnant = re.split(EXPANSION_PATTERN, string, maxsplit=1)
  41. x, y = pattern.split('-')
  42. for i in range(int(x), int(y) + 1):
  43. if remnant:
  44. for string in expand_pattern(remnant):
  45. yield "{0}{1}{2}".format(lead, i, string)
  46. else:
  47. yield "{0}{1}".format(lead, i)
  48. class ComponentCreateView(View):
  49. parent_model = None
  50. parent_field = None
  51. model = None
  52. form = None
  53. model_form = None
  54. def get(self, request, pk):
  55. parent = get_object_or_404(self.parent_model, pk=pk)
  56. form = self.form(parent, initial=request.GET)
  57. return render(request, 'dcim/device_component_add.html', {
  58. 'parent': parent,
  59. 'component_type': self.model._meta.verbose_name,
  60. 'form': form,
  61. 'return_url': parent.get_absolute_url(),
  62. })
  63. def post(self, request, pk):
  64. parent = get_object_or_404(self.parent_model, pk=pk)
  65. form = self.form(parent, request.POST)
  66. if form.is_valid():
  67. new_components = []
  68. data = deepcopy(form.cleaned_data)
  69. for name in form.cleaned_data['name_pattern']:
  70. component_data = {
  71. self.parent_field: parent.pk,
  72. 'name': name,
  73. }
  74. component_data.update(data)
  75. component_form = self.model_form(component_data)
  76. if component_form.is_valid():
  77. new_components.append(component_form.save(commit=False))
  78. else:
  79. for field, errors in component_form.errors.as_data().items():
  80. for e in errors:
  81. form.add_error(field, u'{}: {}'.format(name, ', '.join(e)))
  82. if not form.errors:
  83. self.model.objects.bulk_create(new_components)
  84. messages.success(request, u"Added {} {} to {}.".format(
  85. len(new_components), self.model._meta.verbose_name_plural, parent
  86. ))
  87. if '_addanother' in request.POST:
  88. return redirect(request.path)
  89. else:
  90. return redirect(parent.get_absolute_url())
  91. return render(request, 'dcim/device_component_add.html', {
  92. 'parent': parent,
  93. 'component_type': self.model._meta.verbose_name,
  94. 'form': form,
  95. 'return_url': parent.get_absolute_url(),
  96. })
  97. class ComponentEditView(ObjectEditView):
  98. def get_return_url(self, obj):
  99. return obj.device.get_absolute_url()
  100. class ComponentDeleteView(ObjectDeleteView):
  101. def get_return_url(self, obj):
  102. return obj.device.get_absolute_url()
  103. #
  104. # Regions
  105. #
  106. class RegionListView(ObjectListView):
  107. queryset = Region.objects.annotate(site_count=Count('sites'))
  108. table = tables.RegionTable
  109. template_name = 'dcim/region_list.html'
  110. class RegionEditView(PermissionRequiredMixin, ObjectEditView):
  111. permission_required = 'dcim.change_region'
  112. model = Region
  113. form_class = forms.RegionForm
  114. def get_return_url(self, obj):
  115. return reverse('dcim:region_list')
  116. class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  117. permission_required = 'dcim.delete_region'
  118. cls = Region
  119. default_return_url = 'dcim:region_list'
  120. #
  121. # Sites
  122. #
  123. class SiteListView(ObjectListView):
  124. queryset = Site.objects.select_related('region', 'tenant')
  125. filter = filters.SiteFilter
  126. filter_form = forms.SiteFilterForm
  127. table = tables.SiteTable
  128. template_name = 'dcim/site_list.html'
  129. def site(request, slug):
  130. site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug)
  131. stats = {
  132. 'rack_count': Rack.objects.filter(site=site).count(),
  133. 'device_count': Device.objects.filter(site=site).count(),
  134. 'prefix_count': Prefix.objects.filter(site=site).count(),
  135. 'vlan_count': VLAN.objects.filter(site=site).count(),
  136. 'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
  137. }
  138. rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
  139. topology_maps = TopologyMap.objects.filter(site=site)
  140. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists()
  141. return render(request, 'dcim/site.html', {
  142. 'site': site,
  143. 'stats': stats,
  144. 'rack_groups': rack_groups,
  145. 'topology_maps': topology_maps,
  146. 'show_graphs': show_graphs,
  147. })
  148. class SiteEditView(PermissionRequiredMixin, ObjectEditView):
  149. permission_required = 'dcim.change_site'
  150. model = Site
  151. form_class = forms.SiteForm
  152. template_name = 'dcim/site_edit.html'
  153. default_return_url = 'dcim:site_list'
  154. class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  155. permission_required = 'dcim.delete_site'
  156. model = Site
  157. default_return_url = 'dcim:site_list'
  158. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  159. permission_required = 'dcim.add_site'
  160. form = forms.SiteImportForm
  161. table = tables.SiteTable
  162. template_name = 'dcim/site_import.html'
  163. default_return_url = 'dcim:site_list'
  164. class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
  165. permission_required = 'dcim.change_site'
  166. cls = Site
  167. filter = filters.SiteFilter
  168. form = forms.SiteBulkEditForm
  169. template_name = 'dcim/site_bulk_edit.html'
  170. default_return_url = 'dcim:site_list'
  171. #
  172. # Rack groups
  173. #
  174. class RackGroupListView(ObjectListView):
  175. queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
  176. filter = filters.RackGroupFilter
  177. filter_form = forms.RackGroupFilterForm
  178. table = tables.RackGroupTable
  179. template_name = 'dcim/rackgroup_list.html'
  180. class RackGroupEditView(PermissionRequiredMixin, ObjectEditView):
  181. permission_required = 'dcim.change_rackgroup'
  182. model = RackGroup
  183. form_class = forms.RackGroupForm
  184. def get_return_url(self, obj):
  185. return reverse('dcim:rackgroup_list')
  186. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  187. permission_required = 'dcim.delete_rackgroup'
  188. cls = RackGroup
  189. filter = filters.RackGroupFilter
  190. default_return_url = 'dcim:rackgroup_list'
  191. #
  192. # Rack roles
  193. #
  194. class RackRoleListView(ObjectListView):
  195. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  196. table = tables.RackRoleTable
  197. template_name = 'dcim/rackrole_list.html'
  198. class RackRoleEditView(PermissionRequiredMixin, ObjectEditView):
  199. permission_required = 'dcim.change_rackrole'
  200. model = RackRole
  201. form_class = forms.RackRoleForm
  202. def get_return_url(self, obj):
  203. return reverse('dcim:rackrole_list')
  204. class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  205. permission_required = 'dcim.delete_rackrole'
  206. cls = RackRole
  207. default_return_url = 'dcim:rackrole_list'
  208. #
  209. # Racks
  210. #
  211. class RackListView(ObjectListView):
  212. queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role').prefetch_related('devices__device_type')\
  213. .annotate(device_count=Count('devices', distinct=True))
  214. filter = filters.RackFilter
  215. filter_form = forms.RackFilterForm
  216. table = tables.RackTable
  217. template_name = 'dcim/rack_list.html'
  218. def rack(request, pk):
  219. rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
  220. nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True)\
  221. .select_related('device_type__manufacturer')
  222. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  223. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  224. reservations = RackReservation.objects.filter(rack=rack)
  225. reserved_units = {}
  226. for r in reservations:
  227. for u in r.units:
  228. reserved_units[u] = r
  229. return render(request, 'dcim/rack.html', {
  230. 'rack': rack,
  231. 'reservations': reservations,
  232. 'reserved_units': reserved_units,
  233. 'nonracked_devices': nonracked_devices,
  234. 'next_rack': next_rack,
  235. 'prev_rack': prev_rack,
  236. 'front_elevation': rack.get_front_elevation(),
  237. 'rear_elevation': rack.get_rear_elevation(),
  238. })
  239. class RackEditView(PermissionRequiredMixin, ObjectEditView):
  240. permission_required = 'dcim.change_rack'
  241. model = Rack
  242. form_class = forms.RackForm
  243. template_name = 'dcim/rack_edit.html'
  244. default_return_url = 'dcim:rack_list'
  245. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  246. permission_required = 'dcim.delete_rack'
  247. model = Rack
  248. default_return_url = 'dcim:rack_list'
  249. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  250. permission_required = 'dcim.add_rack'
  251. form = forms.RackImportForm
  252. table = tables.RackImportTable
  253. template_name = 'dcim/rack_import.html'
  254. default_return_url = 'dcim:rack_list'
  255. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  256. permission_required = 'dcim.change_rack'
  257. cls = Rack
  258. filter = filters.RackFilter
  259. form = forms.RackBulkEditForm
  260. template_name = 'dcim/rack_bulk_edit.html'
  261. default_return_url = 'dcim:rack_list'
  262. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  263. permission_required = 'dcim.delete_rack'
  264. cls = Rack
  265. filter = filters.RackFilter
  266. default_return_url = 'dcim:rack_list'
  267. #
  268. # Rack reservations
  269. #
  270. class RackReservationEditView(PermissionRequiredMixin, ObjectEditView):
  271. permission_required = 'dcim.change_rackreservation'
  272. model = RackReservation
  273. form_class = forms.RackReservationForm
  274. def alter_obj(self, obj, request, args, kwargs):
  275. if not obj.pk:
  276. obj.rack = get_object_or_404(Rack, pk=kwargs['rack'])
  277. obj.user = request.user
  278. return obj
  279. def get_return_url(self, obj):
  280. return obj.rack.get_absolute_url()
  281. class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  282. permission_required = 'dcim.delete_rackreservation'
  283. model = RackReservation
  284. def get_return_url(self, obj):
  285. return obj.rack.get_absolute_url()
  286. #
  287. # Manufacturers
  288. #
  289. class ManufacturerListView(ObjectListView):
  290. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  291. table = tables.ManufacturerTable
  292. template_name = 'dcim/manufacturer_list.html'
  293. class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
  294. permission_required = 'dcim.change_manufacturer'
  295. model = Manufacturer
  296. form_class = forms.ManufacturerForm
  297. def get_return_url(self, obj):
  298. return reverse('dcim:manufacturer_list')
  299. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  300. permission_required = 'dcim.delete_manufacturer'
  301. cls = Manufacturer
  302. default_return_url = 'dcim:manufacturer_list'
  303. #
  304. # Device types
  305. #
  306. class DeviceTypeListView(ObjectListView):
  307. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  308. filter = filters.DeviceTypeFilter
  309. filter_form = forms.DeviceTypeFilterForm
  310. table = tables.DeviceTypeTable
  311. template_name = 'dcim/devicetype_list.html'
  312. def devicetype(request, pk):
  313. devicetype = get_object_or_404(DeviceType, pk=pk)
  314. # Component tables
  315. consoleport_table = tables.ConsolePortTemplateTable(
  316. natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  317. )
  318. consoleserverport_table = tables.ConsoleServerPortTemplateTable(
  319. natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  320. )
  321. powerport_table = tables.PowerPortTemplateTable(
  322. natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  323. )
  324. poweroutlet_table = tables.PowerOutletTemplateTable(
  325. natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  326. )
  327. mgmt_interface_table = tables.InterfaceTemplateTable(
  328. list(InterfaceTemplate.objects.order_naturally(devicetype.interface_ordering).filter(device_type=devicetype,
  329. mgmt_only=True))
  330. )
  331. interface_table = tables.InterfaceTemplateTable(
  332. list(InterfaceTemplate.objects.order_naturally(devicetype.interface_ordering).filter(device_type=devicetype,
  333. mgmt_only=False))
  334. )
  335. devicebay_table = tables.DeviceBayTemplateTable(
  336. natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  337. )
  338. if request.user.has_perm('dcim.change_devicetype'):
  339. consoleport_table.base_columns['pk'].visible = True
  340. consoleserverport_table.base_columns['pk'].visible = True
  341. powerport_table.base_columns['pk'].visible = True
  342. poweroutlet_table.base_columns['pk'].visible = True
  343. mgmt_interface_table.base_columns['pk'].visible = True
  344. interface_table.base_columns['pk'].visible = True
  345. devicebay_table.base_columns['pk'].visible = True
  346. return render(request, 'dcim/devicetype.html', {
  347. 'devicetype': devicetype,
  348. 'consoleport_table': consoleport_table,
  349. 'consoleserverport_table': consoleserverport_table,
  350. 'powerport_table': powerport_table,
  351. 'poweroutlet_table': poweroutlet_table,
  352. 'mgmt_interface_table': mgmt_interface_table,
  353. 'interface_table': interface_table,
  354. 'devicebay_table': devicebay_table,
  355. })
  356. class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
  357. permission_required = 'dcim.change_devicetype'
  358. model = DeviceType
  359. form_class = forms.DeviceTypeForm
  360. template_name = 'dcim/devicetype_edit.html'
  361. default_return_url = 'dcim:devicetype_list'
  362. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  363. permission_required = 'dcim.delete_devicetype'
  364. model = DeviceType
  365. default_return_url = 'dcim:devicetype_list'
  366. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  367. permission_required = 'dcim.change_devicetype'
  368. cls = DeviceType
  369. filter = filters.DeviceTypeFilter
  370. form = forms.DeviceTypeBulkEditForm
  371. template_name = 'dcim/devicetype_bulk_edit.html'
  372. default_return_url = 'dcim:devicetype_list'
  373. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  374. permission_required = 'dcim.delete_devicetype'
  375. cls = DeviceType
  376. filter = filters.DeviceTypeFilter
  377. default_return_url = 'dcim:devicetype_list'
  378. #
  379. # Device type components
  380. #
  381. class ConsolePortTemplateAddView(PermissionRequiredMixin, ComponentCreateView):
  382. permission_required = 'dcim.add_consoleporttemplate'
  383. parent_model = DeviceType
  384. parent_field = 'device_type'
  385. model = ConsolePortTemplate
  386. form = forms.ConsolePortTemplateCreateForm
  387. model_form = forms.ConsolePortTemplateForm
  388. class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  389. permission_required = 'dcim.delete_consoleporttemplate'
  390. parent_model = DeviceType
  391. parent_field = 'device_type'
  392. cls = ConsolePortTemplate
  393. parent_cls = DeviceType
  394. class ConsoleServerPortTemplateAddView(PermissionRequiredMixin, ComponentCreateView):
  395. permission_required = 'dcim.add_consoleserverporttemplate'
  396. parent_model = DeviceType
  397. parent_field = 'device_type'
  398. model = ConsoleServerPortTemplate
  399. form = forms.ConsoleServerPortTemplateCreateForm
  400. model_form = forms.ConsoleServerPortTemplateForm
  401. class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  402. permission_required = 'dcim.delete_consoleserverporttemplate'
  403. cls = ConsoleServerPortTemplate
  404. parent_cls = DeviceType
  405. class PowerPortTemplateAddView(PermissionRequiredMixin, ComponentCreateView):
  406. permission_required = 'dcim.add_powerporttemplate'
  407. parent_model = DeviceType
  408. parent_field = 'device_type'
  409. model = PowerPortTemplate
  410. form = forms.PowerPortTemplateCreateForm
  411. model_form = forms.PowerPortTemplateForm
  412. class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  413. permission_required = 'dcim.delete_powerporttemplate'
  414. cls = PowerPortTemplate
  415. parent_cls = DeviceType
  416. class PowerOutletTemplateAddView(PermissionRequiredMixin, ComponentCreateView):
  417. permission_required = 'dcim.add_poweroutlettemplate'
  418. parent_model = DeviceType
  419. parent_field = 'device_type'
  420. model = PowerOutletTemplate
  421. form = forms.PowerOutletTemplateCreateForm
  422. model_form = forms.PowerOutletTemplateForm
  423. class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  424. permission_required = 'dcim.delete_poweroutlettemplate'
  425. cls = PowerOutletTemplate
  426. parent_cls = DeviceType
  427. class InterfaceTemplateAddView(PermissionRequiredMixin, ComponentCreateView):
  428. permission_required = 'dcim.add_interfacetemplate'
  429. parent_model = DeviceType
  430. parent_field = 'device_type'
  431. model = InterfaceTemplate
  432. form = forms.InterfaceTemplateCreateForm
  433. model_form = forms.InterfaceTemplateForm
  434. class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  435. permission_required = 'dcim.change_interfacetemplate'
  436. cls = InterfaceTemplate
  437. parent_cls = DeviceType
  438. form = forms.InterfaceTemplateBulkEditForm
  439. template_name = 'dcim/interfacetemplate_bulk_edit.html'
  440. class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  441. permission_required = 'dcim.delete_interfacetemplate'
  442. cls = InterfaceTemplate
  443. parent_cls = DeviceType
  444. class DeviceBayTemplateAddView(PermissionRequiredMixin, ComponentCreateView):
  445. permission_required = 'dcim.add_devicebaytemplate'
  446. parent_model = DeviceType
  447. parent_field = 'device_type'
  448. model = DeviceBayTemplate
  449. form = forms.DeviceBayTemplateCreateForm
  450. model_form = forms.DeviceBayTemplateForm
  451. class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  452. permission_required = 'dcim.delete_devicebaytemplate'
  453. cls = DeviceBayTemplate
  454. parent_cls = DeviceType
  455. #
  456. # Device roles
  457. #
  458. class DeviceRoleListView(ObjectListView):
  459. queryset = DeviceRole.objects.annotate(device_count=Count('devices'))
  460. table = tables.DeviceRoleTable
  461. template_name = 'dcim/devicerole_list.html'
  462. class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
  463. permission_required = 'dcim.change_devicerole'
  464. model = DeviceRole
  465. form_class = forms.DeviceRoleForm
  466. def get_return_url(self, obj):
  467. return reverse('dcim:devicerole_list')
  468. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  469. permission_required = 'dcim.delete_devicerole'
  470. cls = DeviceRole
  471. default_return_url = 'dcim:devicerole_list'
  472. #
  473. # Platforms
  474. #
  475. class PlatformListView(ObjectListView):
  476. queryset = Platform.objects.annotate(device_count=Count('devices'))
  477. table = tables.PlatformTable
  478. template_name = 'dcim/platform_list.html'
  479. class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
  480. permission_required = 'dcim.change_platform'
  481. model = Platform
  482. form_class = forms.PlatformForm
  483. def get_return_url(self, obj):
  484. return reverse('dcim:platform_list')
  485. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  486. permission_required = 'dcim.delete_platform'
  487. cls = Platform
  488. default_return_url = 'dcim:platform_list'
  489. #
  490. # Devices
  491. #
  492. class DeviceListView(ObjectListView):
  493. queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack',
  494. 'primary_ip4', 'primary_ip6')
  495. filter = filters.DeviceFilter
  496. filter_form = forms.DeviceFilterForm
  497. table = tables.DeviceTable
  498. template_name = 'dcim/device_list.html'
  499. def device(request, pk):
  500. device = get_object_or_404(Device.objects.select_related(
  501. 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
  502. ), pk=pk)
  503. console_ports = natsorted(
  504. ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
  505. )
  506. cs_ports = natsorted(
  507. ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
  508. )
  509. power_ports = natsorted(
  510. PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
  511. )
  512. power_outlets = natsorted(
  513. PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
  514. )
  515. interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
  516. .filter(device=device, mgmt_only=False)\
  517. .select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
  518. 'circuit_termination__circuit')
  519. mgmt_interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering)\
  520. .filter(device=device, mgmt_only=True)\
  521. .select_related('connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
  522. 'circuit_termination__circuit')
  523. device_bays = natsorted(
  524. DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
  525. key=attrgetter('name')
  526. )
  527. # Gather relevant device objects
  528. ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface', 'vrf')\
  529. .order_by('address')
  530. services = Service.objects.filter(device=device)
  531. secrets = device.secrets.all()
  532. # Find any related devices for convenient linking in the UI
  533. related_devices = []
  534. if device.name:
  535. if re.match('.+[0-9]+$', device.name):
  536. # Strip 1 or more trailing digits (e.g. core-switch1)
  537. base_name = re.match('(.*?)[0-9]+$', device.name).group(1)
  538. elif re.match('.+\d[a-z]$', device.name.lower()):
  539. # Strip a trailing letter if preceded by a digit (e.g. dist-switch3a -> dist-switch3)
  540. base_name = re.match('(.*\d+)[a-z]$', device.name.lower()).group(1)
  541. else:
  542. base_name = None
  543. if base_name:
  544. related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk)\
  545. .select_related('rack', 'device_type__manufacturer')[:10]
  546. # Show graph button on interfaces only if at least one graph has been created.
  547. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists()
  548. return render(request, 'dcim/device.html', {
  549. 'device': device,
  550. 'console_ports': console_ports,
  551. 'cs_ports': cs_ports,
  552. 'power_ports': power_ports,
  553. 'power_outlets': power_outlets,
  554. 'interfaces': interfaces,
  555. 'mgmt_interfaces': mgmt_interfaces,
  556. 'device_bays': device_bays,
  557. 'ip_addresses': ip_addresses,
  558. 'services': services,
  559. 'secrets': secrets,
  560. 'related_devices': related_devices,
  561. 'show_graphs': show_graphs,
  562. })
  563. class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
  564. permission_required = 'dcim.change_device'
  565. model = Device
  566. form_class = forms.DeviceForm
  567. fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
  568. template_name = 'dcim/device_edit.html'
  569. default_return_url = 'dcim:device_list'
  570. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  571. permission_required = 'dcim.delete_device'
  572. model = Device
  573. default_return_url = 'dcim:device_list'
  574. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  575. permission_required = 'dcim.add_device'
  576. form = forms.DeviceImportForm
  577. table = tables.DeviceImportTable
  578. template_name = 'dcim/device_import.html'
  579. default_return_url = 'dcim:device_list'
  580. class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  581. permission_required = 'dcim.add_device'
  582. form = forms.ChildDeviceImportForm
  583. table = tables.DeviceImportTable
  584. template_name = 'dcim/device_import_child.html'
  585. default_return_url = 'dcim:device_list'
  586. def save_obj(self, obj):
  587. # Inherent rack from parent device
  588. obj.rack = obj.parent_bay.device.rack
  589. obj.save()
  590. # Save the reverse relation
  591. device_bay = obj.parent_bay
  592. device_bay.installed_device = obj
  593. device_bay.save()
  594. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  595. permission_required = 'dcim.change_device'
  596. cls = Device
  597. filter = filters.DeviceFilter
  598. form = forms.DeviceBulkEditForm
  599. template_name = 'dcim/device_bulk_edit.html'
  600. default_return_url = 'dcim:device_list'
  601. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  602. permission_required = 'dcim.delete_device'
  603. cls = Device
  604. filter = filters.DeviceFilter
  605. default_return_url = 'dcim:device_list'
  606. def device_inventory(request, pk):
  607. device = get_object_or_404(Device, pk=pk)
  608. modules = Module.objects.filter(device=device, parent=None).select_related('manufacturer')\
  609. .prefetch_related('submodules')
  610. return render(request, 'dcim/device_inventory.html', {
  611. 'device': device,
  612. 'modules': modules,
  613. })
  614. def device_lldp_neighbors(request, pk):
  615. device = get_object_or_404(Device, pk=pk)
  616. interfaces = Interface.objects.order_naturally(device.device_type.interface_ordering).filter(device=device)\
  617. .select_related('connected_as_a', 'connected_as_b')
  618. return render(request, 'dcim/device_lldp_neighbors.html', {
  619. 'device': device,
  620. 'interfaces': interfaces,
  621. })
  622. #
  623. # Console ports
  624. #
  625. class ConsolePortAddView(PermissionRequiredMixin, ComponentCreateView):
  626. permission_required = 'dcim.add_consoleport'
  627. parent_model = Device
  628. parent_field = 'device'
  629. model = ConsolePort
  630. form = forms.ConsolePortCreateForm
  631. model_form = forms.ConsolePortForm
  632. @permission_required('dcim.change_consoleport')
  633. def consoleport_connect(request, pk):
  634. consoleport = get_object_or_404(ConsolePort, pk=pk)
  635. if request.method == 'POST':
  636. form = forms.ConsolePortConnectionForm(request.POST, instance=consoleport)
  637. if form.is_valid():
  638. consoleport = form.save()
  639. messages.success(request, u"Connected {} {} to {} {}.".format(
  640. consoleport.device,
  641. consoleport.name,
  642. consoleport.cs_port.device,
  643. consoleport.cs_port.name,
  644. ))
  645. return redirect('dcim:device', pk=consoleport.device.pk)
  646. else:
  647. form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
  648. 'rack': consoleport.device.rack,
  649. 'connection_status': CONNECTION_STATUS_CONNECTED,
  650. })
  651. return render(request, 'dcim/consoleport_connect.html', {
  652. 'consoleport': consoleport,
  653. 'form': form,
  654. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  655. })
  656. @permission_required('dcim.change_consoleport')
  657. def consoleport_disconnect(request, pk):
  658. consoleport = get_object_or_404(ConsolePort, pk=pk)
  659. if not consoleport.cs_port:
  660. messages.warning(request, u"Cannot disconnect console port {}: It is not connected to anything."
  661. .format(consoleport))
  662. return redirect('dcim:device', pk=consoleport.device.pk)
  663. if request.method == 'POST':
  664. form = ConfirmationForm(request.POST)
  665. if form.is_valid():
  666. consoleport.cs_port = None
  667. consoleport.connection_status = None
  668. consoleport.save()
  669. messages.success(request, u"Console port {} has been disconnected.".format(consoleport))
  670. return redirect('dcim:device', pk=consoleport.device.pk)
  671. else:
  672. form = ConfirmationForm()
  673. return render(request, 'dcim/consoleport_disconnect.html', {
  674. 'consoleport': consoleport,
  675. 'form': form,
  676. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  677. })
  678. class ConsolePortEditView(PermissionRequiredMixin, ComponentEditView):
  679. permission_required = 'dcim.change_consoleport'
  680. model = ConsolePort
  681. form_class = forms.ConsolePortForm
  682. class ConsolePortDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  683. permission_required = 'dcim.delete_consoleport'
  684. model = ConsolePort
  685. class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  686. permission_required = 'dcim.delete_consoleport'
  687. cls = ConsolePort
  688. parent_cls = Device
  689. class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  690. permission_required = 'dcim.change_consoleport'
  691. form = forms.ConsoleConnectionImportForm
  692. table = tables.ConsoleConnectionTable
  693. template_name = 'dcim/console_connections_import.html'
  694. #
  695. # Console server ports
  696. #
  697. class ConsoleServerPortAddView(PermissionRequiredMixin, ComponentCreateView):
  698. permission_required = 'dcim.add_consoleserverport'
  699. parent_model = Device
  700. parent_field = 'device'
  701. model = ConsoleServerPort
  702. form = forms.ConsoleServerPortCreateForm
  703. model_form = forms.ConsoleServerPortForm
  704. @permission_required('dcim.change_consoleserverport')
  705. def consoleserverport_connect(request, pk):
  706. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  707. if request.method == 'POST':
  708. form = forms.ConsoleServerPortConnectionForm(consoleserverport, request.POST)
  709. if form.is_valid():
  710. consoleport = form.cleaned_data['port']
  711. consoleport.cs_port = consoleserverport
  712. consoleport.connection_status = form.cleaned_data['connection_status']
  713. consoleport.save()
  714. messages.success(request, u"Connected {} {} to {} {}.".format(
  715. consoleport.device,
  716. consoleport.name,
  717. consoleserverport.device,
  718. consoleserverport.name,
  719. ))
  720. return redirect('dcim:device', pk=consoleserverport.device.pk)
  721. else:
  722. form = forms.ConsoleServerPortConnectionForm(consoleserverport, initial={'rack': consoleserverport.device.rack})
  723. return render(request, 'dcim/consoleserverport_connect.html', {
  724. 'consoleserverport': consoleserverport,
  725. 'form': form,
  726. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  727. })
  728. @permission_required('dcim.change_consoleserverport')
  729. def consoleserverport_disconnect(request, pk):
  730. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  731. if not hasattr(consoleserverport, 'connected_console'):
  732. messages.warning(request, u"Cannot disconnect console server port {}: Nothing is connected to it."
  733. .format(consoleserverport))
  734. return redirect('dcim:device', pk=consoleserverport.device.pk)
  735. if request.method == 'POST':
  736. form = ConfirmationForm(request.POST)
  737. if form.is_valid():
  738. consoleport = consoleserverport.connected_console
  739. consoleport.cs_port = None
  740. consoleport.connection_status = None
  741. consoleport.save()
  742. messages.success(request, u"Console server port {} has been disconnected.".format(consoleserverport))
  743. return redirect('dcim:device', pk=consoleserverport.device.pk)
  744. else:
  745. form = ConfirmationForm()
  746. return render(request, 'dcim/consoleserverport_disconnect.html', {
  747. 'consoleserverport': consoleserverport,
  748. 'form': form,
  749. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  750. })
  751. class ConsoleServerPortEditView(PermissionRequiredMixin, ComponentEditView):
  752. permission_required = 'dcim.change_consoleserverport'
  753. model = ConsoleServerPort
  754. form_class = forms.ConsoleServerPortForm
  755. class ConsoleServerPortDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  756. permission_required = 'dcim.delete_consoleserverport'
  757. model = ConsoleServerPort
  758. class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  759. permission_required = 'dcim.delete_consoleserverport'
  760. cls = ConsoleServerPort
  761. parent_cls = Device
  762. #
  763. # Power ports
  764. #
  765. class PowerPortAddView(PermissionRequiredMixin, ComponentCreateView):
  766. permission_required = 'dcim.add_powerport'
  767. parent_model = Device
  768. parent_field = 'device'
  769. model = PowerPort
  770. form = forms.PowerPortCreateForm
  771. model_form = forms.PowerPortForm
  772. @permission_required('dcim.change_powerport')
  773. def powerport_connect(request, pk):
  774. powerport = get_object_or_404(PowerPort, pk=pk)
  775. if request.method == 'POST':
  776. form = forms.PowerPortConnectionForm(request.POST, instance=powerport)
  777. if form.is_valid():
  778. powerport = form.save()
  779. messages.success(request, u"Connected {} {} to {} {}.".format(
  780. powerport.device,
  781. powerport.name,
  782. powerport.power_outlet.device,
  783. powerport.power_outlet.name,
  784. ))
  785. return redirect('dcim:device', pk=powerport.device.pk)
  786. else:
  787. form = forms.PowerPortConnectionForm(instance=powerport, initial={
  788. 'rack': powerport.device.rack,
  789. 'connection_status': CONNECTION_STATUS_CONNECTED,
  790. })
  791. return render(request, 'dcim/powerport_connect.html', {
  792. 'powerport': powerport,
  793. 'form': form,
  794. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  795. })
  796. @permission_required('dcim.change_powerport')
  797. def powerport_disconnect(request, pk):
  798. powerport = get_object_or_404(PowerPort, pk=pk)
  799. if not powerport.power_outlet:
  800. messages.warning(request, u"Cannot disconnect power port {}: It is not connected to an outlet."
  801. .format(powerport))
  802. return redirect('dcim:device', pk=powerport.device.pk)
  803. if request.method == 'POST':
  804. form = ConfirmationForm(request.POST)
  805. if form.is_valid():
  806. powerport.power_outlet = None
  807. powerport.connection_status = None
  808. powerport.save()
  809. messages.success(request, u"Power port {} has been disconnected.".format(powerport))
  810. return redirect('dcim:device', pk=powerport.device.pk)
  811. else:
  812. form = ConfirmationForm()
  813. return render(request, 'dcim/powerport_disconnect.html', {
  814. 'powerport': powerport,
  815. 'form': form,
  816. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  817. })
  818. class PowerPortEditView(PermissionRequiredMixin, ComponentEditView):
  819. permission_required = 'dcim.change_powerport'
  820. model = PowerPort
  821. form_class = forms.PowerPortForm
  822. class PowerPortDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  823. permission_required = 'dcim.delete_powerport'
  824. model = PowerPort
  825. class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  826. permission_required = 'dcim.delete_powerport'
  827. cls = PowerPort
  828. parent_cls = Device
  829. class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  830. permission_required = 'dcim.change_powerport'
  831. form = forms.PowerConnectionImportForm
  832. table = tables.PowerConnectionTable
  833. template_name = 'dcim/power_connections_import.html'
  834. #
  835. # Power outlets
  836. #
  837. class PowerOutletAddView(PermissionRequiredMixin, ComponentCreateView):
  838. permission_required = 'dcim.add_poweroutlet'
  839. parent_model = Device
  840. parent_field = 'device'
  841. model = PowerOutlet
  842. form = forms.PowerOutletCreateForm
  843. model_form = forms.PowerOutletForm
  844. @permission_required('dcim.change_poweroutlet')
  845. def poweroutlet_connect(request, pk):
  846. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  847. if request.method == 'POST':
  848. form = forms.PowerOutletConnectionForm(poweroutlet, request.POST)
  849. if form.is_valid():
  850. powerport = form.cleaned_data['port']
  851. powerport.power_outlet = poweroutlet
  852. powerport.connection_status = form.cleaned_data['connection_status']
  853. powerport.save()
  854. messages.success(request, u"Connected {} {} to {} {}.".format(
  855. powerport.device,
  856. powerport.name,
  857. poweroutlet.device,
  858. poweroutlet.name,
  859. ))
  860. return redirect('dcim:device', pk=poweroutlet.device.pk)
  861. else:
  862. form = forms.PowerOutletConnectionForm(poweroutlet, initial={'rack': poweroutlet.device.rack})
  863. return render(request, 'dcim/poweroutlet_connect.html', {
  864. 'poweroutlet': poweroutlet,
  865. 'form': form,
  866. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  867. })
  868. @permission_required('dcim.change_poweroutlet')
  869. def poweroutlet_disconnect(request, pk):
  870. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  871. if not hasattr(poweroutlet, 'connected_port'):
  872. messages.warning(request, u"Cannot disconnect power outlet {}: Nothing is connected to it.".format(poweroutlet))
  873. return redirect('dcim:device', pk=poweroutlet.device.pk)
  874. if request.method == 'POST':
  875. form = ConfirmationForm(request.POST)
  876. if form.is_valid():
  877. powerport = poweroutlet.connected_port
  878. powerport.power_outlet = None
  879. powerport.connection_status = None
  880. powerport.save()
  881. messages.success(request, u"Power outlet {} has been disconnected.".format(poweroutlet))
  882. return redirect('dcim:device', pk=poweroutlet.device.pk)
  883. else:
  884. form = ConfirmationForm()
  885. return render(request, 'dcim/poweroutlet_disconnect.html', {
  886. 'poweroutlet': poweroutlet,
  887. 'form': form,
  888. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  889. })
  890. class PowerOutletEditView(PermissionRequiredMixin, ComponentEditView):
  891. permission_required = 'dcim.change_poweroutlet'
  892. model = PowerOutlet
  893. form_class = forms.PowerOutletForm
  894. class PowerOutletDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  895. permission_required = 'dcim.delete_poweroutlet'
  896. model = PowerOutlet
  897. class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  898. permission_required = 'dcim.delete_poweroutlet'
  899. cls = PowerOutlet
  900. parent_cls = Device
  901. #
  902. # Interfaces
  903. #
  904. class InterfaceAddView(PermissionRequiredMixin, ComponentCreateView):
  905. permission_required = 'dcim.add_interface'
  906. parent_model = Device
  907. parent_field = 'device'
  908. model = Interface
  909. form = forms.InterfaceCreateForm
  910. model_form = forms.InterfaceForm
  911. class InterfaceEditView(PermissionRequiredMixin, ComponentEditView):
  912. permission_required = 'dcim.change_interface'
  913. model = Interface
  914. form_class = forms.InterfaceForm
  915. class InterfaceDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  916. permission_required = 'dcim.delete_interface'
  917. model = Interface
  918. class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
  919. permission_required = 'dcim.change_interface'
  920. cls = Interface
  921. parent_cls = Device
  922. form = forms.InterfaceBulkEditForm
  923. template_name = 'dcim/interface_bulk_edit.html'
  924. class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  925. permission_required = 'dcim.delete_interface'
  926. cls = Interface
  927. parent_cls = Device
  928. #
  929. # Device bays
  930. #
  931. class DeviceBayAddView(PermissionRequiredMixin, ComponentCreateView):
  932. permission_required = 'dcim.add_devicebay'
  933. parent_model = Device
  934. parent_field = 'device'
  935. model = DeviceBay
  936. form = forms.DeviceBayCreateForm
  937. model_form = forms.DeviceBayForm
  938. class DeviceBayEditView(PermissionRequiredMixin, ComponentEditView):
  939. permission_required = 'dcim.change_devicebay'
  940. model = DeviceBay
  941. form_class = forms.DeviceBayForm
  942. class DeviceBayDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  943. permission_required = 'dcim.delete_devicebay'
  944. model = DeviceBay
  945. @permission_required('dcim.change_devicebay')
  946. def devicebay_populate(request, pk):
  947. device_bay = get_object_or_404(DeviceBay, pk=pk)
  948. if request.method == 'POST':
  949. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  950. if form.is_valid():
  951. device_bay.installed_device = form.cleaned_data['installed_device']
  952. device_bay.save()
  953. if not form.errors:
  954. messages.success(request, u"Added {} to {}.".format(device_bay.installed_device, device_bay))
  955. return redirect('dcim:device', pk=device_bay.device.pk)
  956. else:
  957. form = forms.PopulateDeviceBayForm(device_bay)
  958. return render(request, 'dcim/devicebay_populate.html', {
  959. 'device_bay': device_bay,
  960. 'form': form,
  961. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  962. })
  963. @permission_required('dcim.change_devicebay')
  964. def devicebay_depopulate(request, pk):
  965. device_bay = get_object_or_404(DeviceBay, pk=pk)
  966. if request.method == 'POST':
  967. form = ConfirmationForm(request.POST)
  968. if form.is_valid():
  969. removed_device = device_bay.installed_device
  970. device_bay.installed_device = None
  971. device_bay.save()
  972. messages.success(request, u"{} has been removed from {}.".format(removed_device, device_bay))
  973. return redirect('dcim:device', pk=device_bay.device.pk)
  974. else:
  975. form = ConfirmationForm()
  976. return render(request, 'dcim/devicebay_depopulate.html', {
  977. 'device_bay': device_bay,
  978. 'form': form,
  979. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  980. })
  981. class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  982. permission_required = 'dcim.delete_devicebay'
  983. cls = DeviceBay
  984. parent_cls = Device
  985. #
  986. # Bulk device component creation
  987. #
  988. class DeviceBulkAddComponentView(View):
  989. """
  990. Add one or more components (e.g. interfaces) to a selected set of Devices.
  991. """
  992. form = forms.DeviceBulkAddComponentForm
  993. model = None
  994. model_form = None
  995. def get(self):
  996. return redirect('dcim:device_list')
  997. def post(self, request):
  998. # Are we editing *all* objects in the queryset or just a selected subset?
  999. if request.POST.get('_all'):
  1000. pk_list = [obj.pk for obj in filters.DeviceFilter(request.GET, Device.objects.all())]
  1001. else:
  1002. pk_list = [int(pk) for pk in request.POST.getlist('pk')]
  1003. if '_create' in request.POST:
  1004. form = self.form(request.POST)
  1005. if form.is_valid():
  1006. new_components = []
  1007. data = deepcopy(form.cleaned_data)
  1008. for device in data['pk']:
  1009. names = data['name_pattern']
  1010. for name in names:
  1011. component_data = {
  1012. 'device': device.pk,
  1013. 'name': name,
  1014. }
  1015. component_data.update(data)
  1016. component_form = self.model_form(component_data)
  1017. if component_form.is_valid():
  1018. new_components.append(component_form.save(commit=False))
  1019. else:
  1020. for field, errors in component_form.errors.as_data().items():
  1021. for e in errors:
  1022. form.add_error(field, u'{} {}: {}'.format(device, name, ', '.join(e)))
  1023. if not form.errors:
  1024. self.model.objects.bulk_create(new_components)
  1025. messages.success(request, u"Added {} {} to {} devices.".format(
  1026. len(new_components), self.model._meta.verbose_name_plural, len(form.cleaned_data['pk'])
  1027. ))
  1028. return redirect('dcim:device_list')
  1029. else:
  1030. form = self.form(initial={'pk': pk_list})
  1031. selected_devices = Device.objects.filter(pk__in=pk_list)
  1032. if not selected_devices:
  1033. messages.warning(request, u"No devices were selected.")
  1034. return redirect('dcim:device_list')
  1035. return render(request, 'dcim/device_bulk_add_component.html', {
  1036. 'form': form,
  1037. 'component_name': self.model._meta.verbose_name_plural,
  1038. 'selected_devices': selected_devices,
  1039. 'return_url': reverse('dcim:device_list'),
  1040. })
  1041. class DeviceBulkAddConsolePortView(PermissionRequiredMixin, DeviceBulkAddComponentView):
  1042. permission_required = 'dcim.add_consoleport'
  1043. model = ConsolePort
  1044. model_form = forms.ConsolePortForm
  1045. class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, DeviceBulkAddComponentView):
  1046. permission_required = 'dcim.add_consoleserverport'
  1047. model = ConsoleServerPort
  1048. model_form = forms.ConsoleServerPortForm
  1049. class DeviceBulkAddPowerPortView(PermissionRequiredMixin, DeviceBulkAddComponentView):
  1050. permission_required = 'dcim.add_powerport'
  1051. model = PowerPort
  1052. model_form = forms.PowerPortForm
  1053. class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, DeviceBulkAddComponentView):
  1054. permission_required = 'dcim.add_poweroutlet'
  1055. model = PowerOutlet
  1056. model_form = forms.PowerOutletForm
  1057. class DeviceBulkAddInterfaceView(PermissionRequiredMixin, DeviceBulkAddComponentView):
  1058. permission_required = 'dcim.add_interface'
  1059. form = forms.DeviceBulkAddInterfaceForm
  1060. model = Interface
  1061. model_form = forms.InterfaceForm
  1062. class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, DeviceBulkAddComponentView):
  1063. permission_required = 'dcim.add_devicebay'
  1064. model = DeviceBay
  1065. model_form = forms.DeviceBayForm
  1066. #
  1067. # Interface connections
  1068. #
  1069. @permission_required('dcim.add_interfaceconnection')
  1070. def interfaceconnection_add(request, pk):
  1071. device = get_object_or_404(Device, pk=pk)
  1072. if request.method == 'POST':
  1073. form = forms.InterfaceConnectionForm(device, request.POST)
  1074. if form.is_valid():
  1075. interfaceconnection = form.save()
  1076. messages.success(request, u"Connected {} {} to {} {}.".format(
  1077. interfaceconnection.interface_a.device,
  1078. interfaceconnection.interface_a,
  1079. interfaceconnection.interface_b.device,
  1080. interfaceconnection.interface_b,
  1081. ))
  1082. if '_addanother' in request.POST:
  1083. base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
  1084. params = urlencode({
  1085. 'rack_b': interfaceconnection.interface_b.device.rack.pk,
  1086. 'device_b': interfaceconnection.interface_b.device.pk,
  1087. })
  1088. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  1089. else:
  1090. return redirect('dcim:device', pk=device.pk)
  1091. else:
  1092. form = forms.InterfaceConnectionForm(device, initial={
  1093. 'interface_a': request.GET.get('interface_a', None),
  1094. 'site_b': request.GET.get('site_b', device.site),
  1095. 'rack_b': request.GET.get('rack_b', None),
  1096. 'device_b': request.GET.get('device_b', None),
  1097. 'interface_b': request.GET.get('interface_b', None),
  1098. })
  1099. return render(request, 'dcim/interfaceconnection_edit.html', {
  1100. 'device': device,
  1101. 'form': form,
  1102. 'return_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1103. })
  1104. @permission_required('dcim.delete_interfaceconnection')
  1105. def interfaceconnection_delete(request, pk):
  1106. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1107. device_id = request.GET.get('device', None)
  1108. if request.method == 'POST':
  1109. form = forms.InterfaceConnectionDeletionForm(request.POST)
  1110. if form.is_valid():
  1111. interfaceconnection.delete()
  1112. messages.success(request, u"Deleted the connection between {} {} and {} {}.".format(
  1113. interfaceconnection.interface_a.device,
  1114. interfaceconnection.interface_a,
  1115. interfaceconnection.interface_b.device,
  1116. interfaceconnection.interface_b,
  1117. ))
  1118. if form.cleaned_data['device']:
  1119. return redirect('dcim:device', pk=form.cleaned_data['device'].pk)
  1120. else:
  1121. return redirect('dcim:device_list')
  1122. else:
  1123. form = forms.InterfaceConnectionDeletionForm(initial={
  1124. 'device': device_id,
  1125. })
  1126. # Determine where to direct user upon cancellation
  1127. if device_id:
  1128. return_url = reverse('dcim:device', kwargs={'pk': device_id})
  1129. else:
  1130. return_url = reverse('dcim:device_list')
  1131. return render(request, 'dcim/interfaceconnection_delete.html', {
  1132. 'interfaceconnection': interfaceconnection,
  1133. 'device_id': device_id,
  1134. 'form': form,
  1135. 'return_url': return_url,
  1136. })
  1137. class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1138. permission_required = 'dcim.change_interface'
  1139. form = forms.InterfaceConnectionImportForm
  1140. table = tables.InterfaceConnectionTable
  1141. template_name = 'dcim/interface_connections_import.html'
  1142. #
  1143. # Connections
  1144. #
  1145. class ConsoleConnectionsListView(ObjectListView):
  1146. queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\
  1147. .order_by('cs_port__device__name', 'cs_port__name')
  1148. filter = filters.ConsoleConnectionFilter
  1149. filter_form = forms.ConsoleConnectionFilterForm
  1150. table = tables.ConsoleConnectionTable
  1151. template_name = 'dcim/console_connections_list.html'
  1152. class PowerConnectionsListView(ObjectListView):
  1153. queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\
  1154. .order_by('power_outlet__device__name', 'power_outlet__name')
  1155. filter = filters.PowerConnectionFilter
  1156. filter_form = forms.PowerConnectionFilterForm
  1157. table = tables.PowerConnectionTable
  1158. template_name = 'dcim/power_connections_list.html'
  1159. class InterfaceConnectionsListView(ObjectListView):
  1160. queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\
  1161. .order_by('interface_a__device__name', 'interface_a__name')
  1162. filter = filters.InterfaceConnectionFilter
  1163. filter_form = forms.InterfaceConnectionFilterForm
  1164. table = tables.InterfaceConnectionTable
  1165. template_name = 'dcim/interface_connections_list.html'
  1166. #
  1167. # IP addresses
  1168. #
  1169. @permission_required(['dcim.change_device', 'ipam.add_ipaddress'])
  1170. def ipaddress_assign(request, pk):
  1171. device = get_object_or_404(Device, pk=pk)
  1172. if request.method == 'POST':
  1173. form = forms.IPAddressForm(device, request.POST)
  1174. if form.is_valid():
  1175. ipaddress = form.save(commit=False)
  1176. ipaddress.interface = form.cleaned_data['interface']
  1177. ipaddress.save()
  1178. form.save_custom_fields()
  1179. messages.success(request, u"Added new IP address {} to interface {}.".format(ipaddress, ipaddress.interface))
  1180. if form.cleaned_data['set_as_primary']:
  1181. if ipaddress.family == 4:
  1182. device.primary_ip4 = ipaddress
  1183. elif ipaddress.family == 6:
  1184. device.primary_ip6 = ipaddress
  1185. device.save()
  1186. if '_addanother' in request.POST:
  1187. return redirect('dcim:ipaddress_assign', pk=device.pk)
  1188. else:
  1189. return redirect('dcim:device', pk=device.pk)
  1190. else:
  1191. form = forms.IPAddressForm(device)
  1192. return render(request, 'dcim/ipaddress_assign.html', {
  1193. 'device': device,
  1194. 'form': form,
  1195. 'return_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1196. })
  1197. #
  1198. # Modules
  1199. #
  1200. class ModuleEditView(PermissionRequiredMixin, ComponentEditView):
  1201. permission_required = 'dcim.change_module'
  1202. model = Module
  1203. form_class = forms.ModuleForm
  1204. def alter_obj(self, obj, request, url_args, url_kwargs):
  1205. if 'device' in url_kwargs:
  1206. obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
  1207. return obj
  1208. class ModuleDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  1209. permission_required = 'dcim.delete_module'
  1210. model = Module