views.py 52 KB

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