views.py 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811
  1. import re
  2. from natsort import natsorted
  3. from operator import attrgetter
  4. from django.contrib import messages
  5. from django.contrib.auth.decorators import permission_required
  6. from django.contrib.auth.mixins import PermissionRequiredMixin
  7. from django.core.exceptions import ValidationError
  8. from django.core.urlresolvers import reverse
  9. from django.db.models import Count, Sum
  10. from django.db.models.functions import Coalesce
  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. cancel_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. success_url = 'dcim:rackgroup_list'
  115. cancel_url = 'dcim:rackgroup_list'
  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. success_url = 'dcim:rackrole_list'
  133. cancel_url = 'dcim:rackrole_list'
  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. u_consumed=Coalesce(Sum('devices__device_type__u_height'), 0))
  145. filter = filters.RackFilter
  146. filter_form = forms.RackFilterForm
  147. table = tables.RackTable
  148. edit_permissions = ['dcim.change_rack', 'dcim.delete_rack']
  149. template_name = 'dcim/rack_list.html'
  150. def rack(request, pk):
  151. rack = get_object_or_404(Rack, pk=pk)
  152. nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True)\
  153. .select_related('device_type__manufacturer')
  154. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  155. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  156. return render(request, 'dcim/rack.html', {
  157. 'rack': rack,
  158. 'nonracked_devices': nonracked_devices,
  159. 'next_rack': next_rack,
  160. 'prev_rack': prev_rack,
  161. 'front_elevation': rack.get_front_elevation(),
  162. 'rear_elevation': rack.get_rear_elevation(),
  163. })
  164. class RackEditView(PermissionRequiredMixin, ObjectEditView):
  165. permission_required = 'dcim.change_rack'
  166. model = Rack
  167. form_class = forms.RackForm
  168. template_name = 'dcim/rack_edit.html'
  169. cancel_url = 'dcim:rack_list'
  170. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  171. permission_required = 'dcim.delete_rack'
  172. model = Rack
  173. redirect_url = 'dcim:rack_list'
  174. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  175. permission_required = 'dcim.add_rack'
  176. form = forms.RackImportForm
  177. table = tables.RackImportTable
  178. template_name = 'dcim/rack_import.html'
  179. obj_list_url = 'dcim:rack_list'
  180. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  181. permission_required = 'dcim.change_rack'
  182. cls = Rack
  183. form = forms.RackBulkEditForm
  184. template_name = 'dcim/rack_bulk_edit.html'
  185. default_redirect_url = 'dcim:rack_list'
  186. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  187. permission_required = 'dcim.delete_rack'
  188. cls = Rack
  189. default_redirect_url = 'dcim:rack_list'
  190. #
  191. # Manufacturers
  192. #
  193. class ManufacturerListView(ObjectListView):
  194. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  195. table = tables.ManufacturerTable
  196. edit_permissions = ['dcim.change_manufacturer', 'dcim.delete_manufacturer']
  197. template_name = 'dcim/manufacturer_list.html'
  198. class ManufacturerEditView(PermissionRequiredMixin, ObjectEditView):
  199. permission_required = 'dcim.change_manufacturer'
  200. model = Manufacturer
  201. form_class = forms.ManufacturerForm
  202. success_url = 'dcim:manufacturer_list'
  203. cancel_url = 'dcim:manufacturer_list'
  204. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  205. permission_required = 'dcim.delete_manufacturer'
  206. cls = Manufacturer
  207. default_redirect_url = 'dcim:manufacturer_list'
  208. #
  209. # Device types
  210. #
  211. class DeviceTypeListView(ObjectListView):
  212. queryset = DeviceType.objects.select_related('manufacturer')
  213. filter = filters.DeviceTypeFilter
  214. filter_form = forms.DeviceTypeFilterForm
  215. table = tables.DeviceTypeTable
  216. edit_permissions = ['dcim.change_devicetype', 'dcim.delete_devicetype']
  217. template_name = 'dcim/devicetype_list.html'
  218. def devicetype(request, pk):
  219. devicetype = get_object_or_404(DeviceType, pk=pk)
  220. # Component tables
  221. consoleport_table = tables.ConsolePortTemplateTable(
  222. natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  223. )
  224. consoleserverport_table = tables.ConsoleServerPortTemplateTable(
  225. natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  226. )
  227. powerport_table = tables.PowerPortTemplateTable(
  228. natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  229. )
  230. poweroutlet_table = tables.PowerOutletTemplateTable(
  231. natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  232. )
  233. mgmt_interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype,
  234. mgmt_only=True))
  235. interface_table = tables.InterfaceTemplateTable(InterfaceTemplate.objects.filter(device_type=devicetype,
  236. mgmt_only=False))
  237. devicebay_table = tables.DeviceBayTemplateTable(
  238. natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name'))
  239. )
  240. if request.user.has_perm('dcim.change_devicetype'):
  241. consoleport_table.base_columns['pk'].visible = True
  242. consoleserverport_table.base_columns['pk'].visible = True
  243. powerport_table.base_columns['pk'].visible = True
  244. poweroutlet_table.base_columns['pk'].visible = True
  245. mgmt_interface_table.base_columns['pk'].visible = True
  246. interface_table.base_columns['pk'].visible = True
  247. devicebay_table.base_columns['pk'].visible = True
  248. return render(request, 'dcim/devicetype.html', {
  249. 'devicetype': devicetype,
  250. 'consoleport_table': consoleport_table,
  251. 'consoleserverport_table': consoleserverport_table,
  252. 'powerport_table': powerport_table,
  253. 'poweroutlet_table': poweroutlet_table,
  254. 'mgmt_interface_table': mgmt_interface_table,
  255. 'interface_table': interface_table,
  256. 'devicebay_table': devicebay_table,
  257. })
  258. class DeviceTypeEditView(PermissionRequiredMixin, ObjectEditView):
  259. permission_required = 'dcim.change_devicetype'
  260. model = DeviceType
  261. form_class = forms.DeviceTypeForm
  262. cancel_url = 'dcim:devicetype_list'
  263. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  264. permission_required = 'dcim.delete_devicetype'
  265. model = DeviceType
  266. redirect_url = 'dcim:devicetype_list'
  267. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  268. permission_required = 'dcim.change_devicetype'
  269. cls = DeviceType
  270. form = forms.DeviceTypeBulkEditForm
  271. template_name = 'dcim/devicetype_bulk_edit.html'
  272. default_redirect_url = 'dcim:devicetype_list'
  273. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  274. permission_required = 'dcim.delete_devicetype'
  275. cls = DeviceType
  276. default_redirect_url = 'dcim:devicetype_list'
  277. #
  278. # Device type components
  279. #
  280. class ComponentTemplateCreateView(View):
  281. model = None
  282. form = None
  283. def get(self, request, pk):
  284. devicetype = get_object_or_404(DeviceType, pk=pk)
  285. return render(request, 'dcim/component_template_add.html', {
  286. 'devicetype': devicetype,
  287. 'component_type': self.model._meta.verbose_name,
  288. 'form': self.form(initial=request.GET),
  289. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  290. })
  291. def post(self, request, pk):
  292. devicetype = get_object_or_404(DeviceType, pk=pk)
  293. form = self.form(request.POST)
  294. if form.is_valid():
  295. component_templates = []
  296. for name in form.cleaned_data['name_pattern']:
  297. component_template = self.form(request.POST).save(commit=False)
  298. component_template.device_type = devicetype
  299. component_template.name = name
  300. try:
  301. component_template.full_clean()
  302. component_templates.append(component_template)
  303. except ValidationError:
  304. form.add_error('name_pattern', "Duplicate name found: {}".format(name))
  305. if not form.errors:
  306. self.model.objects.bulk_create(component_templates)
  307. messages.success(request, "Added {} component(s) to {}".format(len(component_templates), devicetype))
  308. if '_addanother' in request.POST:
  309. return redirect(request.path)
  310. else:
  311. return redirect('dcim:devicetype', pk=devicetype.pk)
  312. return render(request, 'dcim/component_template_add.html', {
  313. 'devicetype': devicetype,
  314. 'component_type': self.model._meta.verbose_name,
  315. 'form': form,
  316. 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}),
  317. })
  318. class ConsolePortTemplateAddView(ComponentTemplateCreateView):
  319. model = ConsolePortTemplate
  320. form = forms.ConsolePortTemplateForm
  321. class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  322. permission_required = 'dcim.delete_consoleporttemplate'
  323. cls = ConsolePortTemplate
  324. parent_cls = DeviceType
  325. class ConsoleServerPortTemplateAddView(ComponentTemplateCreateView):
  326. model = ConsoleServerPortTemplate
  327. form = forms.ConsoleServerPortTemplateForm
  328. class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  329. permission_required = 'dcim.delete_consoleserverporttemplate'
  330. cls = ConsoleServerPortTemplate
  331. parent_cls = DeviceType
  332. class PowerPortTemplateAddView(ComponentTemplateCreateView):
  333. model = PowerPortTemplate
  334. form = forms.PowerPortTemplateForm
  335. class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  336. permission_required = 'dcim.delete_powerporttemplate'
  337. cls = PowerPortTemplate
  338. parent_cls = DeviceType
  339. class PowerOutletTemplateAddView(ComponentTemplateCreateView):
  340. model = PowerOutletTemplate
  341. form = forms.PowerOutletTemplateForm
  342. class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  343. permission_required = 'dcim.delete_poweroutlettemplate'
  344. cls = PowerOutletTemplate
  345. parent_cls = DeviceType
  346. class InterfaceTemplateAddView(ComponentTemplateCreateView):
  347. model = InterfaceTemplate
  348. form = forms.InterfaceTemplateForm
  349. class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  350. permission_required = 'dcim.delete_interfacetemplate'
  351. cls = InterfaceTemplate
  352. parent_cls = DeviceType
  353. class DeviceBayTemplateAddView(ComponentTemplateCreateView):
  354. model = DeviceBayTemplate
  355. form = forms.DeviceBayTemplateForm
  356. class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  357. permission_required = 'dcim.delete_devicebaytemplate'
  358. cls = DeviceBayTemplate
  359. parent_cls = DeviceType
  360. #
  361. # Device roles
  362. #
  363. class DeviceRoleListView(ObjectListView):
  364. queryset = DeviceRole.objects.annotate(device_count=Count('devices'))
  365. table = tables.DeviceRoleTable
  366. edit_permissions = ['dcim.change_devicerole', 'dcim.delete_devicerole']
  367. template_name = 'dcim/devicerole_list.html'
  368. class DeviceRoleEditView(PermissionRequiredMixin, ObjectEditView):
  369. permission_required = 'dcim.change_devicerole'
  370. model = DeviceRole
  371. form_class = forms.DeviceRoleForm
  372. success_url = 'dcim:devicerole_list'
  373. cancel_url = 'dcim:devicerole_list'
  374. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  375. permission_required = 'dcim.delete_devicerole'
  376. cls = DeviceRole
  377. default_redirect_url = 'dcim:devicerole_list'
  378. #
  379. # Platforms
  380. #
  381. class PlatformListView(ObjectListView):
  382. queryset = Platform.objects.annotate(device_count=Count('devices'))
  383. table = tables.PlatformTable
  384. edit_permissions = ['dcim.change_platform', 'dcim.delete_platform']
  385. template_name = 'dcim/platform_list.html'
  386. class PlatformEditView(PermissionRequiredMixin, ObjectEditView):
  387. permission_required = 'dcim.change_platform'
  388. model = Platform
  389. form_class = forms.PlatformForm
  390. success_url = 'dcim:platform_list'
  391. cancel_url = 'dcim:platform_list'
  392. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  393. permission_required = 'dcim.delete_platform'
  394. cls = Platform
  395. default_redirect_url = 'dcim:platform_list'
  396. #
  397. # Devices
  398. #
  399. class DeviceListView(ObjectListView):
  400. queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'rack__site',
  401. 'primary_ip4', 'primary_ip6')
  402. filter = filters.DeviceFilter
  403. filter_form = forms.DeviceFilterForm
  404. table = tables.DeviceTable
  405. edit_permissions = ['dcim.change_device', 'dcim.delete_device']
  406. template_name = 'dcim/device_list.html'
  407. def device(request, pk):
  408. device = get_object_or_404(Device, pk=pk)
  409. console_ports = natsorted(
  410. ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
  411. )
  412. cs_ports = natsorted(
  413. ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
  414. )
  415. power_ports = natsorted(
  416. PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
  417. )
  418. power_outlets = natsorted(
  419. PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
  420. )
  421. interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
  422. .select_related('connected_as_a', 'connected_as_b', 'circuit')
  423. mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
  424. .select_related('connected_as_a', 'connected_as_b', 'circuit')
  425. device_bays = natsorted(
  426. DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
  427. key=attrgetter('name')
  428. )
  429. # Gather any secrets which belong to this device
  430. secrets = device.secrets.all()
  431. # Find all IP addresses assigned to this device
  432. ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface').order_by('address')
  433. # Find any related devices for convenient linking in the UI
  434. related_devices = []
  435. if device.name:
  436. if re.match('.+[0-9]+$', device.name):
  437. # Strip 1 or more trailing digits (e.g. core-switch1)
  438. base_name = re.match('(.*?)[0-9]+$', device.name).group(1)
  439. elif re.match('.+\d[a-z]$', device.name.lower()):
  440. # Strip a trailing letter if preceded by a digit (e.g. dist-switch3a -> dist-switch3)
  441. base_name = re.match('(.*\d+)[a-z]$', device.name.lower()).group(1)
  442. else:
  443. base_name = None
  444. if base_name:
  445. related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk)\
  446. .select_related('rack', 'device_type__manufacturer')[:10]
  447. # Show graph button on interfaces only if at least one graph has been created.
  448. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists()
  449. return render(request, 'dcim/device.html', {
  450. 'device': device,
  451. 'console_ports': console_ports,
  452. 'cs_ports': cs_ports,
  453. 'power_ports': power_ports,
  454. 'power_outlets': power_outlets,
  455. 'interfaces': interfaces,
  456. 'mgmt_interfaces': mgmt_interfaces,
  457. 'device_bays': device_bays,
  458. 'ip_addresses': ip_addresses,
  459. 'secrets': secrets,
  460. 'related_devices': related_devices,
  461. 'show_graphs': show_graphs,
  462. })
  463. class DeviceEditView(PermissionRequiredMixin, ObjectEditView):
  464. permission_required = 'dcim.change_device'
  465. model = Device
  466. form_class = forms.DeviceForm
  467. fields_initial = ['site', 'rack', 'position', 'face', 'device_bay']
  468. template_name = 'dcim/device_edit.html'
  469. cancel_url = 'dcim:device_list'
  470. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  471. permission_required = 'dcim.delete_device'
  472. model = Device
  473. redirect_url = 'dcim:device_list'
  474. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  475. permission_required = 'dcim.add_device'
  476. form = forms.DeviceImportForm
  477. table = tables.DeviceImportTable
  478. template_name = 'dcim/device_import.html'
  479. obj_list_url = 'dcim:device_list'
  480. class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  481. permission_required = 'dcim.add_device'
  482. form = forms.ChildDeviceImportForm
  483. table = tables.DeviceImportTable
  484. template_name = 'dcim/device_import_child.html'
  485. obj_list_url = 'dcim:device_list'
  486. def save_obj(self, obj):
  487. # Inherent rack from parent device
  488. obj.rack = obj.parent_bay.device.rack
  489. obj.save()
  490. # Save the reverse relation
  491. device_bay = obj.parent_bay
  492. device_bay.installed_device = obj
  493. device_bay.save()
  494. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  495. permission_required = 'dcim.change_device'
  496. cls = Device
  497. form = forms.DeviceBulkEditForm
  498. template_name = 'dcim/device_bulk_edit.html'
  499. default_redirect_url = 'dcim:device_list'
  500. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  501. permission_required = 'dcim.delete_device'
  502. cls = Device
  503. default_redirect_url = 'dcim:device_list'
  504. def device_inventory(request, pk):
  505. device = get_object_or_404(Device, pk=pk)
  506. modules = Module.objects.filter(device=device, parent=None).select_related('manufacturer')\
  507. .prefetch_related('submodules')
  508. return render(request, 'dcim/device_inventory.html', {
  509. 'device': device,
  510. 'modules': modules,
  511. })
  512. def device_lldp_neighbors(request, pk):
  513. device = get_object_or_404(Device, pk=pk)
  514. interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b')
  515. return render(request, 'dcim/device_lldp_neighbors.html', {
  516. 'device': device,
  517. 'interfaces': interfaces,
  518. })
  519. #
  520. # Console ports
  521. #
  522. @permission_required('dcim.add_consoleport')
  523. def consoleport_add(request, pk):
  524. device = get_object_or_404(Device, pk=pk)
  525. if request.method == 'POST':
  526. form = forms.ConsolePortCreateForm(request.POST)
  527. if form.is_valid():
  528. console_ports = []
  529. for name in form.cleaned_data['name_pattern']:
  530. cp_form = forms.ConsolePortForm({
  531. 'device': device.pk,
  532. 'name': name,
  533. })
  534. if cp_form.is_valid():
  535. console_ports.append(cp_form.save(commit=False))
  536. else:
  537. form.add_error('name_pattern', "Duplicate console port name for this device: {}".format(name))
  538. if not form.errors:
  539. ConsolePort.objects.bulk_create(console_ports)
  540. messages.success(request, "Added {} console port(s) to {}".format(len(console_ports), device))
  541. if '_addanother' in request.POST:
  542. return redirect('dcim:consoleport_add', pk=device.pk)
  543. else:
  544. return redirect('dcim:device', pk=device.pk)
  545. else:
  546. form = forms.ConsolePortCreateForm()
  547. return render(request, 'dcim/consoleport_edit.html', {
  548. 'device': device,
  549. 'form': form,
  550. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  551. })
  552. @permission_required('dcim.change_consoleport')
  553. def consoleport_connect(request, pk):
  554. consoleport = get_object_or_404(ConsolePort, pk=pk)
  555. if request.method == 'POST':
  556. form = forms.ConsolePortConnectionForm(request.POST, instance=consoleport)
  557. if form.is_valid():
  558. consoleport = form.save()
  559. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  560. consoleport.device,
  561. consoleport.name,
  562. consoleport.cs_port.device,
  563. consoleport.cs_port.name,
  564. ))
  565. return redirect('dcim:device', pk=consoleport.device.pk)
  566. else:
  567. form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
  568. 'rack': consoleport.device.rack,
  569. 'connection_status': CONNECTION_STATUS_CONNECTED,
  570. })
  571. return render(request, 'dcim/consoleport_connect.html', {
  572. 'consoleport': consoleport,
  573. 'form': form,
  574. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  575. })
  576. @permission_required('dcim.change_consoleport')
  577. def consoleport_disconnect(request, pk):
  578. consoleport = get_object_or_404(ConsolePort, pk=pk)
  579. if not consoleport.cs_port:
  580. messages.warning(request, "Cannot disconnect console port {0}: It is not connected to anything"
  581. .format(consoleport))
  582. return redirect('dcim:device', pk=consoleport.device.pk)
  583. if request.method == 'POST':
  584. form = ConfirmationForm(request.POST)
  585. if form.is_valid():
  586. consoleport.cs_port = None
  587. consoleport.connection_status = None
  588. consoleport.save()
  589. messages.success(request, "Console port {0} has been disconnected".format(consoleport))
  590. return redirect('dcim:device', pk=consoleport.device.pk)
  591. else:
  592. form = ConfirmationForm()
  593. return render(request, 'dcim/consoleport_disconnect.html', {
  594. 'consoleport': consoleport,
  595. 'form': form,
  596. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  597. })
  598. @permission_required('dcim.change_consoleport')
  599. def consoleport_edit(request, pk):
  600. consoleport = get_object_or_404(ConsolePort, pk=pk)
  601. if request.method == 'POST':
  602. form = forms.ConsolePortForm(request.POST, instance=consoleport)
  603. if form.is_valid():
  604. consoleport = form.save()
  605. messages.success(request, "Modified {0} {1}".format(consoleport.device.name, consoleport.name))
  606. return redirect('dcim:device', pk=consoleport.device.pk)
  607. else:
  608. form = forms.ConsolePortForm(instance=consoleport)
  609. return render(request, 'dcim/consoleport_edit.html', {
  610. 'consoleport': consoleport,
  611. 'form': form,
  612. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  613. })
  614. @permission_required('dcim.delete_consoleport')
  615. def consoleport_delete(request, pk):
  616. consoleport = get_object_or_404(ConsolePort, pk=pk)
  617. if request.method == 'POST':
  618. form = ConfirmationForm(request.POST)
  619. if form.is_valid():
  620. consoleport.delete()
  621. messages.success(request, "Console port {0} has been deleted from {1}".format(consoleport,
  622. consoleport.device))
  623. return redirect('dcim:device', pk=consoleport.device.pk)
  624. else:
  625. form = ConfirmationForm()
  626. return render(request, 'dcim/consoleport_delete.html', {
  627. 'consoleport': consoleport,
  628. 'form': form,
  629. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  630. })
  631. class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  632. permission_required = 'dcim.delete_consoleport'
  633. cls = ConsolePort
  634. parent_cls = Device
  635. class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  636. permission_required = 'dcim.change_consoleport'
  637. form = forms.ConsoleConnectionImportForm
  638. table = tables.ConsoleConnectionTable
  639. template_name = 'dcim/console_connections_import.html'
  640. #
  641. # Console server ports
  642. #
  643. @permission_required('dcim.add_consoleserverport')
  644. def consoleserverport_add(request, pk):
  645. device = get_object_or_404(Device, pk=pk)
  646. if request.method == 'POST':
  647. form = forms.ConsoleServerPortCreateForm(request.POST)
  648. if form.is_valid():
  649. cs_ports = []
  650. for name in form.cleaned_data['name_pattern']:
  651. csp_form = forms.ConsoleServerPortForm({
  652. 'device': device.pk,
  653. 'name': name,
  654. })
  655. if csp_form.is_valid():
  656. cs_ports.append(csp_form.save(commit=False))
  657. else:
  658. form.add_error('name_pattern', "Duplicate console server port name for this device: {}"
  659. .format(name))
  660. if not form.errors:
  661. ConsoleServerPort.objects.bulk_create(cs_ports)
  662. messages.success(request, "Added {} console server port(s) to {}".format(len(cs_ports), device))
  663. if '_addanother' in request.POST:
  664. return redirect('dcim:consoleserverport_add', pk=device.pk)
  665. else:
  666. return redirect('dcim:device', pk=device.pk)
  667. else:
  668. form = forms.ConsoleServerPortCreateForm()
  669. return render(request, 'dcim/consoleserverport_edit.html', {
  670. 'device': device,
  671. 'form': form,
  672. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  673. })
  674. @permission_required('dcim.change_consoleserverport')
  675. def consoleserverport_connect(request, pk):
  676. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  677. if request.method == 'POST':
  678. form = forms.ConsoleServerPortConnectionForm(consoleserverport, request.POST)
  679. if form.is_valid():
  680. consoleport = form.cleaned_data['port']
  681. consoleport.cs_port = consoleserverport
  682. consoleport.connection_status = form.cleaned_data['connection_status']
  683. consoleport.save()
  684. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  685. consoleport.device,
  686. consoleport.name,
  687. consoleserverport.device,
  688. consoleserverport.name,
  689. ))
  690. return redirect('dcim:device', pk=consoleserverport.device.pk)
  691. else:
  692. form = forms.ConsoleServerPortConnectionForm(consoleserverport, initial={'rack': consoleserverport.device.rack})
  693. return render(request, 'dcim/consoleserverport_connect.html', {
  694. 'consoleserverport': consoleserverport,
  695. 'form': form,
  696. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  697. })
  698. @permission_required('dcim.change_consoleserverport')
  699. def consoleserverport_disconnect(request, pk):
  700. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  701. if not hasattr(consoleserverport, 'connected_console'):
  702. messages.warning(request, "Cannot disconnect console server port {0}: Nothing is connected to it"
  703. .format(consoleserverport))
  704. return redirect('dcim:device', pk=consoleserverport.device.pk)
  705. if request.method == 'POST':
  706. form = ConfirmationForm(request.POST)
  707. if form.is_valid():
  708. consoleport = consoleserverport.connected_console
  709. consoleport.cs_port = None
  710. consoleport.connection_status = None
  711. consoleport.save()
  712. messages.success(request, "Console server port {0} has been disconnected".format(consoleserverport))
  713. return redirect('dcim:device', pk=consoleserverport.device.pk)
  714. else:
  715. form = ConfirmationForm()
  716. return render(request, 'dcim/consoleserverport_disconnect.html', {
  717. 'consoleserverport': consoleserverport,
  718. 'form': form,
  719. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  720. })
  721. @permission_required('dcim.change_consoleserverport')
  722. def consoleserverport_edit(request, pk):
  723. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  724. if request.method == 'POST':
  725. form = forms.ConsoleServerPortForm(request.POST, instance=consoleserverport)
  726. if form.is_valid():
  727. consoleserverport = form.save()
  728. messages.success(request, "Modified {0} {1}".format(consoleserverport.device.name, consoleserverport.name))
  729. return redirect('dcim:device', pk=consoleserverport.device.pk)
  730. else:
  731. form = forms.ConsoleServerPortForm(instance=consoleserverport)
  732. return render(request, 'dcim/consoleserverport_edit.html', {
  733. 'consoleserverport': consoleserverport,
  734. 'form': form,
  735. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  736. })
  737. @permission_required('dcim.delete_consoleserverport')
  738. def consoleserverport_delete(request, pk):
  739. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  740. if request.method == 'POST':
  741. form = ConfirmationForm(request.POST)
  742. if form.is_valid():
  743. consoleserverport.delete()
  744. messages.success(request, "Console server port {0} has been deleted from {1}"
  745. .format(consoleserverport, consoleserverport.device))
  746. return redirect('dcim:device', pk=consoleserverport.device.pk)
  747. else:
  748. form = ConfirmationForm()
  749. return render(request, 'dcim/consoleserverport_delete.html', {
  750. 'consoleserverport': consoleserverport,
  751. 'form': form,
  752. 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  753. })
  754. class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  755. permission_required = 'dcim.delete_consoleserverport'
  756. cls = ConsoleServerPort
  757. parent_cls = Device
  758. #
  759. # Power ports
  760. #
  761. @permission_required('dcim.add_powerport')
  762. def powerport_add(request, pk):
  763. device = get_object_or_404(Device, pk=pk)
  764. if request.method == 'POST':
  765. form = forms.PowerPortCreateForm(request.POST)
  766. if form.is_valid():
  767. power_ports = []
  768. for name in form.cleaned_data['name_pattern']:
  769. pp_form = forms.PowerPortForm({
  770. 'device': device.pk,
  771. 'name': name,
  772. })
  773. if pp_form.is_valid():
  774. power_ports.append(pp_form.save(commit=False))
  775. else:
  776. form.add_error('name_pattern', "Duplicate power port name for this device: {}".format(name))
  777. if not form.errors:
  778. PowerPort.objects.bulk_create(power_ports)
  779. messages.success(request, "Added {} power port(s) to {}".format(len(power_ports), device))
  780. if '_addanother' in request.POST:
  781. return redirect('dcim:powerport_add', pk=device.pk)
  782. else:
  783. return redirect('dcim:device', pk=device.pk)
  784. else:
  785. form = forms.PowerPortCreateForm()
  786. return render(request, 'dcim/powerport_edit.html', {
  787. 'device': device,
  788. 'form': form,
  789. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  790. })
  791. @permission_required('dcim.change_powerport')
  792. def powerport_connect(request, pk):
  793. powerport = get_object_or_404(PowerPort, pk=pk)
  794. if request.method == 'POST':
  795. form = forms.PowerPortConnectionForm(request.POST, instance=powerport)
  796. if form.is_valid():
  797. powerport = form.save()
  798. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  799. powerport.device,
  800. powerport.name,
  801. powerport.power_outlet.device,
  802. powerport.power_outlet.name,
  803. ))
  804. return redirect('dcim:device', pk=powerport.device.pk)
  805. else:
  806. form = forms.PowerPortConnectionForm(instance=powerport, initial={
  807. 'rack': powerport.device.rack,
  808. 'connection_status': CONNECTION_STATUS_CONNECTED,
  809. })
  810. return render(request, 'dcim/powerport_connect.html', {
  811. 'powerport': powerport,
  812. 'form': form,
  813. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  814. })
  815. @permission_required('dcim.change_powerport')
  816. def powerport_disconnect(request, pk):
  817. powerport = get_object_or_404(PowerPort, pk=pk)
  818. if not powerport.power_outlet:
  819. messages.warning(request, "Cannot disconnect power port {0}: It is not connected to an outlet"
  820. .format(powerport))
  821. return redirect('dcim:device', pk=powerport.device.pk)
  822. if request.method == 'POST':
  823. form = ConfirmationForm(request.POST)
  824. if form.is_valid():
  825. powerport.power_outlet = None
  826. powerport.connection_status = None
  827. powerport.save()
  828. messages.success(request, "Power port {0} has been disconnected".format(powerport))
  829. return redirect('dcim:device', pk=powerport.device.pk)
  830. else:
  831. form = ConfirmationForm()
  832. return render(request, 'dcim/powerport_disconnect.html', {
  833. 'powerport': powerport,
  834. 'form': form,
  835. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  836. })
  837. @permission_required('dcim.change_powerport')
  838. def powerport_edit(request, pk):
  839. powerport = get_object_or_404(PowerPort, pk=pk)
  840. if request.method == 'POST':
  841. form = forms.PowerPortForm(request.POST, instance=powerport)
  842. if form.is_valid():
  843. powerport = form.save()
  844. messages.success(request, "Modified {0} power port {1}".format(powerport.device.name, powerport.name))
  845. return redirect('dcim:device', pk=powerport.device.pk)
  846. else:
  847. form = forms.PowerPortForm(instance=powerport)
  848. return render(request, 'dcim/powerport_edit.html', {
  849. 'powerport': powerport,
  850. 'form': form,
  851. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  852. })
  853. @permission_required('dcim.delete_powerport')
  854. def powerport_delete(request, pk):
  855. powerport = get_object_or_404(PowerPort, pk=pk)
  856. if request.method == 'POST':
  857. form = ConfirmationForm(request.POST)
  858. if form.is_valid():
  859. powerport.delete()
  860. messages.success(request, "Power port {0} has been deleted from {1}".format(powerport, powerport.device))
  861. return redirect('dcim:device', pk=powerport.device.pk)
  862. else:
  863. form = ConfirmationForm()
  864. return render(request, 'dcim/powerport_delete.html', {
  865. 'powerport': powerport,
  866. 'form': form,
  867. 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  868. })
  869. class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  870. permission_required = 'dcim.delete_powerport'
  871. cls = PowerPort
  872. parent_cls = Device
  873. class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  874. permission_required = 'dcim.change_powerport'
  875. form = forms.PowerConnectionImportForm
  876. table = tables.PowerConnectionTable
  877. template_name = 'dcim/power_connections_import.html'
  878. #
  879. # Power outlets
  880. #
  881. @permission_required('dcim.add_poweroutlet')
  882. def poweroutlet_add(request, pk):
  883. device = get_object_or_404(Device, pk=pk)
  884. if request.method == 'POST':
  885. form = forms.PowerOutletCreateForm(request.POST)
  886. if form.is_valid():
  887. power_outlets = []
  888. for name in form.cleaned_data['name_pattern']:
  889. po_form = forms.PowerOutletForm({
  890. 'device': device.pk,
  891. 'name': name,
  892. })
  893. if po_form.is_valid():
  894. power_outlets.append(po_form.save(commit=False))
  895. else:
  896. form.add_error('name_pattern', "Duplicate power outlet name for this device: {}".format(name))
  897. if not form.errors:
  898. PowerOutlet.objects.bulk_create(power_outlets)
  899. messages.success(request, "Added {} power outlet(s) to {}".format(len(power_outlets), device))
  900. if '_addanother' in request.POST:
  901. return redirect('dcim:poweroutlet_add', pk=device.pk)
  902. else:
  903. return redirect('dcim:device', pk=device.pk)
  904. else:
  905. form = forms.PowerOutletCreateForm()
  906. return render(request, 'dcim/poweroutlet_edit.html', {
  907. 'device': device,
  908. 'form': form,
  909. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  910. })
  911. @permission_required('dcim.change_poweroutlet')
  912. def poweroutlet_connect(request, pk):
  913. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  914. if request.method == 'POST':
  915. form = forms.PowerOutletConnectionForm(poweroutlet, request.POST)
  916. if form.is_valid():
  917. powerport = form.cleaned_data['port']
  918. powerport.power_outlet = poweroutlet
  919. powerport.connection_status = form.cleaned_data['connection_status']
  920. powerport.save()
  921. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  922. powerport.device,
  923. powerport.name,
  924. poweroutlet.device,
  925. poweroutlet.name,
  926. ))
  927. return redirect('dcim:device', pk=poweroutlet.device.pk)
  928. else:
  929. form = forms.PowerOutletConnectionForm(poweroutlet, initial={'rack': poweroutlet.device.rack})
  930. return render(request, 'dcim/poweroutlet_connect.html', {
  931. 'poweroutlet': poweroutlet,
  932. 'form': form,
  933. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  934. })
  935. @permission_required('dcim.change_poweroutlet')
  936. def poweroutlet_disconnect(request, pk):
  937. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  938. if not hasattr(poweroutlet, 'connected_port'):
  939. messages.warning(request, "Cannot disconnect power outlet {0}: Nothing is connected to it".format(poweroutlet))
  940. return redirect('dcim:device', pk=poweroutlet.device.pk)
  941. if request.method == 'POST':
  942. form = ConfirmationForm(request.POST)
  943. if form.is_valid():
  944. powerport = poweroutlet.connected_port
  945. powerport.power_outlet = None
  946. powerport.connection_status = None
  947. powerport.save()
  948. messages.success(request, "Power outlet {0} has been disconnected".format(poweroutlet))
  949. return redirect('dcim:device', pk=poweroutlet.device.pk)
  950. else:
  951. form = ConfirmationForm()
  952. return render(request, 'dcim/poweroutlet_disconnect.html', {
  953. 'poweroutlet': poweroutlet,
  954. 'form': form,
  955. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  956. })
  957. @permission_required('dcim.change_poweroutlet')
  958. def poweroutlet_edit(request, pk):
  959. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  960. if request.method == 'POST':
  961. form = forms.PowerOutletForm(request.POST, instance=poweroutlet)
  962. if form.is_valid():
  963. poweroutlet = form.save()
  964. messages.success(request, "Modified {0} power outlet {1}".format(poweroutlet.device.name, poweroutlet.name))
  965. return redirect('dcim:device', pk=poweroutlet.device.pk)
  966. else:
  967. form = forms.PowerOutletForm(instance=poweroutlet)
  968. return render(request, 'dcim/poweroutlet_edit.html', {
  969. 'poweroutlet': poweroutlet,
  970. 'form': form,
  971. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  972. })
  973. @permission_required('dcim.delete_poweroutlet')
  974. def poweroutlet_delete(request, pk):
  975. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  976. if request.method == 'POST':
  977. form = ConfirmationForm(request.POST)
  978. if form.is_valid():
  979. poweroutlet.delete()
  980. messages.success(request, "Power outlet {0} has been deleted from {1}".format(poweroutlet,
  981. poweroutlet.device))
  982. return redirect('dcim:device', pk=poweroutlet.device.pk)
  983. else:
  984. form = ConfirmationForm()
  985. return render(request, 'dcim/poweroutlet_delete.html', {
  986. 'poweroutlet': poweroutlet,
  987. 'form': form,
  988. 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  989. })
  990. class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  991. permission_required = 'dcim.delete_poweroutlet'
  992. cls = PowerOutlet
  993. parent_cls = Device
  994. #
  995. # Interfaces
  996. #
  997. @permission_required('dcim.add_interface')
  998. def interface_add(request, pk):
  999. device = get_object_or_404(Device, pk=pk)
  1000. if request.method == 'POST':
  1001. form = forms.InterfaceCreateForm(request.POST)
  1002. if form.is_valid():
  1003. interfaces = []
  1004. for name in form.cleaned_data['name_pattern']:
  1005. iface_form = forms.InterfaceForm({
  1006. 'device': device.pk,
  1007. 'name': name,
  1008. 'form_factor': form.cleaned_data['form_factor'],
  1009. 'mac_address': form.cleaned_data['mac_address'],
  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('name_pattern', "Duplicate interface name for this device: {}".format(name))
  1017. if not form.errors:
  1018. Interface.objects.bulk_create(interfaces)
  1019. messages.success(request, "Added {} interface(s) to {}".format(len(interfaces), device))
  1020. if '_addanother' in request.POST:
  1021. return redirect('dcim:interface_add', pk=device.pk)
  1022. else:
  1023. return redirect('dcim:device', pk=device.pk)
  1024. else:
  1025. form = forms.InterfaceCreateForm()
  1026. return render(request, 'dcim/interface_edit.html', {
  1027. 'device': device,
  1028. 'form': form,
  1029. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1030. })
  1031. @permission_required('dcim.change_interface')
  1032. def interface_edit(request, pk):
  1033. interface = get_object_or_404(Interface, pk=pk)
  1034. if request.method == 'POST':
  1035. form = forms.InterfaceForm(request.POST, instance=interface)
  1036. if form.is_valid():
  1037. interface = form.save()
  1038. messages.success(request, "Modified {0} interface {1}".format(interface.device.name, interface.name))
  1039. return redirect('dcim:device', pk=interface.device.pk)
  1040. else:
  1041. form = forms.InterfaceForm(instance=interface)
  1042. return render(request, 'dcim/interface_edit.html', {
  1043. 'interface': interface,
  1044. 'form': form,
  1045. 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}),
  1046. })
  1047. @permission_required('dcim.delete_interface')
  1048. def interface_delete(request, pk):
  1049. interface = get_object_or_404(Interface, pk=pk)
  1050. if request.method == 'POST':
  1051. form = ConfirmationForm(request.POST)
  1052. if form.is_valid():
  1053. interface.delete()
  1054. messages.success(request, "Interface {0} has been deleted from {1}".format(interface, interface.device))
  1055. return redirect('dcim:device', pk=interface.device.pk)
  1056. else:
  1057. form = ConfirmationForm()
  1058. return render(request, 'dcim/interface_delete.html', {
  1059. 'interface': interface,
  1060. 'form': form,
  1061. 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}),
  1062. })
  1063. class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView):
  1064. permission_required = 'dcim.add_interface'
  1065. cls = Device
  1066. form = forms.InterfaceBulkCreateForm
  1067. template_name = 'dcim/interface_add_multi.html'
  1068. default_redirect_url = 'dcim:device_list'
  1069. def update_objects(self, pk_list, form):
  1070. selected_devices = Device.objects.filter(pk__in=pk_list)
  1071. interfaces = []
  1072. for device in selected_devices:
  1073. for name in form.cleaned_data['name_pattern']:
  1074. iface_form = forms.InterfaceForm({
  1075. 'device': device.pk,
  1076. 'name': name,
  1077. 'mac_address': form.cleaned_data['mac_address'],
  1078. 'form_factor': form.cleaned_data['form_factor'],
  1079. 'mgmt_only': form.cleaned_data['mgmt_only'],
  1080. 'description': form.cleaned_data['description'],
  1081. })
  1082. if iface_form.is_valid():
  1083. interfaces.append(iface_form.save(commit=False))
  1084. else:
  1085. form.add_error(None, "Duplicate interface {} found for device {}".format(name, device))
  1086. if not form.errors:
  1087. Interface.objects.bulk_create(interfaces)
  1088. messages.success(self.request, "Added {} interfaces to {} devices".format(len(interfaces),
  1089. len(selected_devices)))
  1090. class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1091. permission_required = 'dcim.delete_interface'
  1092. cls = Interface
  1093. parent_cls = Device
  1094. #
  1095. # Device bays
  1096. #
  1097. @permission_required('dcim.add_devicebay')
  1098. def devicebay_add(request, pk):
  1099. device = get_object_or_404(Device, pk=pk)
  1100. if request.method == 'POST':
  1101. form = forms.DeviceBayCreateForm(request.POST)
  1102. if form.is_valid():
  1103. device_bays = []
  1104. for name in form.cleaned_data['name_pattern']:
  1105. devicebay_form = forms.DeviceBayForm({
  1106. 'device': device.pk,
  1107. 'name': name,
  1108. })
  1109. if devicebay_form.is_valid():
  1110. device_bays.append(devicebay_form.save(commit=False))
  1111. else:
  1112. for err in devicebay_form.errors.get('__all__', []):
  1113. form.add_error('name_pattern', err)
  1114. if not form.errors:
  1115. DeviceBay.objects.bulk_create(device_bays)
  1116. messages.success(request, "Added {} device bay(s) to {}".format(len(device_bays), device))
  1117. if '_addanother' in request.POST:
  1118. return redirect('dcim:devicebay_add', pk=device.pk)
  1119. else:
  1120. return redirect('dcim:device', pk=device.pk)
  1121. else:
  1122. form = forms.DeviceBayCreateForm()
  1123. return render(request, 'dcim/devicebay_edit.html', {
  1124. 'device': device,
  1125. 'form': form,
  1126. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1127. })
  1128. @permission_required('dcim.change_devicebay')
  1129. def devicebay_edit(request, pk):
  1130. devicebay = get_object_or_404(DeviceBay, pk=pk)
  1131. if request.method == 'POST':
  1132. form = forms.DeviceBayForm(request.POST, instance=devicebay)
  1133. if form.is_valid():
  1134. devicebay = form.save()
  1135. messages.success(request, "Modified {} bay {}".format(devicebay.device.name, devicebay.name))
  1136. return redirect('dcim:device', pk=devicebay.device.pk)
  1137. else:
  1138. form = forms.DeviceBayForm(instance=devicebay)
  1139. return render(request, 'dcim/devicebay_edit.html', {
  1140. 'devicebay': devicebay,
  1141. 'form': form,
  1142. 'cancel_url': reverse('dcim:device', kwargs={'pk': devicebay.device.pk}),
  1143. })
  1144. @permission_required('dcim.delete_devicebay')
  1145. def devicebay_delete(request, pk):
  1146. devicebay = get_object_or_404(DeviceBay, pk=pk)
  1147. if request.method == 'POST':
  1148. form = ConfirmationForm(request.POST)
  1149. if form.is_valid():
  1150. devicebay.delete()
  1151. messages.success(request, "Device bay {} has been deleted from {}".format(devicebay, devicebay.device))
  1152. return redirect('dcim:device', pk=devicebay.device.pk)
  1153. else:
  1154. form = ConfirmationForm()
  1155. return render(request, 'dcim/devicebay_delete.html', {
  1156. 'devicebay': devicebay,
  1157. 'form': form,
  1158. 'cancel_url': reverse('dcim:device', kwargs={'pk': devicebay.device.pk}),
  1159. })
  1160. @permission_required('dcim.change_devicebay')
  1161. def devicebay_populate(request, pk):
  1162. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1163. if request.method == 'POST':
  1164. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  1165. if form.is_valid():
  1166. device_bay.installed_device = form.cleaned_data['installed_device']
  1167. device_bay.save()
  1168. if not form.errors:
  1169. messages.success(request, "Added {} to {}".format(device_bay.installed_device, device_bay))
  1170. return redirect('dcim:device', pk=device_bay.device.pk)
  1171. else:
  1172. form = forms.PopulateDeviceBayForm(device_bay)
  1173. return render(request, 'dcim/devicebay_populate.html', {
  1174. 'device_bay': device_bay,
  1175. 'form': form,
  1176. 'cancel_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1177. })
  1178. @permission_required('dcim.change_devicebay')
  1179. def devicebay_depopulate(request, pk):
  1180. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1181. if request.method == 'POST':
  1182. form = ConfirmationForm(request.POST)
  1183. if form.is_valid():
  1184. removed_device = device_bay.installed_device
  1185. device_bay.installed_device = None
  1186. device_bay.save()
  1187. messages.success(request, "{} has been removed from {}".format(removed_device, device_bay))
  1188. return redirect('dcim:device', pk=device_bay.device.pk)
  1189. else:
  1190. form = ConfirmationForm()
  1191. return render(request, 'dcim/devicebay_depopulate.html', {
  1192. 'device_bay': device_bay,
  1193. 'form': form,
  1194. 'cancel_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1195. })
  1196. class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1197. permission_required = 'dcim.delete_devicebay'
  1198. cls = DeviceBay
  1199. parent_cls = Device
  1200. #
  1201. # Interface connections
  1202. #
  1203. @permission_required('dcim.add_interfaceconnection')
  1204. def interfaceconnection_add(request, pk):
  1205. device = get_object_or_404(Device, pk=pk)
  1206. if request.method == 'POST':
  1207. form = forms.InterfaceConnectionForm(device, request.POST)
  1208. if form.is_valid():
  1209. interfaceconnection = form.save()
  1210. messages.success(request, "Connected {0} {1} to {2} {3}".format(
  1211. interfaceconnection.interface_a.device,
  1212. interfaceconnection.interface_a,
  1213. interfaceconnection.interface_b.device,
  1214. interfaceconnection.interface_b,
  1215. ))
  1216. if '_addanother' in request.POST:
  1217. base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
  1218. params = urlencode({
  1219. 'rack_b': interfaceconnection.interface_b.device.rack.pk,
  1220. 'device_b': interfaceconnection.interface_b.device.pk,
  1221. })
  1222. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  1223. else:
  1224. return redirect('dcim:device', pk=device.pk)
  1225. else:
  1226. form = forms.InterfaceConnectionForm(device, initial={
  1227. 'interface_a': request.GET.get('interface', None),
  1228. 'rack_b': request.GET.get('rack_b', None),
  1229. 'device_b': request.GET.get('device_b', None),
  1230. })
  1231. return render(request, 'dcim/interfaceconnection_edit.html', {
  1232. 'device': device,
  1233. 'form': form,
  1234. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1235. })
  1236. @permission_required('dcim.delete_interfaceconnection')
  1237. def interfaceconnection_delete(request, pk):
  1238. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1239. device_id = request.GET.get('device', None)
  1240. if request.method == 'POST':
  1241. form = forms.InterfaceConnectionDeletionForm(request.POST)
  1242. if form.is_valid():
  1243. interfaceconnection.delete()
  1244. messages.success(request, "Deleted the connection between {0} {1} and {2} {3}".format(
  1245. interfaceconnection.interface_a.device,
  1246. interfaceconnection.interface_a,
  1247. interfaceconnection.interface_b.device,
  1248. interfaceconnection.interface_b,
  1249. ))
  1250. if form.cleaned_data['device']:
  1251. return redirect('dcim:device', pk=form.cleaned_data['device'].pk)
  1252. else:
  1253. return redirect('dcim:device_list')
  1254. else:
  1255. form = forms.InterfaceConnectionDeletionForm(initial={
  1256. 'device': device_id,
  1257. })
  1258. # Determine where to direct user upon cancellation
  1259. if device_id:
  1260. cancel_url = reverse('dcim:device', kwargs={'pk': device_id})
  1261. else:
  1262. cancel_url = reverse('dcim:device_list')
  1263. return render(request, 'dcim/interfaceconnection_delete.html', {
  1264. 'interfaceconnection': interfaceconnection,
  1265. 'device_id': device_id,
  1266. 'form': form,
  1267. 'cancel_url': cancel_url,
  1268. })
  1269. class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1270. permission_required = 'dcim.change_interface'
  1271. form = forms.InterfaceConnectionImportForm
  1272. table = tables.InterfaceConnectionTable
  1273. template_name = 'dcim/interface_connections_import.html'
  1274. #
  1275. # Connections
  1276. #
  1277. class ConsoleConnectionsListView(ObjectListView):
  1278. queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\
  1279. .order_by('cs_port__device__name', 'cs_port__name')
  1280. filter = filters.ConsoleConnectionFilter
  1281. filter_form = forms.ConsoleConnectionFilterForm
  1282. table = tables.ConsoleConnectionTable
  1283. template_name = 'dcim/console_connections_list.html'
  1284. class PowerConnectionsListView(ObjectListView):
  1285. queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\
  1286. .order_by('power_outlet__device__name', 'power_outlet__name')
  1287. filter = filters.PowerConnectionFilter
  1288. filter_form = forms.PowerConnectionFilterForm
  1289. table = tables.PowerConnectionTable
  1290. template_name = 'dcim/power_connections_list.html'
  1291. class InterfaceConnectionsListView(ObjectListView):
  1292. queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\
  1293. .order_by('interface_a__device__name', 'interface_a__name')
  1294. filter = filters.InterfaceConnectionFilter
  1295. filter_form = forms.InterfaceConnectionFilterForm
  1296. table = tables.InterfaceConnectionTable
  1297. template_name = 'dcim/interface_connections_list.html'
  1298. #
  1299. # IP addresses
  1300. #
  1301. @permission_required('ipam.add_ipaddress')
  1302. def ipaddress_assign(request, pk):
  1303. device = get_object_or_404(Device, pk=pk)
  1304. if request.method == 'POST':
  1305. form = forms.IPAddressForm(device, request.POST)
  1306. if form.is_valid():
  1307. ipaddress = form.save(commit=False)
  1308. ipaddress.interface = form.cleaned_data['interface']
  1309. ipaddress.save()
  1310. messages.success(request, "Added new IP address {0} to interface {1}".format(ipaddress,
  1311. ipaddress.interface))
  1312. if form.cleaned_data['set_as_primary']:
  1313. if ipaddress.family == 4:
  1314. device.primary_ip4 = ipaddress
  1315. elif ipaddress.family == 6:
  1316. device.primary_ip6 = ipaddress
  1317. device.save()
  1318. if '_addanother' in request.POST:
  1319. return redirect('dcim:ipaddress_assign', pk=device.pk)
  1320. else:
  1321. return redirect('dcim:device', pk=device.pk)
  1322. else:
  1323. form = forms.IPAddressForm(device)
  1324. return render(request, 'dcim/ipaddress_assign.html', {
  1325. 'device': device,
  1326. 'form': form,
  1327. 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1328. })
  1329. #
  1330. # Modules
  1331. #
  1332. @permission_required('dcim.add_module')
  1333. def module_add(request, pk):
  1334. device = get_object_or_404(Device, pk=pk)
  1335. if request.method == 'POST':
  1336. form = forms.ModuleForm(request.POST)
  1337. if form.is_valid():
  1338. module = form.save(commit=False)
  1339. module.device = device
  1340. module.save()
  1341. messages.success(request, "Added module {} to {}".format(module.name, module.device.name))
  1342. if '_addanother' in request.POST:
  1343. return redirect('dcim:module_add', pk=module.device.pk)
  1344. else:
  1345. return redirect('dcim:device_inventory', pk=module.device.pk)
  1346. else:
  1347. form = forms.ModuleForm()
  1348. return render(request, 'dcim/module_edit.html', {
  1349. 'device': device,
  1350. 'form': form,
  1351. 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': device.pk}),
  1352. })
  1353. @permission_required('dcim.change_module')
  1354. def module_edit(request, pk):
  1355. module = get_object_or_404(Module, pk=pk)
  1356. if request.method == 'POST':
  1357. form = forms.ModuleForm(request.POST, instance=module)
  1358. if form.is_valid():
  1359. module = form.save()
  1360. messages.success(request, "Modified {} module {}".format(module.device.name, module.name))
  1361. return redirect('dcim:device_inventory', pk=module.device.pk)
  1362. else:
  1363. form = forms.ModuleForm(instance=module)
  1364. return render(request, 'dcim/module_edit.html', {
  1365. 'module': module,
  1366. 'form': form,
  1367. 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': module.device.pk}),
  1368. })
  1369. @permission_required('dcim.delete_module')
  1370. def module_delete(request, pk):
  1371. module = get_object_or_404(Module, pk=pk)
  1372. if request.method == 'POST':
  1373. form = ConfirmationForm(request.POST)
  1374. if form.is_valid():
  1375. module.delete()
  1376. messages.success(request, "Module {} has been deleted from {}".format(module, module.device))
  1377. return redirect('dcim:device_inventory', pk=module.device.pk)
  1378. else:
  1379. form = ConfirmationForm()
  1380. return render(request, 'dcim/module_delete.html', {
  1381. 'module': module,
  1382. 'form': form,
  1383. 'cancel_url': reverse('dcim:device_inventory', kwargs={'pk': module.device.pk}),
  1384. })