views.py 53 KB

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