views.py 56 KB

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