views.py 60 KB

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