views.py 51 KB

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