views.py 62 KB

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