views.py 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687
  1. import re
  2. from django.contrib import messages
  3. from django.contrib.auth.decorators import permission_required
  4. from django.contrib.auth.mixins import PermissionRequiredMixin
  5. from django.core.exceptions import ValidationError
  6. from django.core.urlresolvers import reverse
  7. from django.db.models import Count, ProtectedError
  8. from django.forms import ModelMultipleChoiceField, MultipleHiddenInput
  9. from django.http import HttpResponseRedirect
  10. from django.shortcuts import get_object_or_404, redirect, render
  11. from django.utils.http import urlencode
  12. from django.views.generic import View
  13. from django.views.generic.edit import CreateView, UpdateView
  14. from ipam.models import Prefix, IPAddress, VLAN
  15. from circuits.models import Circuit
  16. from extras.models import TopologyMap
  17. from utilities.error_handlers import handle_protectederror
  18. from utilities.forms import ConfirmationForm
  19. from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView
  20. from .filters import RackGroupFilter, RackFilter, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, \
  21. PowerConnectionFilter, InterfaceConnectionFilter
  22. from .forms import SiteForm, SiteImportForm, RackGroupFilterForm, RackGroupBulkDeleteForm, RackForm, RackImportForm, \
  23. RackBulkEditForm, RackBulkDeleteForm, RackFilterForm, DeviceTypeForm, DeviceTypeBulkEditForm, \
  24. DeviceTypeBulkDeleteForm, DeviceTypeFilterForm, DeviceForm, DeviceImportForm, DeviceBulkEditForm, \
  25. DeviceBulkDeleteForm, DeviceFilterForm, ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, \
  26. ConsoleConnectionImportForm, ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, \
  27. PowerPortForm, PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, \
  28. PowerOutletCreateForm, PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, \
  29. InterfaceConnectionForm, InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, \
  30. ConsoleConnectionFilterForm, PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm, \
  31. ConsolePortTemplateForm, ConsoleServerPortTemplateForm, PowerPortTemplateForm, PowerOutletTemplateForm, \
  32. InterfaceTemplateForm
  33. from .models import Site, RackGroup, Rack, DeviceType, ConsolePortTemplate, ConsoleServerPortTemplate, \
  34. PowerPortTemplate, PowerOutletTemplate, InterfaceTemplate, Device, ConsolePort, ConsoleServerPort, PowerPort, \
  35. PowerOutlet, Interface, InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED
  36. from .tables import SiteTable, RackGroupTable, RackGroupBulkEditTable, RackTable, RackBulkEditTable, DeviceTypeTable, \
  37. DeviceTypeBulkEditTable, DeviceTable, DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, \
  38. PowerConnectionTable, InterfaceConnectionTable, ConsolePortTemplateTable, ConsoleServerPortTemplateTable, \
  39. PowerPortTemplateTable, PowerOutletTemplateTable, InterfaceTemplateTable, ConsolePortTemplateBulkDeleteTable, \
  40. ConsoleServerPortTemplateBulkDeleteTable, PowerPortTemplateBulkDeleteTable, PowerOutletTemplateBulkDeleteTable, \
  41. InterfaceTemplateBulkDeleteTable
  42. EXPANSION_PATTERN = '\[(\d+-\d+)\]'
  43. def xstr(s):
  44. """
  45. Replace None with an empty string (for CSV export)
  46. """
  47. return '' if s is None else str(s)
  48. def expand_pattern(string):
  49. """
  50. Expand a numeric pattern into a list of strings. Examples:
  51. 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3']
  52. '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']
  53. """
  54. lead, pattern, remnant = re.split(EXPANSION_PATTERN, string, maxsplit=1)
  55. x, y = pattern.split('-')
  56. for i in range(int(x), int(y) + 1):
  57. if remnant:
  58. for string in expand_pattern(remnant):
  59. yield "{0}{1}{2}".format(lead, i, string)
  60. else:
  61. yield "{0}{1}".format(lead, i)
  62. #
  63. # Sites
  64. #
  65. class SiteListView(ObjectListView):
  66. queryset = Site.objects.all()
  67. table = SiteTable
  68. template_name = 'dcim/site_list.html'
  69. def site(request, slug):
  70. site = get_object_or_404(Site, slug=slug)
  71. stats = {
  72. 'rack_count': Rack.objects.filter(site=site).count(),
  73. 'device_count': Device.objects.filter(rack__site=site).count(),
  74. 'prefix_count': Prefix.objects.filter(site=site).count(),
  75. 'vlan_count': VLAN.objects.filter(site=site).count(),
  76. 'circuit_count': Circuit.objects.filter(site=site).count(),
  77. }
  78. topology_maps = TopologyMap.objects.filter(site=site)
  79. return render(request, 'dcim/site.html', {
  80. 'site': site,
  81. 'stats': stats,
  82. 'topology_maps': topology_maps,
  83. })
  84. @permission_required('dcim.add_site')
  85. def site_add(request):
  86. if request.method == 'POST':
  87. form = SiteForm(request.POST)
  88. if form.is_valid():
  89. site = form.save()
  90. messages.success(request, "Added new site: {0}".format(site.name))
  91. if '_addanother' in request.POST:
  92. return redirect('dcim:site_add')
  93. else:
  94. return redirect('dcim:site', slug=site.slug)
  95. else:
  96. form = SiteForm()
  97. return render(request, 'dcim/site_edit.html', {
  98. 'form': form,
  99. 'cancel_url': reverse('dcim:site_list'),
  100. })
  101. @permission_required('dcim.change_site')
  102. def site_edit(request, slug):
  103. site = get_object_or_404(Site, slug=slug)
  104. if request.method == 'POST':
  105. form = SiteForm(request.POST, instance=site)
  106. if form.is_valid():
  107. site = form.save()
  108. messages.success(request, "Modified site {0}".format(site.name))
  109. return redirect('dcim:site', slug=site.slug)
  110. else:
  111. form = SiteForm(instance=site)
  112. return render(request, 'dcim/site_edit.html', {
  113. 'site': site,
  114. 'form': form,
  115. 'cancel_url': reverse('dcim:site', kwargs={'slug': site.slug}),
  116. })
  117. @permission_required('dcim.delete_site')
  118. def site_delete(request, slug):
  119. site = get_object_or_404(Site, slug=slug)
  120. if request.method == 'POST':
  121. form = ConfirmationForm(request.POST)
  122. if form.is_valid():
  123. try:
  124. site.delete()
  125. messages.success(request, "Site {0} has been deleted".format(site))
  126. return redirect('dcim:site_list')
  127. except ProtectedError, e:
  128. handle_protectederror(site, request, e)
  129. return redirect('dcim:site', slug=site.slug)
  130. else:
  131. form = ConfirmationForm()
  132. return render(request, 'dcim/site_delete.html', {
  133. 'site': site,
  134. 'form': form,
  135. 'cancel_url': reverse('dcim:site', kwargs={'slug': site.slug}),
  136. })
  137. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  138. permission_required = 'dcim.add_site'
  139. form = SiteImportForm
  140. table = SiteTable
  141. template_name = 'dcim/site_import.html'
  142. obj_list_url = 'dcim:site_list'
  143. #
  144. # Rack groups
  145. #
  146. class RackGroupListView(ObjectListView):
  147. queryset = RackGroup.objects.all()
  148. filter = RackGroupFilter
  149. filter_form = RackGroupFilterForm
  150. table = RackGroupTable
  151. edit_table = RackGroupBulkEditTable
  152. edit_table_permissions = ['dcim.change_rackgroup', 'dcim.delete_rackgroup']
  153. template_name = 'dcim/rackgroup_list.html'
  154. class RackGroupAddView(PermissionRequiredMixin, CreateView):
  155. permission_required = 'dcim.add_rackgroup'
  156. model = RackGroup
  157. fields = ['site', 'name', 'slug']
  158. template_name = 'dcim/rackgroup_edit.html'
  159. class RackGroupEditView(PermissionRequiredMixin, UpdateView):
  160. permission_required = 'dcim.change_rackgroup'
  161. model = RackGroup
  162. fields = ['site', 'name', 'slug']
  163. template_name = 'dcim/rackgroup_edit.html'
  164. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  165. permission_required = 'dcim.delete_rackgroup'
  166. cls = RackGroup
  167. form = RackGroupBulkDeleteForm
  168. template_name = 'dcim/rackgroup_bulk_delete.html'
  169. default_redirect_url = 'dcim:rackgroup_list'
  170. #
  171. # Racks
  172. #
  173. class RackListView(ObjectListView):
  174. queryset = Rack.objects.select_related('site').annotate(device_count=Count('devices', distinct=True))
  175. filter = RackFilter
  176. filter_form = RackFilterForm
  177. table = RackTable
  178. edit_table = RackBulkEditTable
  179. edit_table_permissions = ['dcim.change_rack', 'dcim.delete_rack']
  180. template_name = 'dcim/rack_list.html'
  181. def rack(request, pk):
  182. rack = get_object_or_404(Rack, pk=pk)
  183. nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True)
  184. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  185. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  186. return render(request, 'dcim/rack.html', {
  187. 'rack': rack,
  188. 'nonracked_devices': nonracked_devices,
  189. 'next_rack': next_rack,
  190. 'prev_rack': prev_rack,
  191. 'front_elevation': rack.get_front_elevation(),
  192. 'rear_elevation': rack.get_rear_elevation(),
  193. })
  194. @permission_required('dcim.add_rack')
  195. def rack_add(request):
  196. if request.method == 'POST':
  197. form = RackForm(request.POST)
  198. if form.is_valid():
  199. rack = form.save()
  200. messages.success(request, "Added new rack to {}: {}".format(rack.site.name, rack))
  201. if '_addanother' in request.POST:
  202. base_url = reverse('dcim:rack_add')
  203. params = urlencode({
  204. 'site': rack.site.pk,
  205. })
  206. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  207. else:
  208. return redirect('dcim:rack', pk=rack.pk)
  209. else:
  210. form = RackForm()
  211. return render(request, 'dcim/rack_edit.html', {
  212. 'form': form,
  213. 'cancel_url': reverse('dcim:rack_list'),
  214. })
  215. @permission_required('dcim.change_rack')
  216. def rack_edit(request, pk):
  217. rack = get_object_or_404(Rack, pk=pk)
  218. if request.method == 'POST':
  219. form = RackForm(request.POST, instance=rack)
  220. if form.is_valid():
  221. rack = form.save()
  222. messages.success(request, "Modified rack {0}".format(rack.name))
  223. return redirect('dcim:rack', pk=rack.pk)
  224. else:
  225. form = RackForm(instance=rack)
  226. return render(request, 'dcim/rack_edit.html', {
  227. 'rack': rack,
  228. 'form': form,
  229. 'cancel_url': reverse('dcim:rack', kwargs={'pk': rack.pk}),
  230. })
  231. @permission_required('dcim.delete_rack')
  232. def rack_delete(request, pk):
  233. rack = get_object_or_404(Rack, pk=pk)
  234. if request.method == 'POST':
  235. form = ConfirmationForm(request.POST)
  236. if form.is_valid():
  237. try:
  238. rack.delete()
  239. messages.success(request, "Rack {0} has been deleted".format(rack))
  240. return redirect('dcim:rack_list')
  241. except ProtectedError, e:
  242. handle_protectederror(rack, request, e)
  243. return redirect('dcim:rack', pk=rack.pk)
  244. else:
  245. form = ConfirmationForm()
  246. return render(request, 'dcim/rack_delete.html', {
  247. 'rack': rack,
  248. 'form': form,
  249. 'cancel_url': reverse('dcim:rack', kwargs={'pk': rack.pk}),
  250. })
  251. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  252. permission_required = 'dcim.add_rack'
  253. form = RackImportForm
  254. table = RackTable
  255. template_name = 'dcim/rack_import.html'
  256. obj_list_url = 'dcim:rack_list'
  257. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  258. permission_required = 'dcim.change_rack'
  259. cls = Rack
  260. form = RackBulkEditForm
  261. template_name = 'dcim/rack_bulk_edit.html'
  262. default_redirect_url = 'dcim:rack_list'
  263. def update_objects(self, pk_list, form):
  264. fields_to_update = {}
  265. for field in ['site', 'group', 'u_height', 'comments']:
  266. if form.cleaned_data[field]:
  267. fields_to_update[field] = form.cleaned_data[field]
  268. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  269. messages.success(self.request, "Updated {} racks".format(updated_count))
  270. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  271. permission_required = 'dcim.delete_rack'
  272. cls = Rack
  273. form = RackBulkDeleteForm
  274. template_name = 'dcim/rack_bulk_delete.html'
  275. default_redirect_url = 'dcim:rack_list'
  276. #
  277. # Device types
  278. #
  279. class DeviceTypeListView(ObjectListView):
  280. queryset = DeviceType.objects.select_related('manufacturer')
  281. filter = DeviceTypeFilter
  282. filter_form = DeviceTypeFilterForm
  283. table = DeviceTypeTable
  284. edit_table = DeviceTypeBulkEditTable
  285. edit_table_permissions = ['dcim.change_devicetype', 'dcim.delete_devicetype']
  286. template_name = 'dcim/devicetype_list.html'
  287. def devicetype(request, pk):
  288. devicetype = get_object_or_404(DeviceType, pk=pk)
  289. # Component tables
  290. if request.user.has_perm('dcim.change_devicetype'):
  291. consoleport_table = ConsolePortTemplateBulkDeleteTable(ConsolePortTemplate.objects.filter(device_type=devicetype))
  292. consoleserverport_table = ConsoleServerPortTemplateBulkDeleteTable(ConsoleServerPortTemplate.objects.filter(device_type=devicetype))
  293. powerport_table = PowerPortTemplateBulkDeleteTable(PowerPortTemplate.objects.filter(device_type=devicetype))
  294. poweroutlet_table = PowerOutletTemplateBulkDeleteTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
  295. interface_table = InterfaceTemplateBulkDeleteTable(InterfaceTemplate.objects.filter(device_type=devicetype))
  296. else:
  297. consoleport_table = ConsolePortTemplateTable(ConsolePortTemplate.objects.filter(device_type=devicetype))
  298. consoleserverport_table = ConsoleServerPortTemplateTable(ConsoleServerPortTemplate.objects.filter(device_type=devicetype))
  299. powerport_table = PowerPortTemplateTable(PowerPortTemplate.objects.filter(device_type=devicetype))
  300. poweroutlet_table = PowerOutletTemplateTable(PowerOutletTemplate.objects.filter(device_type=devicetype))
  301. interface_table = InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype))
  302. return render(request, 'dcim/devicetype.html', {
  303. 'devicetype': devicetype,
  304. 'consoleport_table': consoleport_table,
  305. 'consoleserverport_table': consoleserverport_table,
  306. 'powerport_table': powerport_table,
  307. 'poweroutlet_table': poweroutlet_table,
  308. 'interface_table': interface_table,
  309. })
  310. @permission_required('dcim.add_devicetype')
  311. def devicetype_add(request):
  312. if request.method == 'POST':
  313. form = DeviceTypeForm(request.POST)
  314. if form.is_valid():
  315. devicetype = form.save()
  316. messages.success(request, "Added new device type: {}".format(devicetype))
  317. if '_addanother' in request.POST:
  318. return redirect('dcim:devicetype_add')
  319. else:
  320. return redirect('dcim:devicetype', pk=devicetype.pk)
  321. else:
  322. form = DeviceTypeForm()
  323. return render(request, 'dcim/devicetype_edit.html', {
  324. 'form': form,
  325. 'cancel_url': reverse('dcim:devicetype_list'),
  326. })
  327. @permission_required('dcim.change_devicetype')
  328. def devicetype_edit(request, pk):
  329. devicetype = get_object_or_404(DeviceType, pk=pk)
  330. if request.method == 'POST':
  331. form = DeviceTypeForm(request.POST, instance=devicetype)
  332. if form.is_valid():
  333. devicetype = form.save()
  334. messages.success(request, "Modified device type {}".format(devicetype))
  335. return redirect('dcim:devicetype', pk=devicetype.pk)
  336. else:
  337. form = DeviceTypeForm(instance=devicetype)
  338. return render(request, 'dcim/devicetype_edit.html', {
  339. 'devicetype': devicetype,
  340. 'form': form,
  341. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  342. })
  343. @permission_required('dcim.delete_devicetype')
  344. def devicetype_delete(request, pk):
  345. devicetype = get_object_or_404(DeviceType, pk=pk)
  346. if request.method == 'POST':
  347. form = ConfirmationForm(request.POST)
  348. if form.is_valid():
  349. try:
  350. devicetype.delete()
  351. messages.success(request, "Device type {} has been deleted".format(devicetype))
  352. return redirect('dcim:devicetype_list')
  353. except ProtectedError, e:
  354. handle_protectederror(devicetype, request, e)
  355. return redirect('dcim:devicetype', pk=devicetype.pk)
  356. else:
  357. form = ConfirmationForm()
  358. return render(request, 'dcim/devicetype_delete.html', {
  359. 'devicetype': device,
  360. 'form': form,
  361. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  362. })
  363. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  364. permission_required = 'dcim.change_devicetype'
  365. cls = DeviceType
  366. form = DeviceTypeBulkEditForm
  367. template_name = 'dcim/devicetype_bulk_edit.html'
  368. default_redirect_url = 'dcim:devicetype_list'
  369. def update_objects(self, pk_list, form):
  370. fields_to_update = {}
  371. for field in ['manufacturer', 'u_height']:
  372. if form.cleaned_data[field]:
  373. fields_to_update[field] = form.cleaned_data[field]
  374. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  375. messages.success(self.request, "Updated {} device types".format(updated_count))
  376. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  377. permission_required = 'dcim.delete_devicetype'
  378. cls = DeviceType
  379. form = DeviceTypeBulkDeleteForm
  380. template_name = 'dcim/devicetype_bulk_delete.html'
  381. default_redirect_url = 'dcim:devicetype_list'
  382. #
  383. # Device type components
  384. #
  385. class ComponentTemplateCreateView(View):
  386. model = None
  387. form = None
  388. def get(self, request, pk, *args, **kwargs):
  389. devicetype = get_object_or_404(DeviceType, pk=pk)
  390. return render(request, 'dcim/component_template_add.html', {
  391. 'devicetype': devicetype,
  392. 'component_type': self.model._meta.verbose_name,
  393. 'form': self.form(),
  394. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  395. })
  396. def post(self, request, pk, *args, **kwargs):
  397. devicetype = get_object_or_404(DeviceType, pk=pk)
  398. form = self.form(request.POST)
  399. if form.is_valid():
  400. component_templates = []
  401. for name in form.cleaned_data['name_pattern']:
  402. component_template = self.form(request.POST).save(commit=False)
  403. component_template.device_type = devicetype
  404. component_template.name = name
  405. try:
  406. component_template.full_clean()
  407. component_templates.append(component_template)
  408. except ValidationError:
  409. form.add_error('name_pattern', "Duplicate name found: {}".format(name))
  410. if not form.errors:
  411. self.model.objects.bulk_create(component_templates)
  412. messages.success(request, "Added {} compontent(s) to {}".format(len(component_templates), devicetype))
  413. if '_addanother' in request.POST:
  414. return redirect(request.path)
  415. else:
  416. return redirect('dcim:devicetype', pk=devicetype.pk)
  417. return render(request, 'dcim/component_template_add.html', {
  418. 'devicetype': devicetype,
  419. 'component_type': self.model._meta.verbose_name,
  420. 'form': form,
  421. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  422. })
  423. class ConsolePortTemplateAddView(ComponentTemplateCreateView):
  424. model = ConsolePortTemplate
  425. form = ConsolePortTemplateForm
  426. class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
  427. model = ConsoleServerPortTemplate
  428. form = ConsoleServerPortTemplateForm
  429. class PowerPortTemplateAddView(ComponentTemplateCreateView):
  430. model = PowerPortTemplate
  431. form = PowerPortTemplateForm
  432. class PowerOutletTemplateAddView(ComponentTemplateCreateView):
  433. model = PowerOutletTemplate
  434. form = PowerOutletTemplateForm
  435. class InterfaceTemplateAddView(ComponentTemplateCreateView):
  436. model = InterfaceTemplate
  437. form = InterfaceTemplateForm
  438. def component_template_delete(request, pk, model):
  439. devicetype = get_object_or_404(DeviceType, pk=pk)
  440. class ComponentTemplateBulkDeleteForm(ConfirmationForm):
  441. pk = ModelMultipleChoiceField(queryset=model.objects.all(), widget=MultipleHiddenInput)
  442. if '_confirm' in request.POST:
  443. form = ComponentTemplateBulkDeleteForm(request.POST)
  444. if form.is_valid():
  445. # Delete component templates
  446. objects_to_delete = model.objects.filter(pk__in=[v.id for v in form.cleaned_data['pk']])
  447. try:
  448. deleted_count = objects_to_delete.count()
  449. objects_to_delete.delete()
  450. except ProtectedError, e:
  451. handle_protectederror(list(objects_to_delete), request, e)
  452. return redirect('dcim:devicetype', {'pk': devicetype.pk})
  453. messages.success(request, "Deleted {} {}".format(deleted_count, model._meta.verbose_name_plural))
  454. return redirect('dcim:devicetype', pk=devicetype.pk)
  455. else:
  456. form = ComponentTemplateBulkDeleteForm(initial={'pk': request.POST.getlist('pk')})
  457. selected_objects = model.objects.filter(pk__in=form.initial.get('pk'))
  458. if not selected_objects:
  459. messages.warning(request, "No {} were selected for deletion.".format(model._meta.verbose_name_plural))
  460. return redirect('dcim:devicetype', pk=devicetype.pk)
  461. return render(request, 'dcim/component_template_delete.html', {
  462. 'devicetype': devicetype,
  463. 'form': form,
  464. 'selected_objects': selected_objects,
  465. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  466. })
  467. #
  468. # Devices
  469. #
  470. class DeviceListView(ObjectListView):
  471. queryset = Device.objects.select_related('device_type', 'device_type__manufacturer', 'device_role', 'rack',
  472. 'rack__site', 'primary_ip')
  473. filter = DeviceFilter
  474. filter_form = DeviceFilterForm
  475. table = DeviceTable
  476. edit_table = DeviceBulkEditTable
  477. edit_table_permissions = ['dcim.change_device', 'dcim.delete_device']
  478. template_name = 'dcim/device_list.html'
  479. def device(request, pk):
  480. device = get_object_or_404(Device, pk=pk)
  481. console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device')
  482. cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
  483. power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device')
  484. power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
  485. interfaces = Interface.objects.filter(device=device, mgmt_only=False).select_related('connected_as_a', 'connected_as_b', 'circuit')
  486. mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True).select_related('connected_as_a', 'connected_as_b', 'circuit')
  487. # Gather any secrets which belong to this device
  488. secrets = device.secrets.all()
  489. # Find all IP addresses assigned to this device
  490. ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface').order_by('address')
  491. # Find any related devices for convenient linking in the UI
  492. related_devices = []
  493. if device.name:
  494. if re.match('.+[0-9]+$', device.name):
  495. # Strip 1 or more trailing digits (e.g. core-switch1)
  496. base_name = re.match('(.*?)[0-9]+$', device.name).group(1)
  497. elif re.match('.+\d[a-z]+$', device.name.lower()):
  498. # Strip a trailing letter if preceded by a digit (e.g. dist-switch3a -> dist-switch3)
  499. base_name = re.match('(.*\d+)[a-z]$', device.name.lower()).group(1)
  500. else:
  501. base_name = None
  502. if base_name:
  503. related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk).select_related('rack', 'device_type__manufacturer')[:10]
  504. return render(request, 'dcim/device.html', {
  505. 'device': device,
  506. 'console_ports': console_ports,
  507. 'cs_ports': cs_ports,
  508. 'power_ports': power_ports,
  509. 'power_outlets': power_outlets,
  510. 'interfaces': interfaces,
  511. 'mgmt_interfaces': mgmt_interfaces,
  512. 'ip_addresses': ip_addresses,
  513. 'secrets': secrets,
  514. 'related_devices': related_devices,
  515. })
  516. @permission_required('dcim.add_device')
  517. def device_add(request):
  518. if request.method == 'POST':
  519. form = DeviceForm(request.POST)
  520. if form.is_valid():
  521. device = form.save()
  522. messages.success(request, "Added new device: {0} ({1})".format(device.name, device.device_type))
  523. if '_addanother' in request.POST:
  524. base_url = reverse('dcim:device_add')
  525. params = urlencode({
  526. 'site': device.rack.site.pk,
  527. 'rack': device.rack.pk,
  528. })
  529. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  530. else:
  531. return redirect('dcim:device', pk=device.pk)
  532. else:
  533. initial_data = {}
  534. if request.GET.get('rack', None):
  535. try:
  536. rack = Rack.objects.get(pk=request.GET.get('rack', None))
  537. initial_data['rack'] = rack.pk
  538. initial_data['site'] = rack.site.pk
  539. initial_data['position'] = request.GET.get('position')
  540. initial_data['face'] = request.GET.get('face')
  541. except Rack.DoesNotExist:
  542. pass
  543. form = DeviceForm(initial=initial_data)
  544. return render(request, 'dcim/device_edit.html', {
  545. 'form': form,
  546. 'cancel_url': reverse('dcim:device_list'),
  547. })
  548. @permission_required('dcim.change_device')
  549. def device_edit(request, pk):
  550. device = get_object_or_404(Device, pk=pk)
  551. if request.method == 'POST':
  552. form = DeviceForm(request.POST, instance=device)
  553. if form.is_valid():
  554. device = form.save()
  555. messages.success(request, "Modified device {0}".format(device.name))
  556. return redirect('dcim:device', pk=device.pk)
  557. else:
  558. form = DeviceForm(instance=device)
  559. return render(request, 'dcim/device_edit.html', {
  560. 'device': device,
  561. 'form': form,
  562. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  563. })
  564. @permission_required('dcim.delete_device')
  565. def device_delete(request, pk):
  566. device = get_object_or_404(Device, pk=pk)
  567. if request.method == 'POST':
  568. form = ConfirmationForm(request.POST)
  569. if form.is_valid():
  570. try:
  571. device.delete()
  572. messages.success(request, "Device {0} has been deleted".format(device))
  573. return redirect('dcim:device_list')
  574. except ProtectedError, e:
  575. handle_protectederror(device, request, e)
  576. return redirect('dcim:device', pk=device.pk)
  577. else:
  578. form = ConfirmationForm()
  579. return render(request, 'dcim/device_delete.html', {
  580. 'device': device,
  581. 'form': form,
  582. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  583. })
  584. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  585. permission_required = 'dcim.add_device'
  586. form = DeviceImportForm
  587. table = DeviceImportTable
  588. template_name = 'dcim/device_import.html'
  589. obj_list_url = 'dcim:device_list'
  590. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  591. permission_required = 'dcim.change_device'
  592. cls = Device
  593. form = DeviceBulkEditForm
  594. template_name = 'dcim/device_bulk_edit.html'
  595. default_redirect_url = 'dcim:device_list'
  596. def update_objects(self, pk_list, form):
  597. fields_to_update = {}
  598. if form.cleaned_data['platform']:
  599. fields_to_update['platform'] = form.cleaned_data['platform']
  600. elif form.cleaned_data['platform_delete']:
  601. fields_to_update['platform'] = None
  602. if form.cleaned_data['status']:
  603. status = form.cleaned_data['status']
  604. fields_to_update['status'] = True if status == 'True' else False
  605. for field in ['device_type', 'device_role', 'serial', 'ro_snmp']:
  606. if form.cleaned_data[field]:
  607. fields_to_update[field] = form.cleaned_data[field]
  608. updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
  609. messages.success(self.request, "Updated {} devices".format(updated_count))
  610. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  611. permission_required = 'dcim.delete_device'
  612. cls = Device
  613. form = DeviceBulkDeleteForm
  614. template_name = 'dcim/device_bulk_delete.html'
  615. default_redirect_url = 'dcim:device_list'
  616. def device_inventory(request, pk):
  617. device = get_object_or_404(Device, pk=pk)
  618. modules = Module.objects.filter(device=device, parent=None).prefetch_related('submodules')
  619. return render(request, 'dcim/device_inventory.html', {
  620. 'device': device,
  621. 'modules': modules,
  622. })
  623. def device_lldp_neighbors(request, pk):
  624. device = get_object_or_404(Device, pk=pk)
  625. interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b')
  626. return render(request, 'dcim/device_lldp_neighbors.html', {
  627. 'device': device,
  628. 'interfaces': interfaces,
  629. })
  630. #
  631. # Console ports
  632. #
  633. @permission_required('dcim.add_consoleport')
  634. def consoleport_add(request, pk):
  635. device = get_object_or_404(Device, pk=pk)
  636. if request.method == 'POST':
  637. form = ConsolePortCreateForm(request.POST)
  638. if form.is_valid():
  639. console_ports = []
  640. for name in form.cleaned_data['name_pattern']:
  641. cp_form = ConsolePortForm({
  642. 'device': device.pk,
  643. 'name': name,
  644. })
  645. if cp_form.is_valid():
  646. console_ports.append(cp_form.save(commit=False))
  647. else:
  648. form.add_error('name_pattern', "Duplicate console port name for this device: {}".format(name))
  649. if not form.errors:
  650. ConsolePort.objects.bulk_create(console_ports)
  651. messages.success(request, "Added {} console port(s) to {}".format(len(console_ports), device))
  652. if '_addanother' in request.POST:
  653. return redirect('dcim:consoleport_add', pk=device.pk)
  654. else:
  655. return redirect('dcim:device', pk=device.pk)
  656. else:
  657. form = ConsolePortCreateForm()
  658. return render(request, 'dcim/consoleport_edit.html', {
  659. 'device': device,
  660. 'form': form,
  661. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  662. })
  663. @permission_required('dcim.change_consoleport')
  664. def consoleport_connect(request, pk):
  665. consoleport = get_object_or_404(ConsolePort, pk=pk)
  666. if request.method == 'POST':
  667. form = ConsolePortConnectionForm(request.POST, instance=consoleport)
  668. if form.is_valid():
  669. consoleport = form.save()
  670. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  671. consoleport.device,
  672. consoleport.name,
  673. consoleport.cs_port.device,
  674. consoleport.cs_port.name,
  675. ))
  676. return redirect('dcim:device', pk=consoleport.device.pk)
  677. else:
  678. form = ConsolePortConnectionForm(instance=consoleport, initial={
  679. 'rack': consoleport.device.rack,
  680. 'connection_status': CONNECTION_STATUS_CONNECTED,
  681. })
  682. return render(request, 'dcim/consoleport_connect.html', {
  683. 'consoleport': consoleport,
  684. 'form': form,
  685. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  686. })
  687. @permission_required('dcim.change_consoleport')
  688. def consoleport_disconnect(request, pk):
  689. consoleport = get_object_or_404(ConsolePort, pk=pk)
  690. if not consoleport.cs_port:
  691. messages.warning(request, "Cannot disconnect console port {0}: It is not connected to anything".format(consoleport))
  692. return redirect('dcim:device', pk=consoleport.device.pk)
  693. if request.method == 'POST':
  694. form = ConfirmationForm(request.POST)
  695. if form.is_valid():
  696. consoleport.cs_port = None
  697. consoleport.connection_status = None
  698. consoleport.save()
  699. messages.success(request, "Console port {0} has been disconnected".format(consoleport))
  700. return redirect('dcim:device', pk=consoleport.device.pk)
  701. else:
  702. form = ConfirmationForm()
  703. return render(request, 'dcim/consoleport_disconnect.html', {
  704. 'consoleport': consoleport,
  705. 'form': form,
  706. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  707. })
  708. @permission_required('dcim.change_consoleport')
  709. def consoleport_edit(request, pk):
  710. consoleport = get_object_or_404(ConsolePort, pk=pk)
  711. if request.method == 'POST':
  712. form = ConsolePortForm(request.POST, instance=consoleport)
  713. if form.is_valid():
  714. consoleport = form.save()
  715. messages.success(request, "Modified {0} {1}".format(consoleport.device.name, consoleport.name))
  716. return redirect('dcim:device', pk=consoleport.device.pk)
  717. else:
  718. form = ConsolePortForm(instance=consoleport)
  719. return render(request, 'dcim/consoleport_edit.html', {
  720. 'consoleport': consoleport,
  721. 'form': form,
  722. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  723. })
  724. @permission_required('dcim.delete_consoleport')
  725. def consoleport_delete(request, pk):
  726. consoleport = get_object_or_404(ConsolePort, pk=pk)
  727. if request.method == 'POST':
  728. form = ConfirmationForm(request.POST)
  729. if form.is_valid():
  730. consoleport.delete()
  731. messages.success(request, "Console port {0} has been deleted from {1}".format(consoleport, consoleport.device))
  732. return redirect('dcim:device', pk=consoleport.device.pk)
  733. else:
  734. form = ConfirmationForm()
  735. return render(request, 'dcim/consoleport_delete.html', {
  736. 'consoleport': consoleport,
  737. 'form': form,
  738. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  739. })
  740. class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  741. permission_required = 'dcim.change_consoleport'
  742. form = ConsoleConnectionImportForm
  743. table = ConsoleConnectionTable
  744. template_name = 'dcim/console_connections_import.html'
  745. #
  746. # Console server ports
  747. #
  748. @permission_required('dcim.add_consoleserverport')
  749. def consoleserverport_add(request, pk):
  750. device = get_object_or_404(Device, pk=pk)
  751. if request.method == 'POST':
  752. form = ConsoleServerPortCreateForm(request.POST)
  753. if form.is_valid():
  754. cs_ports = []
  755. for name in form.cleaned_data['name_pattern']:
  756. csp_form = ConsoleServerPortForm({
  757. 'device': device.pk,
  758. 'name': name,
  759. })
  760. if csp_form.is_valid():
  761. cs_ports.append(csp_form.save(commit=False))
  762. else:
  763. form.add_error('name_pattern', "Duplicate console server port name for this device: {}"
  764. .format(name))
  765. if not form.errors:
  766. ConsoleServerPort.objects.bulk_create(cs_ports)
  767. messages.success(request, "Added {} console server port(s) to {}".format(len(cs_ports), device))
  768. if '_addanother' in request.POST:
  769. return redirect('dcim:consoleserverport_add', pk=device.pk)
  770. else:
  771. return redirect('dcim:device', pk=device.pk)
  772. else:
  773. form = ConsoleServerPortCreateForm()
  774. return render(request, 'dcim/consoleserverport_edit.html', {
  775. 'device': device,
  776. 'form': form,
  777. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  778. })
  779. @permission_required('dcim.change_consoleserverport')
  780. def consoleserverport_connect(request, pk):
  781. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  782. if request.method == 'POST':
  783. form = ConsoleServerPortConnectionForm(consoleserverport, request.POST)
  784. if form.is_valid():
  785. consoleport = form.cleaned_data['port']
  786. consoleport.cs_port = consoleserverport
  787. consoleport.connection_status = form.cleaned_data['connection_status']
  788. consoleport.save()
  789. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  790. consoleport.device,
  791. consoleport.name,
  792. consoleserverport.device,
  793. consoleserverport.name,
  794. ))
  795. return redirect('dcim:device', pk=consoleserverport.device.pk)
  796. else:
  797. form = ConsoleServerPortConnectionForm(consoleserverport, initial={'rack': consoleserverport.device.rack})
  798. return render(request, 'dcim/consoleserverport_connect.html', {
  799. 'consoleserverport': consoleserverport,
  800. 'form': form,
  801. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  802. })
  803. @permission_required('dcim.change_consoleserverport')
  804. def consoleserverport_disconnect(request, pk):
  805. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  806. if not hasattr(consoleserverport, 'connected_console'):
  807. messages.warning(request, "Cannot disconnect console server port {0}: Nothing is connected to it".format(consoleserverport))
  808. return redirect('dcim:device', pk=consoleserverport.device.pk)
  809. if request.method == 'POST':
  810. form = ConfirmationForm(request.POST)
  811. if form.is_valid():
  812. consoleport = consoleserverport.connected_console
  813. consoleport.cs_port = None
  814. consoleport.connection_status = None
  815. consoleport.save()
  816. messages.success(request, "Console server port {0} has been disconnected".format(consoleserverport))
  817. return redirect('dcim:device', pk=consoleserverport.device.pk)
  818. else:
  819. form = ConfirmationForm()
  820. return render(request, 'dcim/consoleserverport_disconnect.html', {
  821. 'consoleserverport': consoleserverport,
  822. 'form': form,
  823. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  824. })
  825. @permission_required('dcim.change_consoleserverport')
  826. def consoleserverport_edit(request, pk):
  827. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  828. if request.method == 'POST':
  829. form = ConsoleServerPortForm(request.POST, instance=consoleserverport)
  830. if form.is_valid():
  831. consoleserverport = form.save()
  832. messages.success(request, "Modified {0} {1}".format(consoleserverport.device.name, consoleserverport.name))
  833. return redirect('dcim:device', pk=consoleserverport.device.pk)
  834. else:
  835. form = ConsoleServerPortForm(instance=consoleserverport)
  836. return render(request, 'dcim/consoleserverport_edit.html', {
  837. 'consoleserverport': consoleserverport,
  838. 'form': form,
  839. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  840. })
  841. @permission_required('dcim.delete_consoleserverport')
  842. def consoleserverport_delete(request, pk):
  843. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  844. if request.method == 'POST':
  845. form = ConfirmationForm(request.POST)
  846. if form.is_valid():
  847. consoleserverport.delete()
  848. messages.success(request, "Console server port {0} has been deleted from {1}".format(consoleserverport, consoleserverport.device))
  849. return redirect('dcim:device', pk=consoleserverport.device.pk)
  850. else:
  851. form = ConfirmationForm()
  852. return render(request, 'dcim/consoleserverport_delete.html', {
  853. 'consoleserverport': consoleserverport,
  854. 'form': form,
  855. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  856. })
  857. #
  858. # Power ports
  859. #
  860. @permission_required('dcim.add_powerport')
  861. def powerport_add(request, pk):
  862. device = get_object_or_404(Device, pk=pk)
  863. if request.method == 'POST':
  864. form = PowerPortCreateForm(request.POST)
  865. if form.is_valid():
  866. power_ports = []
  867. for name in form.cleaned_data['name_pattern']:
  868. pp_form = PowerPortForm({
  869. 'device': device.pk,
  870. 'name': name,
  871. })
  872. if pp_form.is_valid():
  873. power_ports.append(pp_form.save(commit=False))
  874. else:
  875. form.add_error('name_pattern', "Duplicate power port name for this device: {}".format(name))
  876. if not form.errors:
  877. PowerPort.objects.bulk_create(power_ports)
  878. messages.success(request, "Added {} power port(s) to {}".format(len(power_ports), device))
  879. if '_addanother' in request.POST:
  880. return redirect('dcim:powerport_add', pk=device.pk)
  881. else:
  882. return redirect('dcim:device', pk=device.pk)
  883. else:
  884. form = PowerPortCreateForm()
  885. return render(request, 'dcim/powerport_edit.html', {
  886. 'device': device,
  887. 'form': form,
  888. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  889. })
  890. @permission_required('dcim.change_powerport')
  891. def powerport_connect(request, pk):
  892. powerport = get_object_or_404(PowerPort, pk=pk)
  893. if request.method == 'POST':
  894. form = PowerPortConnectionForm(request.POST, instance=powerport)
  895. if form.is_valid():
  896. powerport = form.save()
  897. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  898. powerport.device,
  899. powerport.name,
  900. powerport.power_outlet.device,
  901. powerport.power_outlet.name,
  902. ))
  903. return redirect('dcim:device', pk=powerport.device.pk)
  904. else:
  905. form = PowerPortConnectionForm(instance=powerport, initial={
  906. 'rack': powerport.device.rack,
  907. 'connection_status': CONNECTION_STATUS_CONNECTED,
  908. })
  909. return render(request, 'dcim/powerport_connect.html', {
  910. 'powerport': powerport,
  911. 'form': form,
  912. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  913. })
  914. @permission_required('dcim.change_powerport')
  915. def powerport_disconnect(request, pk):
  916. powerport = get_object_or_404(PowerPort, pk=pk)
  917. if not powerport.power_outlet:
  918. messages.warning(request, "Cannot disconnect power port {0}: It is not connected to an outlet".format(powerport))
  919. return redirect('dcim:device', pk=powerport.device.pk)
  920. if request.method == 'POST':
  921. form = ConfirmationForm(request.POST)
  922. if form.is_valid():
  923. powerport.power_outlet = None
  924. powerport.connection_status = None
  925. powerport.save()
  926. messages.success(request, "Power port {0} has been disconnected".format(powerport))
  927. return redirect('dcim:device', pk=powerport.device.pk)
  928. else:
  929. form = ConfirmationForm()
  930. return render(request, 'dcim/powerport_disconnect.html', {
  931. 'powerport': powerport,
  932. 'form': form,
  933. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  934. })
  935. @permission_required('dcim.change_powerport')
  936. def powerport_edit(request, pk):
  937. powerport = get_object_or_404(PowerPort, pk=pk)
  938. if request.method == 'POST':
  939. form = PowerPortForm(request.POST, instance=powerport)
  940. if form.is_valid():
  941. powerport = form.save()
  942. messages.success(request, "Modified {0} power port {1}".format(powerport.device.name, powerport.name))
  943. return redirect('dcim:device', pk=powerport.device.pk)
  944. else:
  945. form = PowerPortForm(instance=powerport)
  946. return render(request, 'dcim/powerport_edit.html', {
  947. 'powerport': powerport,
  948. 'form': form,
  949. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  950. })
  951. @permission_required('dcim.delete_powerport')
  952. def powerport_delete(request, pk):
  953. powerport = get_object_or_404(PowerPort, pk=pk)
  954. if request.method == 'POST':
  955. form = ConfirmationForm(request.POST)
  956. if form.is_valid():
  957. powerport.delete()
  958. messages.success(request, "Power port {0} has been deleted from {1}".format(powerport, powerport.device))
  959. return redirect('dcim:device', pk=powerport.device.pk)
  960. else:
  961. form = ConfirmationForm()
  962. return render(request, 'dcim/powerport_delete.html', {
  963. 'powerport': powerport,
  964. 'form': form,
  965. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  966. })
  967. class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  968. permission_required = 'dcim.change_powerport'
  969. form = PowerConnectionImportForm
  970. table = PowerConnectionTable
  971. template_name = 'dcim/power_connections_import.html'
  972. #
  973. # Power outlets
  974. #
  975. @permission_required('dcim.add_poweroutlet')
  976. def poweroutlet_add(request, pk):
  977. device = get_object_or_404(Device, pk=pk)
  978. if request.method == 'POST':
  979. form = PowerOutletCreateForm(request.POST)
  980. if form.is_valid():
  981. power_outlets = []
  982. for name in form.cleaned_data['name_pattern']:
  983. po_form = PowerOutletForm({
  984. 'device': device.pk,
  985. 'name': name,
  986. })
  987. if po_form.is_valid():
  988. power_outlets.append(po_form.save(commit=False))
  989. else:
  990. form.add_error('name_pattern', "Duplicate power outlet name for this device: {}".format(name))
  991. if not form.errors:
  992. PowerOutlet.objects.bulk_create(power_outlets)
  993. messages.success(request, "Added {} power outlet(s) to {}".format(len(power_outlets), device))
  994. if '_addanother' in request.POST:
  995. return redirect('dcim:poweroutlet_add', pk=device.pk)
  996. else:
  997. return redirect('dcim:device', pk=device.pk)
  998. else:
  999. form = PowerOutletCreateForm()
  1000. return render(request, 'dcim/poweroutlet_edit.html', {
  1001. 'device': device,
  1002. 'form': form,
  1003. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1004. })
  1005. @permission_required('dcim.change_poweroutlet')
  1006. def poweroutlet_connect(request, pk):
  1007. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1008. if request.method == 'POST':
  1009. form = PowerOutletConnectionForm(poweroutlet, request.POST)
  1010. if form.is_valid():
  1011. powerport = form.cleaned_data['port']
  1012. powerport.power_outlet = poweroutlet
  1013. powerport.connection_status = form.cleaned_data['connection_status']
  1014. powerport.save()
  1015. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  1016. powerport.device,
  1017. powerport.name,
  1018. poweroutlet.device,
  1019. poweroutlet.name,
  1020. ))
  1021. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1022. else:
  1023. form = PowerOutletConnectionForm(poweroutlet, initial={'rack': poweroutlet.device.rack})
  1024. return render(request, 'dcim/poweroutlet_connect.html', {
  1025. 'poweroutlet': poweroutlet,
  1026. 'form': form,
  1027. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1028. })
  1029. @permission_required('dcim.change_poweroutlet')
  1030. def poweroutlet_disconnect(request, pk):
  1031. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1032. if not hasattr(poweroutlet, 'connected_port'):
  1033. messages.warning(request, "Cannot disconnect power outlet {0}: Nothing is connected to it".format(poweroutlet))
  1034. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1035. if request.method == 'POST':
  1036. form = ConfirmationForm(request.POST)
  1037. if form.is_valid():
  1038. powerport = poweroutlet.connected_port
  1039. powerport.power_outlet = None
  1040. powerport.connection_status = None
  1041. powerport.save()
  1042. messages.success(request, "Power outlet {0} has been disconnected".format(poweroutlet))
  1043. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1044. else:
  1045. form = ConfirmationForm()
  1046. return render(request, 'dcim/poweroutlet_disconnect.html', {
  1047. 'poweroutlet': poweroutlet,
  1048. 'form': form,
  1049. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1050. })
  1051. @permission_required('dcim.change_poweroutlet')
  1052. def poweroutlet_edit(request, pk):
  1053. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1054. if request.method == 'POST':
  1055. form = PowerOutletForm(request.POST, instance=poweroutlet)
  1056. if form.is_valid():
  1057. poweroutlet = form.save()
  1058. messages.success(request, "Modified {0} power outlet {1}".format(poweroutlet.device.name, poweroutlet.name))
  1059. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1060. else:
  1061. form = PowerOutletForm(instance=poweroutlet)
  1062. return render(request, 'dcim/poweroutlet_edit.html', {
  1063. 'poweroutlet': poweroutlet,
  1064. 'form': form,
  1065. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1066. })
  1067. @permission_required('dcim.delete_poweroutlet')
  1068. def poweroutlet_delete(request, pk):
  1069. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1070. if request.method == 'POST':
  1071. form = ConfirmationForm(request.POST)
  1072. if form.is_valid():
  1073. poweroutlet.delete()
  1074. messages.success(request, "Power outlet {0} has been deleted from {1}".format(poweroutlet, poweroutlet.device))
  1075. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1076. else:
  1077. form = ConfirmationForm()
  1078. return render(request, 'dcim/poweroutlet_delete.html', {
  1079. 'poweroutlet': poweroutlet,
  1080. 'form': form,
  1081. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1082. })
  1083. #
  1084. # Interfaces
  1085. #
  1086. @permission_required('dcim.add_interface')
  1087. def interface_add(request, pk):
  1088. device = get_object_or_404(Device, pk=pk)
  1089. if request.method == 'POST':
  1090. form = InterfaceCreateForm(request.POST)
  1091. if form.is_valid():
  1092. interfaces = []
  1093. for name in form.cleaned_data['name_pattern']:
  1094. iface_form = InterfaceForm({
  1095. 'device': device.pk,
  1096. 'name': name,
  1097. 'form_factor': form.cleaned_data['form_factor'],
  1098. 'mgmt_only': form.cleaned_data['mgmt_only'],
  1099. 'description': form.cleaned_data['description'],
  1100. })
  1101. if iface_form.is_valid():
  1102. interfaces.append(iface_form.save(commit=False))
  1103. else:
  1104. form.add_error('name_pattern', "Duplicate interface name for this device: {}".format(name))
  1105. if not form.errors:
  1106. Interface.objects.bulk_create(interfaces)
  1107. messages.success(request, "Added {} interface(s) to {}".format(len(interfaces), device))
  1108. if '_addanother' in request.POST:
  1109. return redirect('dcim:interface_add', pk=device.pk)
  1110. else:
  1111. return redirect('dcim:device', pk=device.pk)
  1112. else:
  1113. form = InterfaceCreateForm()
  1114. return render(request, 'dcim/interface_edit.html', {
  1115. 'device': device,
  1116. 'form': form,
  1117. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1118. })
  1119. @permission_required('dcim.change_interface')
  1120. def interface_edit(request, pk):
  1121. interface = get_object_or_404(Interface, pk=pk)
  1122. if request.method == 'POST':
  1123. form = InterfaceForm(request.POST, instance=interface)
  1124. if form.is_valid():
  1125. interface = form.save()
  1126. messages.success(request, "Modified {0} interface {1}".format(interface.device.name, interface.name))
  1127. return redirect('dcim:device', pk=interface.device.pk)
  1128. else:
  1129. form = InterfaceForm(instance=interface)
  1130. return render(request, 'dcim/interface_edit.html', {
  1131. 'interface': interface,
  1132. 'form': form,
  1133. 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}),
  1134. })
  1135. @permission_required('dcim.delete_interface')
  1136. def interface_delete(request, pk):
  1137. interface = get_object_or_404(Interface, pk=pk)
  1138. if request.method == 'POST':
  1139. form = ConfirmationForm(request.POST)
  1140. if form.is_valid():
  1141. interface.delete()
  1142. messages.success(request, "Interface {0} has been deleted from {1}".format(interface, interface.device))
  1143. return redirect('dcim:device', pk=interface.device.pk)
  1144. else:
  1145. form = ConfirmationForm()
  1146. return render(request, 'dcim/interface_delete.html', {
  1147. 'interface': interface,
  1148. 'form': form,
  1149. 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}),
  1150. })
  1151. class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
  1152. permission_required = 'dcim.add_interface'
  1153. cls = Device
  1154. form = InterfaceBulkCreateForm
  1155. template_name = 'dcim/interface_bulk_add.html'
  1156. default_redirect_url = 'dcim:device_list'
  1157. def update_objects(self, pk_list, form):
  1158. selected_devices = Device.objects.filter(pk__in=pk_list)
  1159. interfaces = []
  1160. for device in selected_devices:
  1161. for name in form.cleaned_data['name_pattern']:
  1162. iface_form = InterfaceForm({
  1163. 'device': device.pk,
  1164. 'name': name,
  1165. 'form_factor': form.cleaned_data['form_factor'],
  1166. 'mgmt_only': form.cleaned_data['mgmt_only'],
  1167. 'description': form.cleaned_data['description'],
  1168. })
  1169. if iface_form.is_valid():
  1170. interfaces.append(iface_form.save(commit=False))
  1171. else:
  1172. form.add_error(None, "Duplicate interface {} found for device {}".format(name, device))
  1173. if not form.errors:
  1174. Interface.objects.bulk_create(interfaces)
  1175. messages.success(self.request, "Added {} interfaces to {} devices".format(len(interfaces),
  1176. len(selected_devices)))
  1177. #
  1178. # Interface connections
  1179. #
  1180. @permission_required('dcim.add_interfaceconnection')
  1181. def interfaceconnection_add(request, pk):
  1182. device = get_object_or_404(Device, pk=pk)
  1183. if request.method == 'POST':
  1184. form = InterfaceConnectionForm(device, request.POST)
  1185. if form.is_valid():
  1186. interfaceconnection = form.save()
  1187. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  1188. interfaceconnection.interface_a.device,
  1189. interfaceconnection.interface_a,
  1190. interfaceconnection.interface_b.device,
  1191. interfaceconnection.interface_b,
  1192. ))
  1193. if '_addanother' in request.POST:
  1194. base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
  1195. params = urlencode({
  1196. 'rack_b': interfaceconnection.interface_b.device.rack.pk,
  1197. 'device_b': interfaceconnection.interface_b.device.pk,
  1198. })
  1199. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  1200. else:
  1201. return redirect('dcim:device', pk=device.pk)
  1202. else:
  1203. form = InterfaceConnectionForm(device, initial={
  1204. 'interface_a': request.GET.get('interface', None),
  1205. 'rack_b': request.GET.get('rack_b', None),
  1206. 'device_b': request.GET.get('device_b', None),
  1207. })
  1208. return render(request, 'dcim/interfaceconnection_edit.html', {
  1209. 'device': device,
  1210. 'form': form,
  1211. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1212. })
  1213. @permission_required('dcim.delete_interfaceconnection')
  1214. def interfaceconnection_delete(request, pk):
  1215. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1216. device_id = request.GET.get('device', None)
  1217. if request.method == 'POST':
  1218. form = InterfaceConnectionDeletionForm(request.POST)
  1219. if form.is_valid():
  1220. interfaceconnection.delete()
  1221. messages.success(request, "Deleted the connection between {0} {1} and {2} {3}".format(
  1222. interfaceconnection.interface_a.device,
  1223. interfaceconnection.interface_a,
  1224. interfaceconnection.interface_b.device,
  1225. interfaceconnection.interface_b,
  1226. ))
  1227. if form.cleaned_data['device']:
  1228. return redirect('dcim:device', pk=form.cleaned_data['device'].pk)
  1229. else:
  1230. return redirect('dcim:device_list')
  1231. else:
  1232. form = InterfaceConnectionDeletionForm(initial={
  1233. 'device': device_id,
  1234. })
  1235. # Determine where to direct user upon cancellation
  1236. if device_id:
  1237. cancel_url = reverse('dcim:device', kwargs={'pk': device_id})
  1238. else:
  1239. cancel_url = reverse('dcim:device_list')
  1240. return render(request, 'dcim/interfaceconnection_delete.html', {
  1241. 'interfaceconnection': interfaceconnection,
  1242. 'device_id': device_id,
  1243. 'form': form,
  1244. 'cancel_url': cancel_url,
  1245. })
  1246. class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1247. permission_required = 'dcim.change_interface'
  1248. form = InterfaceConnectionImportForm
  1249. table = InterfaceConnectionTable
  1250. template_name = 'dcim/interface_connections_import.html'
  1251. #
  1252. # Connections
  1253. #
  1254. class ConsoleConnectionsListView(ObjectListView):
  1255. queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\
  1256. .order_by('cs_port__device__name', 'cs_port__name')
  1257. filter = ConsoleConnectionFilter
  1258. filter_form = ConsoleConnectionFilterForm
  1259. table = ConsoleConnectionTable
  1260. template_name = 'dcim/console_connections_list.html'
  1261. class PowerConnectionsListView(ObjectListView):
  1262. queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\
  1263. .order_by('power_outlet__device__name', 'power_outlet__name')
  1264. filter = PowerConnectionFilter
  1265. filter_form = PowerConnectionFilterForm
  1266. table = PowerConnectionTable
  1267. template_name = 'dcim/power_connections_list.html'
  1268. class InterfaceConnectionsListView(ObjectListView):
  1269. queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\
  1270. .order_by('interface_a__device__name', 'interface_a__name')
  1271. filter = InterfaceConnectionFilter
  1272. filter_form = InterfaceConnectionFilterForm
  1273. table = InterfaceConnectionTable
  1274. template_name = 'dcim/interface_connections_list.html'
  1275. #
  1276. # IP addresses
  1277. #
  1278. @permission_required('ipam.add_ipaddress')
  1279. def ipaddress_assign(request, pk):
  1280. device = get_object_or_404(Device, pk=pk)
  1281. if request.method == 'POST':
  1282. form = IPAddressForm(device, request.POST)
  1283. if form.is_valid():
  1284. ipaddress = form.save(commit=False)
  1285. ipaddress.interface = form.cleaned_data['interface']
  1286. ipaddress.save()
  1287. messages.success(request, "Added new IP address {0} to interface {1}".format(ipaddress, ipaddress.interface))
  1288. if form.cleaned_data['set_as_primary']:
  1289. device.primary_ip = ipaddress
  1290. device.save()
  1291. if '_addanother' in request.POST:
  1292. return redirect('dcim:ipaddress_assign', pk=device.pk)
  1293. else:
  1294. return redirect('dcim:device', pk=device.pk)
  1295. else:
  1296. form = IPAddressForm(device)
  1297. return render(request, 'dcim/ipaddress_assign.html', {
  1298. 'device': device,
  1299. 'form': form,
  1300. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1301. })