views.py 51 KB

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