views.py 91 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675
  1. from collections import OrderedDict
  2. import re
  3. from django.conf import settings
  4. from django.contrib import messages
  5. from django.contrib.auth.mixins import PermissionRequiredMixin
  6. from django.contrib.contenttypes.models import ContentType
  7. from django.core.paginator import EmptyPage, PageNotAnInteger
  8. from django.db import transaction
  9. from django.db.models import Count, F
  10. from django.forms import modelformset_factory
  11. from django.shortcuts import get_object_or_404, redirect, render
  12. from django.urls import reverse
  13. from django.utils.html import escape
  14. from django.utils.http import is_safe_url
  15. from django.utils.safestring import mark_safe
  16. from django.views.generic import View
  17. from circuits.models import Circuit
  18. from extras.models import Graph
  19. from extras.views import ObjectConfigContextView
  20. from ipam.models import Prefix, VLAN
  21. from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
  22. from utilities.forms import ConfirmationForm
  23. from utilities.paginator import EnhancedPaginator
  24. from utilities.utils import csv_format
  25. from utilities.views import (
  26. BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin,
  27. ObjectImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
  28. )
  29. from virtualization.models import VirtualMachine
  30. from . import filters, forms, tables
  31. from .choices import DeviceFaceChoices
  32. from .constants import NONCONNECTABLE_IFACE_TYPES
  33. from .models import (
  34. Cable, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
  35. DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate,
  36. InventoryItem, Manufacturer, Platform, PowerFeed, PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort,
  37. PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site,
  38. VirtualChassis,
  39. )
  40. class BulkRenameView(GetReturnURLMixin, View):
  41. """
  42. An extendable view for renaming device components in bulk.
  43. """
  44. queryset = None
  45. form = None
  46. template_name = 'dcim/bulk_rename.html'
  47. def post(self, request):
  48. model = self.queryset.model
  49. if '_preview' in request.POST or '_apply' in request.POST:
  50. form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
  51. selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
  52. if form.is_valid():
  53. for obj in selected_objects:
  54. find = form.cleaned_data['find']
  55. replace = form.cleaned_data['replace']
  56. if form.cleaned_data['use_regex']:
  57. try:
  58. obj.new_name = re.sub(find, replace, obj.name)
  59. # Catch regex group reference errors
  60. except re.error:
  61. obj.new_name = obj.name
  62. else:
  63. obj.new_name = obj.name.replace(find, replace)
  64. if '_apply' in request.POST:
  65. for obj in selected_objects:
  66. obj.name = obj.new_name
  67. obj.save()
  68. messages.success(request, "Renamed {} {}".format(
  69. len(selected_objects),
  70. model._meta.verbose_name_plural
  71. ))
  72. return redirect(self.get_return_url(request))
  73. else:
  74. form = self.form(initial={'pk': request.POST.getlist('pk')})
  75. selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
  76. return render(request, self.template_name, {
  77. 'form': form,
  78. 'obj_type_plural': model._meta.verbose_name_plural,
  79. 'selected_objects': selected_objects,
  80. 'return_url': self.get_return_url(request),
  81. })
  82. class BulkDisconnectView(GetReturnURLMixin, View):
  83. """
  84. An extendable view for disconnection console/power/interface components in bulk.
  85. """
  86. model = None
  87. form = None
  88. template_name = 'dcim/bulk_disconnect.html'
  89. def post(self, request):
  90. selected_objects = []
  91. return_url = self.get_return_url(request)
  92. if '_confirm' in request.POST:
  93. form = self.form(request.POST)
  94. if form.is_valid():
  95. with transaction.atomic():
  96. count = 0
  97. for obj in self.model.objects.filter(pk__in=form.cleaned_data['pk']):
  98. if obj.cable is None:
  99. continue
  100. obj.cable.delete()
  101. count += 1
  102. messages.success(request, "Disconnected {} {}".format(
  103. count, self.model._meta.verbose_name_plural
  104. ))
  105. return redirect(return_url)
  106. else:
  107. form = self.form(initial={'pk': request.POST.getlist('pk')})
  108. selected_objects = self.model.objects.filter(pk__in=form.initial['pk'])
  109. return render(request, self.template_name, {
  110. 'form': form,
  111. 'obj_type_plural': self.model._meta.verbose_name_plural,
  112. 'selected_objects': selected_objects,
  113. 'return_url': return_url,
  114. })
  115. #
  116. # Regions
  117. #
  118. class RegionListView(PermissionRequiredMixin, ObjectListView):
  119. permission_required = 'dcim.view_region'
  120. queryset = Region.objects.add_related_count(
  121. Region.objects.all(),
  122. Site,
  123. 'region',
  124. 'site_count',
  125. cumulative=True
  126. )
  127. filterset = filters.RegionFilterSet
  128. filterset_form = forms.RegionFilterForm
  129. table = tables.RegionTable
  130. class RegionCreateView(PermissionRequiredMixin, ObjectEditView):
  131. permission_required = 'dcim.add_region'
  132. model = Region
  133. model_form = forms.RegionForm
  134. default_return_url = 'dcim:region_list'
  135. class RegionEditView(RegionCreateView):
  136. permission_required = 'dcim.change_region'
  137. class RegionBulkImportView(PermissionRequiredMixin, BulkImportView):
  138. permission_required = 'dcim.add_region'
  139. model_form = forms.RegionCSVForm
  140. table = tables.RegionTable
  141. default_return_url = 'dcim:region_list'
  142. class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  143. permission_required = 'dcim.delete_region'
  144. queryset = Region.objects.all()
  145. filterset = filters.RegionFilterSet
  146. table = tables.RegionTable
  147. default_return_url = 'dcim:region_list'
  148. #
  149. # Sites
  150. #
  151. class SiteListView(PermissionRequiredMixin, ObjectListView):
  152. permission_required = 'dcim.view_site'
  153. queryset = Site.objects.prefetch_related('region', 'tenant')
  154. filterset = filters.SiteFilterSet
  155. filterset_form = forms.SiteFilterForm
  156. table = tables.SiteTable
  157. class SiteView(PermissionRequiredMixin, View):
  158. permission_required = 'dcim.view_site'
  159. def get(self, request, slug):
  160. site = get_object_or_404(Site.objects.prefetch_related('region', 'tenant__group'), slug=slug)
  161. stats = {
  162. 'rack_count': Rack.objects.filter(site=site).count(),
  163. 'device_count': Device.objects.filter(site=site).count(),
  164. 'prefix_count': Prefix.objects.filter(site=site).count(),
  165. 'vlan_count': VLAN.objects.filter(site=site).count(),
  166. 'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
  167. 'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(),
  168. }
  169. rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
  170. show_graphs = Graph.objects.filter(type__model='site').exists()
  171. return render(request, 'dcim/site.html', {
  172. 'site': site,
  173. 'stats': stats,
  174. 'rack_groups': rack_groups,
  175. 'show_graphs': show_graphs,
  176. })
  177. class SiteCreateView(PermissionRequiredMixin, ObjectEditView):
  178. permission_required = 'dcim.add_site'
  179. model = Site
  180. model_form = forms.SiteForm
  181. template_name = 'dcim/site_edit.html'
  182. default_return_url = 'dcim:site_list'
  183. class SiteEditView(SiteCreateView):
  184. permission_required = 'dcim.change_site'
  185. class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  186. permission_required = 'dcim.delete_site'
  187. model = Site
  188. default_return_url = 'dcim:site_list'
  189. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  190. permission_required = 'dcim.add_site'
  191. model_form = forms.SiteCSVForm
  192. table = tables.SiteTable
  193. default_return_url = 'dcim:site_list'
  194. class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
  195. permission_required = 'dcim.change_site'
  196. queryset = Site.objects.prefetch_related('region', 'tenant')
  197. filterset = filters.SiteFilterSet
  198. table = tables.SiteTable
  199. form = forms.SiteBulkEditForm
  200. default_return_url = 'dcim:site_list'
  201. class SiteBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  202. permission_required = 'dcim.delete_site'
  203. queryset = Site.objects.prefetch_related('region', 'tenant')
  204. filterset = filters.SiteFilterSet
  205. table = tables.SiteTable
  206. default_return_url = 'dcim:site_list'
  207. #
  208. # Rack groups
  209. #
  210. class RackGroupListView(PermissionRequiredMixin, ObjectListView):
  211. permission_required = 'dcim.view_rackgroup'
  212. queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
  213. filterset = filters.RackGroupFilterSet
  214. filterset_form = forms.RackGroupFilterForm
  215. table = tables.RackGroupTable
  216. class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView):
  217. permission_required = 'dcim.add_rackgroup'
  218. model = RackGroup
  219. model_form = forms.RackGroupForm
  220. default_return_url = 'dcim:rackgroup_list'
  221. class RackGroupEditView(RackGroupCreateView):
  222. permission_required = 'dcim.change_rackgroup'
  223. class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
  224. permission_required = 'dcim.add_rackgroup'
  225. model_form = forms.RackGroupCSVForm
  226. table = tables.RackGroupTable
  227. default_return_url = 'dcim:rackgroup_list'
  228. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  229. permission_required = 'dcim.delete_rackgroup'
  230. queryset = RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks'))
  231. filterset = filters.RackGroupFilterSet
  232. table = tables.RackGroupTable
  233. default_return_url = 'dcim:rackgroup_list'
  234. #
  235. # Rack roles
  236. #
  237. class RackRoleListView(PermissionRequiredMixin, ObjectListView):
  238. permission_required = 'dcim.view_rackrole'
  239. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  240. table = tables.RackRoleTable
  241. class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  242. permission_required = 'dcim.add_rackrole'
  243. model = RackRole
  244. model_form = forms.RackRoleForm
  245. default_return_url = 'dcim:rackrole_list'
  246. class RackRoleEditView(RackRoleCreateView):
  247. permission_required = 'dcim.change_rackrole'
  248. class RackRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  249. permission_required = 'dcim.add_rackrole'
  250. model_form = forms.RackRoleCSVForm
  251. table = tables.RackRoleTable
  252. default_return_url = 'dcim:rackrole_list'
  253. class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  254. permission_required = 'dcim.delete_rackrole'
  255. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  256. table = tables.RackRoleTable
  257. default_return_url = 'dcim:rackrole_list'
  258. #
  259. # Racks
  260. #
  261. class RackListView(PermissionRequiredMixin, ObjectListView):
  262. permission_required = 'dcim.view_rack'
  263. queryset = Rack.objects.prefetch_related(
  264. 'site', 'group', 'tenant', 'role', 'devices__device_type'
  265. ).annotate(
  266. device_count=Count('devices')
  267. )
  268. filterset = filters.RackFilterSet
  269. filterset_form = forms.RackFilterForm
  270. table = tables.RackDetailTable
  271. class RackElevationListView(PermissionRequiredMixin, View):
  272. """
  273. Display a set of rack elevations side-by-side.
  274. """
  275. permission_required = 'dcim.view_rack'
  276. def get(self, request):
  277. racks = Rack.objects.prefetch_related('role')
  278. racks = filters.RackFilterSet(request.GET, racks).qs
  279. total_count = racks.count()
  280. # Pagination
  281. per_page = request.GET.get('per_page', settings.PAGINATE_COUNT)
  282. page_number = request.GET.get('page', 1)
  283. paginator = EnhancedPaginator(racks, per_page)
  284. try:
  285. page = paginator.page(page_number)
  286. except PageNotAnInteger:
  287. page = paginator.page(1)
  288. except EmptyPage:
  289. page = paginator.page(paginator.num_pages)
  290. # Determine rack face
  291. rack_face = request.GET.get('face', DeviceFaceChoices.FACE_FRONT)
  292. if rack_face not in DeviceFaceChoices.values():
  293. rack_face = DeviceFaceChoices.FACE_FRONT
  294. return render(request, 'dcim/rack_elevation_list.html', {
  295. 'paginator': paginator,
  296. 'page': page,
  297. 'total_count': total_count,
  298. 'rack_face': rack_face,
  299. 'filter_form': forms.RackElevationFilterForm(request.GET),
  300. })
  301. class RackView(PermissionRequiredMixin, View):
  302. permission_required = 'dcim.view_rack'
  303. def get(self, request, pk):
  304. rack = get_object_or_404(Rack.objects.prefetch_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
  305. nonracked_devices = Device.objects.filter(
  306. rack=rack,
  307. position__isnull=True,
  308. parent_bay__isnull=True
  309. ).prefetch_related('device_type__manufacturer')
  310. if rack.group:
  311. peer_racks = Rack.objects.filter(site=rack.site, group=rack.group)
  312. else:
  313. peer_racks = Rack.objects.filter(site=rack.site, group__isnull=True)
  314. next_rack = peer_racks.filter(name__gt=rack.name).order_by('name').first()
  315. prev_rack = peer_racks.filter(name__lt=rack.name).order_by('-name').first()
  316. reservations = RackReservation.objects.filter(rack=rack)
  317. power_feeds = PowerFeed.objects.filter(rack=rack).prefetch_related('power_panel')
  318. return render(request, 'dcim/rack.html', {
  319. 'rack': rack,
  320. 'reservations': reservations,
  321. 'power_feeds': power_feeds,
  322. 'nonracked_devices': nonracked_devices,
  323. 'next_rack': next_rack,
  324. 'prev_rack': prev_rack,
  325. })
  326. class RackCreateView(PermissionRequiredMixin, ObjectEditView):
  327. permission_required = 'dcim.add_rack'
  328. model = Rack
  329. model_form = forms.RackForm
  330. template_name = 'dcim/rack_edit.html'
  331. default_return_url = 'dcim:rack_list'
  332. class RackEditView(RackCreateView):
  333. permission_required = 'dcim.change_rack'
  334. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  335. permission_required = 'dcim.delete_rack'
  336. model = Rack
  337. default_return_url = 'dcim:rack_list'
  338. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  339. permission_required = 'dcim.add_rack'
  340. model_form = forms.RackCSVForm
  341. table = tables.RackTable
  342. default_return_url = 'dcim:rack_list'
  343. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  344. permission_required = 'dcim.change_rack'
  345. queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
  346. filterset = filters.RackFilterSet
  347. table = tables.RackTable
  348. form = forms.RackBulkEditForm
  349. default_return_url = 'dcim:rack_list'
  350. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  351. permission_required = 'dcim.delete_rack'
  352. queryset = Rack.objects.prefetch_related('site', 'group', 'tenant', 'role')
  353. filterset = filters.RackFilterSet
  354. table = tables.RackTable
  355. default_return_url = 'dcim:rack_list'
  356. #
  357. # Rack reservations
  358. #
  359. class RackReservationListView(PermissionRequiredMixin, ObjectListView):
  360. permission_required = 'dcim.view_rackreservation'
  361. queryset = RackReservation.objects.prefetch_related('rack__site')
  362. filterset = filters.RackReservationFilterSet
  363. filterset_form = forms.RackReservationFilterForm
  364. table = tables.RackReservationTable
  365. action_buttons = ('export',)
  366. class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView):
  367. permission_required = 'dcim.add_rackreservation'
  368. model = RackReservation
  369. model_form = forms.RackReservationForm
  370. def alter_obj(self, obj, request, args, kwargs):
  371. if not obj.pk:
  372. obj.rack = get_object_or_404(Rack, pk=kwargs['rack'])
  373. obj.user = request.user
  374. return obj
  375. def get_return_url(self, request, obj):
  376. return obj.rack.get_absolute_url()
  377. class RackReservationEditView(RackReservationCreateView):
  378. permission_required = 'dcim.change_rackreservation'
  379. class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  380. permission_required = 'dcim.delete_rackreservation'
  381. model = RackReservation
  382. def get_return_url(self, request, obj):
  383. return obj.rack.get_absolute_url()
  384. class RackReservationImportView(PermissionRequiredMixin, BulkImportView):
  385. permission_required = 'dcim.add_rackreservation'
  386. model_form = forms.RackReservationCSVForm
  387. table = tables.RackReservationTable
  388. default_return_url = 'dcim:rackreservation_list'
  389. def _save_obj(self, obj_form, request):
  390. """
  391. Assign the currently authenticated user to the RackReservation.
  392. """
  393. instance = obj_form.save(commit=False)
  394. instance.user = request.user
  395. instance.save()
  396. return instance
  397. class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
  398. permission_required = 'dcim.change_rackreservation'
  399. queryset = RackReservation.objects.prefetch_related('rack', 'user')
  400. filterset = filters.RackReservationFilterSet
  401. table = tables.RackReservationTable
  402. form = forms.RackReservationBulkEditForm
  403. default_return_url = 'dcim:rackreservation_list'
  404. class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  405. permission_required = 'dcim.delete_rackreservation'
  406. queryset = RackReservation.objects.prefetch_related('rack', 'user')
  407. filterset = filters.RackReservationFilterSet
  408. table = tables.RackReservationTable
  409. default_return_url = 'dcim:rackreservation_list'
  410. #
  411. # Manufacturers
  412. #
  413. class ManufacturerListView(PermissionRequiredMixin, ObjectListView):
  414. permission_required = 'dcim.view_manufacturer'
  415. queryset = Manufacturer.objects.annotate(
  416. devicetype_count=Count('device_types', distinct=True),
  417. inventoryitem_count=Count('inventory_items', distinct=True),
  418. platform_count=Count('platforms', distinct=True),
  419. )
  420. table = tables.ManufacturerTable
  421. class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView):
  422. permission_required = 'dcim.add_manufacturer'
  423. model = Manufacturer
  424. model_form = forms.ManufacturerForm
  425. default_return_url = 'dcim:manufacturer_list'
  426. class ManufacturerEditView(ManufacturerCreateView):
  427. permission_required = 'dcim.change_manufacturer'
  428. class ManufacturerBulkImportView(PermissionRequiredMixin, BulkImportView):
  429. permission_required = 'dcim.add_manufacturer'
  430. model_form = forms.ManufacturerCSVForm
  431. table = tables.ManufacturerTable
  432. default_return_url = 'dcim:manufacturer_list'
  433. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  434. permission_required = 'dcim.delete_manufacturer'
  435. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  436. table = tables.ManufacturerTable
  437. default_return_url = 'dcim:manufacturer_list'
  438. #
  439. # Device types
  440. #
  441. class DeviceTypeListView(PermissionRequiredMixin, ObjectListView):
  442. permission_required = 'dcim.view_devicetype'
  443. queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
  444. filterset = filters.DeviceTypeFilterSet
  445. filterset_form = forms.DeviceTypeFilterForm
  446. table = tables.DeviceTypeTable
  447. class DeviceTypeView(PermissionRequiredMixin, View):
  448. permission_required = 'dcim.view_devicetype'
  449. def get(self, request, pk):
  450. devicetype = get_object_or_404(DeviceType, pk=pk)
  451. # Component tables
  452. consoleport_table = tables.ConsolePortTemplateTable(
  453. ConsolePortTemplate.objects.filter(device_type=devicetype),
  454. orderable=False
  455. )
  456. consoleserverport_table = tables.ConsoleServerPortTemplateTable(
  457. ConsoleServerPortTemplate.objects.filter(device_type=devicetype),
  458. orderable=False
  459. )
  460. powerport_table = tables.PowerPortTemplateTable(
  461. PowerPortTemplate.objects.filter(device_type=devicetype),
  462. orderable=False
  463. )
  464. poweroutlet_table = tables.PowerOutletTemplateTable(
  465. PowerOutletTemplate.objects.filter(device_type=devicetype),
  466. orderable=False
  467. )
  468. interface_table = tables.InterfaceTemplateTable(
  469. list(InterfaceTemplate.objects.filter(device_type=devicetype)),
  470. orderable=False
  471. )
  472. front_port_table = tables.FrontPortTemplateTable(
  473. FrontPortTemplate.objects.filter(device_type=devicetype),
  474. orderable=False
  475. )
  476. rear_port_table = tables.RearPortTemplateTable(
  477. RearPortTemplate.objects.filter(device_type=devicetype),
  478. orderable=False
  479. )
  480. devicebay_table = tables.DeviceBayTemplateTable(
  481. DeviceBayTemplate.objects.filter(device_type=devicetype),
  482. orderable=False
  483. )
  484. if request.user.has_perm('dcim.change_devicetype'):
  485. consoleport_table.columns.show('pk')
  486. consoleserverport_table.columns.show('pk')
  487. powerport_table.columns.show('pk')
  488. poweroutlet_table.columns.show('pk')
  489. interface_table.columns.show('pk')
  490. front_port_table.columns.show('pk')
  491. rear_port_table.columns.show('pk')
  492. devicebay_table.columns.show('pk')
  493. return render(request, 'dcim/devicetype.html', {
  494. 'devicetype': devicetype,
  495. 'consoleport_table': consoleport_table,
  496. 'consoleserverport_table': consoleserverport_table,
  497. 'powerport_table': powerport_table,
  498. 'poweroutlet_table': poweroutlet_table,
  499. 'interface_table': interface_table,
  500. 'front_port_table': front_port_table,
  501. 'rear_port_table': rear_port_table,
  502. 'devicebay_table': devicebay_table,
  503. })
  504. class DeviceTypeCreateView(PermissionRequiredMixin, ObjectEditView):
  505. permission_required = 'dcim.add_devicetype'
  506. model = DeviceType
  507. model_form = forms.DeviceTypeForm
  508. template_name = 'dcim/devicetype_edit.html'
  509. default_return_url = 'dcim:devicetype_list'
  510. class DeviceTypeEditView(DeviceTypeCreateView):
  511. permission_required = 'dcim.change_devicetype'
  512. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  513. permission_required = 'dcim.delete_devicetype'
  514. model = DeviceType
  515. default_return_url = 'dcim:devicetype_list'
  516. class DeviceTypeImportView(PermissionRequiredMixin, ObjectImportView):
  517. permission_required = [
  518. 'dcim.add_devicetype',
  519. 'dcim.add_consoleporttemplate',
  520. 'dcim.add_consoleserverporttemplate',
  521. 'dcim.add_powerporttemplate',
  522. 'dcim.add_poweroutlettemplate',
  523. 'dcim.add_interfacetemplate',
  524. 'dcim.add_frontporttemplate',
  525. 'dcim.add_rearporttemplate',
  526. 'dcim.add_devicebaytemplate',
  527. ]
  528. model = DeviceType
  529. model_form = forms.DeviceTypeImportForm
  530. related_object_forms = OrderedDict((
  531. ('console-ports', forms.ConsolePortTemplateImportForm),
  532. ('console-server-ports', forms.ConsoleServerPortTemplateImportForm),
  533. ('power-ports', forms.PowerPortTemplateImportForm),
  534. ('power-outlets', forms.PowerOutletTemplateImportForm),
  535. ('interfaces', forms.InterfaceTemplateImportForm),
  536. ('rear-ports', forms.RearPortTemplateImportForm),
  537. ('front-ports', forms.FrontPortTemplateImportForm),
  538. ('device-bays', forms.DeviceBayTemplateImportForm),
  539. ))
  540. default_return_url = 'dcim:devicetype_import'
  541. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  542. permission_required = 'dcim.change_devicetype'
  543. queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
  544. filterset = filters.DeviceTypeFilterSet
  545. table = tables.DeviceTypeTable
  546. form = forms.DeviceTypeBulkEditForm
  547. default_return_url = 'dcim:devicetype_list'
  548. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  549. permission_required = 'dcim.delete_devicetype'
  550. queryset = DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances'))
  551. filterset = filters.DeviceTypeFilterSet
  552. table = tables.DeviceTypeTable
  553. default_return_url = 'dcim:devicetype_list'
  554. #
  555. # Console port templates
  556. #
  557. class ConsolePortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  558. permission_required = 'dcim.add_consoleporttemplate'
  559. model = ConsolePortTemplate
  560. form = forms.ConsolePortTemplateCreateForm
  561. model_form = forms.ConsolePortTemplateForm
  562. template_name = 'dcim/device_component_add.html'
  563. class ConsolePortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  564. permission_required = 'dcim.change_consoleporttemplate'
  565. model = ConsolePortTemplate
  566. model_form = forms.ConsolePortTemplateForm
  567. class ConsolePortTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  568. permission_required = 'dcim.delete_consoleporttemplate'
  569. model = ConsolePortTemplate
  570. class ConsolePortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  571. permission_required = 'dcim.change_consoleporttemplate'
  572. queryset = ConsolePortTemplate.objects.all()
  573. table = tables.ConsolePortTemplateTable
  574. form = forms.ConsolePortTemplateBulkEditForm
  575. class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  576. permission_required = 'dcim.delete_consoleporttemplate'
  577. queryset = ConsolePortTemplate.objects.all()
  578. table = tables.ConsolePortTemplateTable
  579. #
  580. # Console server port templates
  581. #
  582. class ConsoleServerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  583. permission_required = 'dcim.add_consoleserverporttemplate'
  584. model = ConsoleServerPortTemplate
  585. form = forms.ConsoleServerPortTemplateCreateForm
  586. model_form = forms.ConsoleServerPortTemplateForm
  587. template_name = 'dcim/device_component_add.html'
  588. class ConsoleServerPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  589. permission_required = 'dcim.change_consoleserverporttemplate'
  590. model = ConsoleServerPortTemplate
  591. model_form = forms.ConsoleServerPortTemplateForm
  592. class ConsoleServerPortTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  593. permission_required = 'dcim.delete_consoleserverporttemplate'
  594. model = ConsoleServerPortTemplate
  595. class ConsoleServerPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  596. permission_required = 'dcim.change_consoleserverporttemplate'
  597. queryset = ConsoleServerPortTemplate.objects.all()
  598. table = tables.ConsoleServerPortTemplateTable
  599. form = forms.ConsoleServerPortTemplateBulkEditForm
  600. class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  601. permission_required = 'dcim.delete_consoleserverporttemplate'
  602. queryset = ConsoleServerPortTemplate.objects.all()
  603. table = tables.ConsoleServerPortTemplateTable
  604. #
  605. # Power port templates
  606. #
  607. class PowerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  608. permission_required = 'dcim.add_powerporttemplate'
  609. model = PowerPortTemplate
  610. form = forms.PowerPortTemplateCreateForm
  611. model_form = forms.PowerPortTemplateForm
  612. template_name = 'dcim/device_component_add.html'
  613. class PowerPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  614. permission_required = 'dcim.change_powerporttemplate'
  615. model = PowerPortTemplate
  616. model_form = forms.PowerPortTemplateForm
  617. class PowerPortTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  618. permission_required = 'dcim.delete_powerporttemplate'
  619. model = PowerPortTemplate
  620. class PowerPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  621. permission_required = 'dcim.change_powerporttemplate'
  622. queryset = PowerPortTemplate.objects.all()
  623. table = tables.PowerPortTemplateTable
  624. form = forms.PowerPortTemplateBulkEditForm
  625. class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  626. permission_required = 'dcim.delete_powerporttemplate'
  627. queryset = PowerPortTemplate.objects.all()
  628. table = tables.PowerPortTemplateTable
  629. #
  630. # Power outlet templates
  631. #
  632. class PowerOutletTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  633. permission_required = 'dcim.add_poweroutlettemplate'
  634. model = PowerOutletTemplate
  635. form = forms.PowerOutletTemplateCreateForm
  636. model_form = forms.PowerOutletTemplateForm
  637. template_name = 'dcim/device_component_add.html'
  638. class PowerOutletTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  639. permission_required = 'dcim.change_poweroutlettemplate'
  640. model = PowerOutletTemplate
  641. model_form = forms.PowerOutletTemplateForm
  642. class PowerOutletTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  643. permission_required = 'dcim.delete_poweroutlettemplate'
  644. model = PowerOutletTemplate
  645. class PowerOutletTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  646. permission_required = 'dcim.change_poweroutlettemplate'
  647. queryset = PowerOutletTemplate.objects.all()
  648. table = tables.PowerOutletTemplateTable
  649. form = forms.PowerOutletTemplateBulkEditForm
  650. class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  651. permission_required = 'dcim.delete_poweroutlettemplate'
  652. queryset = PowerOutletTemplate.objects.all()
  653. table = tables.PowerOutletTemplateTable
  654. #
  655. # Interface templates
  656. #
  657. class InterfaceTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  658. permission_required = 'dcim.add_interfacetemplate'
  659. model = InterfaceTemplate
  660. form = forms.InterfaceTemplateCreateForm
  661. model_form = forms.InterfaceTemplateForm
  662. template_name = 'dcim/device_component_add.html'
  663. class InterfaceTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  664. permission_required = 'dcim.change_interfacetemplate'
  665. model = InterfaceTemplate
  666. model_form = forms.InterfaceTemplateForm
  667. class InterfaceTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  668. permission_required = 'dcim.delete_interfacetemplate'
  669. model = InterfaceTemplate
  670. class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  671. permission_required = 'dcim.change_interfacetemplate'
  672. queryset = InterfaceTemplate.objects.all()
  673. table = tables.InterfaceTemplateTable
  674. form = forms.InterfaceTemplateBulkEditForm
  675. class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  676. permission_required = 'dcim.delete_interfacetemplate'
  677. queryset = InterfaceTemplate.objects.all()
  678. table = tables.InterfaceTemplateTable
  679. #
  680. # Front port templates
  681. #
  682. class FrontPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  683. permission_required = 'dcim.add_frontporttemplate'
  684. model = FrontPortTemplate
  685. form = forms.FrontPortTemplateCreateForm
  686. model_form = forms.FrontPortTemplateForm
  687. template_name = 'dcim/device_component_add.html'
  688. class FrontPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  689. permission_required = 'dcim.change_frontporttemplate'
  690. model = FrontPortTemplate
  691. model_form = forms.FrontPortTemplateForm
  692. class FrontPortTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  693. permission_required = 'dcim.delete_frontporttemplate'
  694. model = FrontPortTemplate
  695. class FrontPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  696. permission_required = 'dcim.change_frontporttemplate'
  697. queryset = FrontPortTemplate.objects.all()
  698. table = tables.FrontPortTemplateTable
  699. form = forms.FrontPortTemplateBulkEditForm
  700. class FrontPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  701. permission_required = 'dcim.delete_frontporttemplate'
  702. queryset = FrontPortTemplate.objects.all()
  703. table = tables.FrontPortTemplateTable
  704. #
  705. # Rear port templates
  706. #
  707. class RearPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  708. permission_required = 'dcim.add_rearporttemplate'
  709. model = RearPortTemplate
  710. form = forms.RearPortTemplateCreateForm
  711. model_form = forms.RearPortTemplateForm
  712. template_name = 'dcim/device_component_add.html'
  713. class RearPortTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  714. permission_required = 'dcim.change_rearporttemplate'
  715. model = RearPortTemplate
  716. model_form = forms.RearPortTemplateForm
  717. class RearPortTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  718. permission_required = 'dcim.delete_rearporttemplate'
  719. model = RearPortTemplate
  720. class RearPortTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  721. permission_required = 'dcim.change_rearporttemplate'
  722. queryset = RearPortTemplate.objects.all()
  723. table = tables.RearPortTemplateTable
  724. form = forms.RearPortTemplateBulkEditForm
  725. class RearPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  726. permission_required = 'dcim.delete_rearporttemplate'
  727. queryset = RearPortTemplate.objects.all()
  728. table = tables.RearPortTemplateTable
  729. #
  730. # Device bay templates
  731. #
  732. class DeviceBayTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  733. permission_required = 'dcim.add_devicebaytemplate'
  734. model = DeviceBayTemplate
  735. form = forms.DeviceBayTemplateCreateForm
  736. model_form = forms.DeviceBayTemplateForm
  737. template_name = 'dcim/device_component_add.html'
  738. class DeviceBayTemplateEditView(PermissionRequiredMixin, ObjectEditView):
  739. permission_required = 'dcim.change_devicebaytemplate'
  740. model = DeviceBayTemplate
  741. model_form = forms.DeviceBayTemplateForm
  742. class DeviceBayTemplateDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  743. permission_required = 'dcim.delete_devicebaytemplate'
  744. model = DeviceBayTemplate
  745. # class DeviceBayTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  746. # permission_required = 'dcim.change_devicebaytemplate'
  747. # queryset = DeviceBayTemplate.objects.all()
  748. # table = tables.DeviceBayTemplateTable
  749. # form = forms.DeviceBayTemplateBulkEditForm
  750. class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  751. permission_required = 'dcim.delete_devicebaytemplate'
  752. queryset = DeviceBayTemplate.objects.all()
  753. table = tables.DeviceBayTemplateTable
  754. #
  755. # Device roles
  756. #
  757. class DeviceRoleListView(PermissionRequiredMixin, ObjectListView):
  758. permission_required = 'dcim.view_devicerole'
  759. queryset = DeviceRole.objects.all()
  760. table = tables.DeviceRoleTable
  761. class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  762. permission_required = 'dcim.add_devicerole'
  763. model = DeviceRole
  764. model_form = forms.DeviceRoleForm
  765. default_return_url = 'dcim:devicerole_list'
  766. class DeviceRoleEditView(DeviceRoleCreateView):
  767. permission_required = 'dcim.change_devicerole'
  768. class DeviceRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  769. permission_required = 'dcim.add_devicerole'
  770. model_form = forms.DeviceRoleCSVForm
  771. table = tables.DeviceRoleTable
  772. default_return_url = 'dcim:devicerole_list'
  773. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  774. permission_required = 'dcim.delete_devicerole'
  775. queryset = DeviceRole.objects.all()
  776. table = tables.DeviceRoleTable
  777. default_return_url = 'dcim:devicerole_list'
  778. #
  779. # Platforms
  780. #
  781. class PlatformListView(PermissionRequiredMixin, ObjectListView):
  782. permission_required = 'dcim.view_platform'
  783. queryset = Platform.objects.all()
  784. table = tables.PlatformTable
  785. class PlatformCreateView(PermissionRequiredMixin, ObjectEditView):
  786. permission_required = 'dcim.add_platform'
  787. model = Platform
  788. model_form = forms.PlatformForm
  789. default_return_url = 'dcim:platform_list'
  790. class PlatformEditView(PlatformCreateView):
  791. permission_required = 'dcim.change_platform'
  792. class PlatformBulkImportView(PermissionRequiredMixin, BulkImportView):
  793. permission_required = 'dcim.add_platform'
  794. model_form = forms.PlatformCSVForm
  795. table = tables.PlatformTable
  796. default_return_url = 'dcim:platform_list'
  797. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  798. permission_required = 'dcim.delete_platform'
  799. queryset = Platform.objects.all()
  800. table = tables.PlatformTable
  801. default_return_url = 'dcim:platform_list'
  802. #
  803. # Devices
  804. #
  805. class DeviceListView(PermissionRequiredMixin, ObjectListView):
  806. permission_required = 'dcim.view_device'
  807. queryset = Device.objects.prefetch_related(
  808. 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6'
  809. )
  810. filterset = filters.DeviceFilterSet
  811. filterset_form = forms.DeviceFilterForm
  812. table = tables.DeviceDetailTable
  813. template_name = 'dcim/device_list.html'
  814. class DeviceView(PermissionRequiredMixin, View):
  815. permission_required = 'dcim.view_device'
  816. def get(self, request, pk):
  817. device = get_object_or_404(Device.objects.prefetch_related(
  818. 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
  819. ), pk=pk)
  820. # VirtualChassis members
  821. if device.virtual_chassis is not None:
  822. vc_members = Device.objects.filter(
  823. virtual_chassis=device.virtual_chassis
  824. ).order_by('vc_position')
  825. else:
  826. vc_members = []
  827. # Console ports
  828. console_ports = device.consoleports.prefetch_related('connected_endpoint__device', 'cable')
  829. # Console server ports
  830. consoleserverports = device.consoleserverports.prefetch_related('connected_endpoint__device', 'cable')
  831. # Power ports
  832. power_ports = device.powerports.prefetch_related('_connected_poweroutlet__device', 'cable')
  833. # Power outlets
  834. poweroutlets = device.poweroutlets.prefetch_related('connected_endpoint__device', 'cable', 'power_port')
  835. # Interfaces
  836. interfaces = device.vc_interfaces.prefetch_related(
  837. 'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable',
  838. 'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags'
  839. )
  840. # Front ports
  841. front_ports = device.frontports.prefetch_related('rear_port', 'cable')
  842. # Rear ports
  843. rear_ports = device.rearports.prefetch_related('cable')
  844. # Device bays
  845. device_bays = device.device_bays.prefetch_related('installed_device__device_type__manufacturer')
  846. # Services
  847. services = device.services.all()
  848. # Secrets
  849. secrets = device.secrets.all()
  850. # Find up to ten devices in the same site with the same functional role for quick reference.
  851. related_devices = Device.objects.filter(
  852. site=device.site, device_role=device.device_role
  853. ).exclude(
  854. pk=device.pk
  855. ).prefetch_related(
  856. 'rack', 'device_type__manufacturer'
  857. )[:10]
  858. return render(request, 'dcim/device.html', {
  859. 'device': device,
  860. 'console_ports': console_ports,
  861. 'consoleserverports': consoleserverports,
  862. 'power_ports': power_ports,
  863. 'poweroutlets': poweroutlets,
  864. 'interfaces': interfaces,
  865. 'device_bays': device_bays,
  866. 'front_ports': front_ports,
  867. 'rear_ports': rear_ports,
  868. 'services': services,
  869. 'secrets': secrets,
  870. 'vc_members': vc_members,
  871. 'related_devices': related_devices,
  872. 'show_graphs': Graph.objects.filter(type__model='device').exists(),
  873. 'show_interface_graphs': Graph.objects.filter(type__model='interface').exists(),
  874. })
  875. class DeviceInventoryView(PermissionRequiredMixin, View):
  876. permission_required = 'dcim.view_device'
  877. def get(self, request, pk):
  878. device = get_object_or_404(Device, pk=pk)
  879. inventory_items = InventoryItem.objects.filter(
  880. device=device, parent=None
  881. ).prefetch_related(
  882. 'manufacturer', 'child_items'
  883. )
  884. return render(request, 'dcim/device_inventory.html', {
  885. 'device': device,
  886. 'inventory_items': inventory_items,
  887. 'active_tab': 'inventory',
  888. })
  889. class DeviceStatusView(PermissionRequiredMixin, View):
  890. permission_required = ('dcim.view_device', 'dcim.napalm_read')
  891. def get(self, request, pk):
  892. device = get_object_or_404(Device, pk=pk)
  893. return render(request, 'dcim/device_status.html', {
  894. 'device': device,
  895. 'active_tab': 'status',
  896. })
  897. class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
  898. permission_required = ('dcim.view_device', 'dcim.napalm_read')
  899. def get(self, request, pk):
  900. device = get_object_or_404(Device, pk=pk)
  901. interfaces = device.vc_interfaces.exclude(type__in=NONCONNECTABLE_IFACE_TYPES).prefetch_related(
  902. '_connected_interface__device'
  903. )
  904. return render(request, 'dcim/device_lldp_neighbors.html', {
  905. 'device': device,
  906. 'interfaces': interfaces,
  907. 'active_tab': 'lldp-neighbors',
  908. })
  909. class DeviceConfigView(PermissionRequiredMixin, View):
  910. permission_required = ('dcim.view_device', 'dcim.napalm_read')
  911. def get(self, request, pk):
  912. device = get_object_or_404(Device, pk=pk)
  913. return render(request, 'dcim/device_config.html', {
  914. 'device': device,
  915. 'active_tab': 'config',
  916. })
  917. class DeviceConfigContextView(PermissionRequiredMixin, ObjectConfigContextView):
  918. permission_required = 'dcim.view_device'
  919. object_class = Device
  920. base_template = 'dcim/device.html'
  921. class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
  922. permission_required = 'dcim.add_device'
  923. model = Device
  924. model_form = forms.DeviceForm
  925. template_name = 'dcim/device_edit.html'
  926. default_return_url = 'dcim:device_list'
  927. class DeviceEditView(DeviceCreateView):
  928. permission_required = 'dcim.change_device'
  929. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  930. permission_required = 'dcim.delete_device'
  931. model = Device
  932. default_return_url = 'dcim:device_list'
  933. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  934. permission_required = 'dcim.add_device'
  935. model_form = forms.DeviceCSVForm
  936. table = tables.DeviceImportTable
  937. template_name = 'dcim/device_import.html'
  938. default_return_url = 'dcim:device_list'
  939. class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  940. permission_required = 'dcim.add_device'
  941. model_form = forms.ChildDeviceCSVForm
  942. table = tables.DeviceImportTable
  943. template_name = 'dcim/device_import_child.html'
  944. default_return_url = 'dcim:device_list'
  945. def _save_obj(self, obj_form, request):
  946. obj = obj_form.save()
  947. # Save the reverse relation to the parent device bay
  948. device_bay = obj.parent_bay
  949. device_bay.installed_device = obj
  950. device_bay.save()
  951. return obj
  952. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  953. permission_required = 'dcim.change_device'
  954. queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  955. filterset = filters.DeviceFilterSet
  956. table = tables.DeviceTable
  957. form = forms.DeviceBulkEditForm
  958. default_return_url = 'dcim:device_list'
  959. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  960. permission_required = 'dcim.delete_device'
  961. queryset = Device.objects.prefetch_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  962. filterset = filters.DeviceFilterSet
  963. table = tables.DeviceTable
  964. default_return_url = 'dcim:device_list'
  965. #
  966. # Console ports
  967. #
  968. class ConsolePortListView(PermissionRequiredMixin, ObjectListView):
  969. permission_required = 'dcim.view_consoleport'
  970. queryset = ConsolePort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  971. filterset = filters.ConsolePortFilterSet
  972. filterset_form = forms.ConsolePortFilterForm
  973. table = tables.ConsolePortDetailTable
  974. action_buttons = ('import', 'export')
  975. class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView):
  976. permission_required = 'dcim.add_consoleport'
  977. model = ConsolePort
  978. form = forms.ConsolePortCreateForm
  979. model_form = forms.ConsolePortForm
  980. template_name = 'dcim/device_component_add.html'
  981. class ConsolePortEditView(PermissionRequiredMixin, ObjectEditView):
  982. permission_required = 'dcim.change_consoleport'
  983. model = ConsolePort
  984. model_form = forms.ConsolePortForm
  985. class ConsolePortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  986. permission_required = 'dcim.delete_consoleport'
  987. model = ConsolePort
  988. class ConsolePortBulkImportView(PermissionRequiredMixin, BulkImportView):
  989. permission_required = 'dcim.add_consoleport'
  990. model_form = forms.ConsolePortCSVForm
  991. table = tables.ConsolePortImportTable
  992. default_return_url = 'dcim:consoleport_list'
  993. class ConsolePortBulkEditView(PermissionRequiredMixin, BulkEditView):
  994. permission_required = 'dcim.change_consoleport'
  995. queryset = ConsolePort.objects.all()
  996. table = tables.ConsolePortTable
  997. form = forms.ConsolePortBulkEditForm
  998. class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  999. permission_required = 'dcim.delete_consoleport'
  1000. queryset = ConsolePort.objects.all()
  1001. table = tables.ConsolePortTable
  1002. default_return_url = 'dcim:consoleport_list'
  1003. #
  1004. # Console server ports
  1005. #
  1006. class ConsoleServerPortListView(PermissionRequiredMixin, ObjectListView):
  1007. permission_required = 'dcim.view_consoleserverport'
  1008. queryset = ConsoleServerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  1009. filterset = filters.ConsoleServerPortFilterSet
  1010. filterset_form = forms.ConsoleServerPortFilterForm
  1011. table = tables.ConsoleServerPortDetailTable
  1012. action_buttons = ('import', 'export')
  1013. class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  1014. permission_required = 'dcim.add_consoleserverport'
  1015. model = ConsoleServerPort
  1016. form = forms.ConsoleServerPortCreateForm
  1017. model_form = forms.ConsoleServerPortForm
  1018. template_name = 'dcim/device_component_add.html'
  1019. class ConsoleServerPortEditView(PermissionRequiredMixin, ObjectEditView):
  1020. permission_required = 'dcim.change_consoleserverport'
  1021. model = ConsoleServerPort
  1022. model_form = forms.ConsoleServerPortForm
  1023. class ConsoleServerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1024. permission_required = 'dcim.delete_consoleserverport'
  1025. model = ConsoleServerPort
  1026. class ConsoleServerPortBulkImportView(PermissionRequiredMixin, BulkImportView):
  1027. permission_required = 'dcim.add_consoleserverport'
  1028. model_form = forms.ConsoleServerPortCSVForm
  1029. table = tables.ConsoleServerPortImportTable
  1030. default_return_url = 'dcim:consoleserverport_list'
  1031. class ConsoleServerPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  1032. permission_required = 'dcim.change_consoleserverport'
  1033. queryset = ConsoleServerPort.objects.all()
  1034. table = tables.ConsoleServerPortTable
  1035. form = forms.ConsoleServerPortBulkEditForm
  1036. class ConsoleServerPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1037. permission_required = 'dcim.change_consoleserverport'
  1038. queryset = ConsoleServerPort.objects.all()
  1039. form = forms.ConsoleServerPortBulkRenameForm
  1040. class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1041. permission_required = 'dcim.change_consoleserverport'
  1042. model = ConsoleServerPort
  1043. form = forms.ConsoleServerPortBulkDisconnectForm
  1044. class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1045. permission_required = 'dcim.delete_consoleserverport'
  1046. queryset = ConsoleServerPort.objects.all()
  1047. table = tables.ConsoleServerPortTable
  1048. default_return_url = 'dcim:consoleserverport_list'
  1049. #
  1050. # Power ports
  1051. #
  1052. class PowerPortListView(PermissionRequiredMixin, ObjectListView):
  1053. permission_required = 'dcim.view_powerport'
  1054. queryset = PowerPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  1055. filterset = filters.PowerPortFilterSet
  1056. filterset_form = forms.PowerPortFilterForm
  1057. table = tables.PowerPortDetailTable
  1058. action_buttons = ('import', 'export')
  1059. class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  1060. permission_required = 'dcim.add_powerport'
  1061. model = PowerPort
  1062. form = forms.PowerPortCreateForm
  1063. model_form = forms.PowerPortForm
  1064. template_name = 'dcim/device_component_add.html'
  1065. class PowerPortEditView(PermissionRequiredMixin, ObjectEditView):
  1066. permission_required = 'dcim.change_powerport'
  1067. model = PowerPort
  1068. model_form = forms.PowerPortForm
  1069. class PowerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1070. permission_required = 'dcim.delete_powerport'
  1071. model = PowerPort
  1072. class PowerPortBulkImportView(PermissionRequiredMixin, BulkImportView):
  1073. permission_required = 'dcim.add_powerport'
  1074. model_form = forms.PowerPortCSVForm
  1075. table = tables.PowerPortImportTable
  1076. default_return_url = 'dcim:powerport_list'
  1077. class PowerPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  1078. permission_required = 'dcim.change_powerport'
  1079. queryset = PowerPort.objects.all()
  1080. table = tables.PowerPortTable
  1081. form = forms.PowerPortBulkEditForm
  1082. class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1083. permission_required = 'dcim.delete_powerport'
  1084. queryset = PowerPort.objects.all()
  1085. table = tables.PowerPortTable
  1086. default_return_url = 'dcim:powerport_list'
  1087. #
  1088. # Power outlets
  1089. #
  1090. class PowerOutletListView(PermissionRequiredMixin, ObjectListView):
  1091. permission_required = 'dcim.view_poweroutlet'
  1092. queryset = PowerOutlet.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  1093. filterset = filters.PowerOutletFilterSet
  1094. filterset_form = forms.PowerOutletFilterForm
  1095. table = tables.PowerOutletDetailTable
  1096. action_buttons = ('import', 'export')
  1097. class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView):
  1098. permission_required = 'dcim.add_poweroutlet'
  1099. model = PowerOutlet
  1100. form = forms.PowerOutletCreateForm
  1101. model_form = forms.PowerOutletForm
  1102. template_name = 'dcim/device_component_add.html'
  1103. class PowerOutletEditView(PermissionRequiredMixin, ObjectEditView):
  1104. permission_required = 'dcim.change_poweroutlet'
  1105. model = PowerOutlet
  1106. model_form = forms.PowerOutletForm
  1107. class PowerOutletDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1108. permission_required = 'dcim.delete_poweroutlet'
  1109. model = PowerOutlet
  1110. class PowerOutletBulkImportView(PermissionRequiredMixin, BulkImportView):
  1111. permission_required = 'dcim.add_poweroutlet'
  1112. model_form = forms.PowerOutletCSVForm
  1113. table = tables.PowerOutletImportTable
  1114. default_return_url = 'dcim:poweroutlet_list'
  1115. class PowerOutletBulkEditView(PermissionRequiredMixin, BulkEditView):
  1116. permission_required = 'dcim.change_poweroutlet'
  1117. queryset = PowerOutlet.objects.all()
  1118. table = tables.PowerOutletTable
  1119. form = forms.PowerOutletBulkEditForm
  1120. class PowerOutletBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1121. permission_required = 'dcim.change_poweroutlet'
  1122. queryset = PowerOutlet.objects.all()
  1123. form = forms.PowerOutletBulkRenameForm
  1124. class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1125. permission_required = 'dcim.change_poweroutlet'
  1126. model = PowerOutlet
  1127. form = forms.PowerOutletBulkDisconnectForm
  1128. class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1129. permission_required = 'dcim.delete_poweroutlet'
  1130. queryset = PowerOutlet.objects.all()
  1131. table = tables.PowerOutletTable
  1132. default_return_url = 'dcim:poweroutlet_list'
  1133. #
  1134. # Interfaces
  1135. #
  1136. class InterfaceListView(PermissionRequiredMixin, ObjectListView):
  1137. permission_required = 'dcim.view_interface'
  1138. queryset = Interface.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  1139. filterset = filters.InterfaceFilterSet
  1140. filterset_form = forms.InterfaceFilterForm
  1141. table = tables.InterfaceDetailTable
  1142. action_buttons = ('import', 'export')
  1143. class InterfaceView(PermissionRequiredMixin, View):
  1144. permission_required = 'dcim.view_interface'
  1145. def get(self, request, pk):
  1146. interface = get_object_or_404(Interface, pk=pk)
  1147. # Get assigned IP addresses
  1148. ipaddress_table = InterfaceIPAddressTable(
  1149. data=interface.ip_addresses.prefetch_related('vrf', 'tenant'),
  1150. orderable=False
  1151. )
  1152. # Get assigned VLANs and annotate whether each is tagged or untagged
  1153. vlans = []
  1154. if interface.untagged_vlan is not None:
  1155. vlans.append(interface.untagged_vlan)
  1156. vlans[0].tagged = False
  1157. for vlan in interface.tagged_vlans.prefetch_related('site', 'group', 'tenant', 'role'):
  1158. vlan.tagged = True
  1159. vlans.append(vlan)
  1160. vlan_table = InterfaceVLANTable(
  1161. interface=interface,
  1162. data=vlans,
  1163. orderable=False
  1164. )
  1165. return render(request, 'dcim/interface.html', {
  1166. 'interface': interface,
  1167. 'connected_interface': interface._connected_interface,
  1168. 'connected_circuittermination': interface._connected_circuittermination,
  1169. 'ipaddress_table': ipaddress_table,
  1170. 'vlan_table': vlan_table,
  1171. })
  1172. class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
  1173. permission_required = 'dcim.add_interface'
  1174. model = Interface
  1175. form = forms.InterfaceCreateForm
  1176. model_form = forms.InterfaceForm
  1177. template_name = 'dcim/device_component_add.html'
  1178. class InterfaceEditView(PermissionRequiredMixin, ObjectEditView):
  1179. permission_required = 'dcim.change_interface'
  1180. model = Interface
  1181. model_form = forms.InterfaceForm
  1182. template_name = 'dcim/interface_edit.html'
  1183. class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1184. permission_required = 'dcim.delete_interface'
  1185. model = Interface
  1186. class InterfaceBulkImportView(PermissionRequiredMixin, BulkImportView):
  1187. permission_required = 'dcim.add_interface'
  1188. model_form = forms.InterfaceCSVForm
  1189. table = tables.InterfaceImportTable
  1190. default_return_url = 'dcim:interface_list'
  1191. class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
  1192. permission_required = 'dcim.change_interface'
  1193. queryset = Interface.objects.all()
  1194. table = tables.InterfaceTable
  1195. form = forms.InterfaceBulkEditForm
  1196. class InterfaceBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1197. permission_required = 'dcim.change_interface'
  1198. queryset = Interface.objects.all()
  1199. form = forms.InterfaceBulkRenameForm
  1200. class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1201. permission_required = 'dcim.change_interface'
  1202. model = Interface
  1203. form = forms.InterfaceBulkDisconnectForm
  1204. class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1205. permission_required = 'dcim.delete_interface'
  1206. queryset = Interface.objects.all()
  1207. table = tables.InterfaceTable
  1208. default_return_url = 'dcim:interface_list'
  1209. #
  1210. # Front ports
  1211. #
  1212. class FrontPortListView(PermissionRequiredMixin, ObjectListView):
  1213. permission_required = 'dcim.view_frontport'
  1214. queryset = FrontPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  1215. filterset = filters.FrontPortFilterSet
  1216. filterset_form = forms.FrontPortFilterForm
  1217. table = tables.FrontPortDetailTable
  1218. action_buttons = ('import', 'export')
  1219. class FrontPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  1220. permission_required = 'dcim.add_frontport'
  1221. model = FrontPort
  1222. form = forms.FrontPortCreateForm
  1223. model_form = forms.FrontPortForm
  1224. template_name = 'dcim/device_component_add.html'
  1225. class FrontPortEditView(PermissionRequiredMixin, ObjectEditView):
  1226. permission_required = 'dcim.change_frontport'
  1227. model = FrontPort
  1228. model_form = forms.FrontPortForm
  1229. class FrontPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1230. permission_required = 'dcim.delete_frontport'
  1231. model = FrontPort
  1232. class FrontPortBulkImportView(PermissionRequiredMixin, BulkImportView):
  1233. permission_required = 'dcim.add_frontport'
  1234. model_form = forms.FrontPortCSVForm
  1235. table = tables.FrontPortImportTable
  1236. default_return_url = 'dcim:frontport_list'
  1237. class FrontPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  1238. permission_required = 'dcim.change_frontport'
  1239. queryset = FrontPort.objects.all()
  1240. table = tables.FrontPortTable
  1241. form = forms.FrontPortBulkEditForm
  1242. class FrontPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1243. permission_required = 'dcim.change_frontport'
  1244. queryset = FrontPort.objects.all()
  1245. form = forms.FrontPortBulkRenameForm
  1246. class FrontPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1247. permission_required = 'dcim.change_frontport'
  1248. model = FrontPort
  1249. form = forms.FrontPortBulkDisconnectForm
  1250. class FrontPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1251. permission_required = 'dcim.delete_frontport'
  1252. queryset = FrontPort.objects.all()
  1253. table = tables.FrontPortTable
  1254. default_return_url = 'dcim:frontport_list'
  1255. #
  1256. # Rear ports
  1257. #
  1258. class RearPortListView(PermissionRequiredMixin, ObjectListView):
  1259. permission_required = 'dcim.view_rearport'
  1260. queryset = RearPort.objects.prefetch_related('device', 'device__tenant', 'device__site', 'cable')
  1261. filterset = filters.RearPortFilterSet
  1262. filterset_form = forms.RearPortFilterForm
  1263. table = tables.RearPortDetailTable
  1264. action_buttons = ('import', 'export')
  1265. class RearPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  1266. permission_required = 'dcim.add_rearport'
  1267. model = RearPort
  1268. form = forms.RearPortCreateForm
  1269. model_form = forms.RearPortForm
  1270. template_name = 'dcim/device_component_add.html'
  1271. class RearPortEditView(PermissionRequiredMixin, ObjectEditView):
  1272. permission_required = 'dcim.change_rearport'
  1273. model = RearPort
  1274. model_form = forms.RearPortForm
  1275. class RearPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1276. permission_required = 'dcim.delete_rearport'
  1277. model = RearPort
  1278. class RearPortBulkImportView(PermissionRequiredMixin, BulkImportView):
  1279. permission_required = 'dcim.add_rearport'
  1280. model_form = forms.RearPortCSVForm
  1281. table = tables.RearPortImportTable
  1282. default_return_url = 'dcim:rearport_list'
  1283. class RearPortBulkEditView(PermissionRequiredMixin, BulkEditView):
  1284. permission_required = 'dcim.change_rearport'
  1285. queryset = RearPort.objects.all()
  1286. table = tables.RearPortTable
  1287. form = forms.RearPortBulkEditForm
  1288. class RearPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1289. permission_required = 'dcim.change_rearport'
  1290. queryset = RearPort.objects.all()
  1291. form = forms.RearPortBulkRenameForm
  1292. class RearPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1293. permission_required = 'dcim.change_rearport'
  1294. model = RearPort
  1295. form = forms.RearPortBulkDisconnectForm
  1296. class RearPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1297. permission_required = 'dcim.delete_rearport'
  1298. queryset = RearPort.objects.all()
  1299. table = tables.RearPortTable
  1300. default_return_url = 'dcim:rearport_list'
  1301. #
  1302. # Device bays
  1303. #
  1304. class DeviceBayListView(PermissionRequiredMixin, ObjectListView):
  1305. permission_required = 'dcim.view_devicebay'
  1306. queryset = DeviceBay.objects.prefetch_related(
  1307. 'device', 'device__site', 'installed_device', 'installed_device__site'
  1308. )
  1309. filterset = filters.DeviceBayFilterSet
  1310. filterset_form = forms.DeviceBayFilterForm
  1311. table = tables.DeviceBayDetailTable
  1312. action_buttons = ('import', 'export')
  1313. class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView):
  1314. permission_required = 'dcim.add_devicebay'
  1315. model = DeviceBay
  1316. form = forms.DeviceBayCreateForm
  1317. model_form = forms.DeviceBayForm
  1318. template_name = 'dcim/device_component_add.html'
  1319. class DeviceBayEditView(PermissionRequiredMixin, ObjectEditView):
  1320. permission_required = 'dcim.change_devicebay'
  1321. model = DeviceBay
  1322. model_form = forms.DeviceBayForm
  1323. class DeviceBayDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1324. permission_required = 'dcim.delete_devicebay'
  1325. model = DeviceBay
  1326. class DeviceBayPopulateView(PermissionRequiredMixin, View):
  1327. permission_required = 'dcim.change_devicebay'
  1328. def get(self, request, pk):
  1329. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1330. form = forms.PopulateDeviceBayForm(device_bay)
  1331. return render(request, 'dcim/devicebay_populate.html', {
  1332. 'device_bay': device_bay,
  1333. 'form': form,
  1334. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1335. })
  1336. def post(self, request, pk):
  1337. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1338. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  1339. if form.is_valid():
  1340. device_bay.installed_device = form.cleaned_data['installed_device']
  1341. device_bay.save()
  1342. messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
  1343. return redirect('dcim:device', pk=device_bay.device.pk)
  1344. return render(request, 'dcim/devicebay_populate.html', {
  1345. 'device_bay': device_bay,
  1346. 'form': form,
  1347. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1348. })
  1349. class DeviceBayDepopulateView(PermissionRequiredMixin, View):
  1350. permission_required = 'dcim.change_devicebay'
  1351. def get(self, request, pk):
  1352. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1353. form = ConfirmationForm()
  1354. return render(request, 'dcim/devicebay_depopulate.html', {
  1355. 'device_bay': device_bay,
  1356. 'form': form,
  1357. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1358. })
  1359. def post(self, request, pk):
  1360. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1361. form = ConfirmationForm(request.POST)
  1362. if form.is_valid():
  1363. removed_device = device_bay.installed_device
  1364. device_bay.installed_device = None
  1365. device_bay.save()
  1366. messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay))
  1367. return redirect('dcim:device', pk=device_bay.device.pk)
  1368. return render(request, 'dcim/devicebay_depopulate.html', {
  1369. 'device_bay': device_bay,
  1370. 'form': form,
  1371. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1372. })
  1373. class DeviceBayBulkImportView(PermissionRequiredMixin, BulkImportView):
  1374. permission_required = 'dcim.add_devicebay'
  1375. model_form = forms.DeviceBayCSVForm
  1376. table = tables.DeviceBayImportTable
  1377. default_return_url = 'dcim:devicebay_list'
  1378. class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1379. permission_required = 'dcim.change_devicebay'
  1380. queryset = DeviceBay.objects.all()
  1381. form = forms.DeviceBayBulkRenameForm
  1382. class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1383. permission_required = 'dcim.delete_devicebay'
  1384. queryset = DeviceBay.objects.all()
  1385. table = tables.DeviceBayTable
  1386. default_return_url = 'dcim:devicebay_list'
  1387. #
  1388. # Bulk Device component creation
  1389. #
  1390. class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateView):
  1391. permission_required = 'dcim.add_consoleport'
  1392. parent_model = Device
  1393. parent_field = 'device'
  1394. form = forms.DeviceBulkAddComponentForm
  1395. model = ConsolePort
  1396. model_form = forms.ConsolePortForm
  1397. filterset = filters.DeviceFilterSet
  1398. table = tables.DeviceTable
  1399. default_return_url = 'dcim:device_list'
  1400. class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1401. permission_required = 'dcim.add_consoleserverport'
  1402. parent_model = Device
  1403. parent_field = 'device'
  1404. form = forms.DeviceBulkAddComponentForm
  1405. model = ConsoleServerPort
  1406. model_form = forms.ConsoleServerPortForm
  1407. filterset = filters.DeviceFilterSet
  1408. table = tables.DeviceTable
  1409. default_return_url = 'dcim:device_list'
  1410. class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1411. permission_required = 'dcim.add_powerport'
  1412. parent_model = Device
  1413. parent_field = 'device'
  1414. form = forms.DeviceBulkAddComponentForm
  1415. model = PowerPort
  1416. model_form = forms.PowerPortForm
  1417. filterset = filters.DeviceFilterSet
  1418. table = tables.DeviceTable
  1419. default_return_url = 'dcim:device_list'
  1420. class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateView):
  1421. permission_required = 'dcim.add_poweroutlet'
  1422. parent_model = Device
  1423. parent_field = 'device'
  1424. form = forms.DeviceBulkAddComponentForm
  1425. model = PowerOutlet
  1426. model_form = forms.PowerOutletForm
  1427. filterset = filters.DeviceFilterSet
  1428. table = tables.DeviceTable
  1429. default_return_url = 'dcim:device_list'
  1430. class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView):
  1431. permission_required = 'dcim.add_interface'
  1432. parent_model = Device
  1433. parent_field = 'device'
  1434. form = forms.DeviceBulkAddInterfaceForm
  1435. model = Interface
  1436. model_form = forms.InterfaceForm
  1437. filterset = filters.DeviceFilterSet
  1438. table = tables.DeviceTable
  1439. default_return_url = 'dcim:device_list'
  1440. class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateView):
  1441. permission_required = 'dcim.add_devicebay'
  1442. parent_model = Device
  1443. parent_field = 'device'
  1444. form = forms.DeviceBulkAddComponentForm
  1445. model = DeviceBay
  1446. model_form = forms.DeviceBayForm
  1447. filterset = filters.DeviceFilterSet
  1448. table = tables.DeviceTable
  1449. default_return_url = 'dcim:device_list'
  1450. #
  1451. # Cables
  1452. #
  1453. class CableListView(PermissionRequiredMixin, ObjectListView):
  1454. permission_required = 'dcim.view_cable'
  1455. queryset = Cable.objects.prefetch_related(
  1456. 'termination_a', 'termination_b'
  1457. )
  1458. filterset = filters.CableFilterSet
  1459. filterset_form = forms.CableFilterForm
  1460. table = tables.CableTable
  1461. action_buttons = ('import', 'export')
  1462. class CableView(PermissionRequiredMixin, View):
  1463. permission_required = 'dcim.view_cable'
  1464. def get(self, request, pk):
  1465. cable = get_object_or_404(Cable, pk=pk)
  1466. return render(request, 'dcim/cable.html', {
  1467. 'cable': cable,
  1468. })
  1469. class CableTraceView(PermissionRequiredMixin, View):
  1470. """
  1471. Trace a cable path beginning from the given termination.
  1472. """
  1473. permission_required = 'dcim.view_cable'
  1474. def get(self, request, model, pk):
  1475. obj = get_object_or_404(model, pk=pk)
  1476. trace = obj.trace(follow_circuits=True)
  1477. total_length = sum([entry[1]._abs_length for entry in trace if entry[1] and entry[1]._abs_length])
  1478. return render(request, 'dcim/cable_trace.html', {
  1479. 'obj': obj,
  1480. 'trace': trace,
  1481. 'total_length': total_length,
  1482. })
  1483. class CableCreateView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1484. permission_required = 'dcim.add_cable'
  1485. template_name = 'dcim/cable_connect.html'
  1486. def dispatch(self, request, *args, **kwargs):
  1487. termination_a_type = kwargs.get('termination_a_type')
  1488. termination_a_id = kwargs.get('termination_a_id')
  1489. termination_b_type_name = kwargs.get('termination_b_type')
  1490. self.termination_b_type = ContentType.objects.get(model=termination_b_type_name.replace('-', ''))
  1491. self.obj = Cable(
  1492. termination_a=termination_a_type.objects.get(pk=termination_a_id),
  1493. termination_b_type=self.termination_b_type
  1494. )
  1495. self.form_class = {
  1496. 'console-port': forms.ConnectCableToConsolePortForm,
  1497. 'console-server-port': forms.ConnectCableToConsoleServerPortForm,
  1498. 'power-port': forms.ConnectCableToPowerPortForm,
  1499. 'power-outlet': forms.ConnectCableToPowerOutletForm,
  1500. 'interface': forms.ConnectCableToInterfaceForm,
  1501. 'front-port': forms.ConnectCableToFrontPortForm,
  1502. 'rear-port': forms.ConnectCableToRearPortForm,
  1503. 'power-feed': forms.ConnectCableToPowerFeedForm,
  1504. 'circuit-termination': forms.ConnectCableToCircuitTerminationForm,
  1505. }[termination_b_type_name]
  1506. return super().dispatch(request, *args, **kwargs)
  1507. def get(self, request, *args, **kwargs):
  1508. # Parse initial data manually to avoid setting field values as lists
  1509. initial_data = {k: request.GET[k] for k in request.GET}
  1510. # Set initial site and rack based on side A termination (if not already set)
  1511. if 'termination_b_site' not in initial_data:
  1512. initial_data['termination_b_site'] = getattr(self.obj.termination_a.parent, 'site', None)
  1513. if 'termination_b_rack' not in initial_data:
  1514. initial_data['termination_b_rack'] = getattr(self.obj.termination_a.parent, 'rack', None)
  1515. form = self.form_class(instance=self.obj, initial=initial_data)
  1516. return render(request, self.template_name, {
  1517. 'obj': self.obj,
  1518. 'obj_type': Cable._meta.verbose_name,
  1519. 'termination_b_type': self.termination_b_type.name,
  1520. 'form': form,
  1521. 'return_url': self.get_return_url(request, self.obj),
  1522. })
  1523. def post(self, request, *args, **kwargs):
  1524. form = self.form_class(request.POST, request.FILES, instance=self.obj)
  1525. if form.is_valid():
  1526. obj = form.save()
  1527. msg = 'Created cable <a href="{}">{}</a>'.format(
  1528. obj.get_absolute_url(),
  1529. escape(obj)
  1530. )
  1531. messages.success(request, mark_safe(msg))
  1532. if '_addanother' in request.POST:
  1533. return redirect(request.get_full_path())
  1534. return_url = form.cleaned_data.get('return_url')
  1535. if return_url is not None and is_safe_url(url=return_url, allowed_hosts=request.get_host()):
  1536. return redirect(return_url)
  1537. else:
  1538. return redirect(self.get_return_url(request, obj))
  1539. return render(request, self.template_name, {
  1540. 'obj': self.obj,
  1541. 'obj_type': Cable._meta.verbose_name,
  1542. 'termination_b_type': self.termination_b_type.name,
  1543. 'form': form,
  1544. 'return_url': self.get_return_url(request, self.obj),
  1545. })
  1546. class CableEditView(PermissionRequiredMixin, ObjectEditView):
  1547. permission_required = 'dcim.change_cable'
  1548. model = Cable
  1549. model_form = forms.CableForm
  1550. template_name = 'dcim/cable_edit.html'
  1551. default_return_url = 'dcim:cable_list'
  1552. class CableDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1553. permission_required = 'dcim.delete_cable'
  1554. model = Cable
  1555. default_return_url = 'dcim:cable_list'
  1556. class CableBulkImportView(PermissionRequiredMixin, BulkImportView):
  1557. permission_required = 'dcim.add_cable'
  1558. model_form = forms.CableCSVForm
  1559. table = tables.CableTable
  1560. default_return_url = 'dcim:cable_list'
  1561. class CableBulkEditView(PermissionRequiredMixin, BulkEditView):
  1562. permission_required = 'dcim.change_cable'
  1563. queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
  1564. filterset = filters.CableFilterSet
  1565. table = tables.CableTable
  1566. form = forms.CableBulkEditForm
  1567. default_return_url = 'dcim:cable_list'
  1568. class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1569. permission_required = 'dcim.delete_cable'
  1570. queryset = Cable.objects.prefetch_related('termination_a', 'termination_b')
  1571. filterset = filters.CableFilterSet
  1572. table = tables.CableTable
  1573. default_return_url = 'dcim:cable_list'
  1574. #
  1575. # Connections
  1576. #
  1577. class ConsoleConnectionsListView(PermissionRequiredMixin, ObjectListView):
  1578. permission_required = ('dcim.view_consoleport', 'dcim.view_consoleserverport')
  1579. queryset = ConsolePort.objects.prefetch_related(
  1580. 'device', 'connected_endpoint__device'
  1581. ).filter(
  1582. connected_endpoint__isnull=False
  1583. ).order_by(
  1584. 'cable', 'connected_endpoint__device__name', 'connected_endpoint__name'
  1585. )
  1586. filterset = filters.ConsoleConnectionFilterSet
  1587. filterset_form = forms.ConsoleConnectionFilterForm
  1588. table = tables.ConsoleConnectionTable
  1589. template_name = 'dcim/console_connections_list.html'
  1590. def queryset_to_csv(self):
  1591. csv_data = [
  1592. # Headers
  1593. ','.join(['console_server', 'port', 'device', 'console_port', 'connection_status'])
  1594. ]
  1595. for obj in self.queryset:
  1596. csv = csv_format([
  1597. obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
  1598. obj.connected_endpoint.name if obj.connected_endpoint else None,
  1599. obj.device.identifier,
  1600. obj.name,
  1601. obj.get_connection_status_display(),
  1602. ])
  1603. csv_data.append(csv)
  1604. return '\n'.join(csv_data)
  1605. class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView):
  1606. permission_required = ('dcim.view_powerport', 'dcim.view_poweroutlet')
  1607. queryset = PowerPort.objects.prefetch_related(
  1608. 'device', '_connected_poweroutlet__device'
  1609. ).filter(
  1610. _connected_poweroutlet__isnull=False
  1611. ).order_by(
  1612. 'cable', '_connected_poweroutlet__device__name', '_connected_poweroutlet__name'
  1613. )
  1614. filterset = filters.PowerConnectionFilterSet
  1615. filterset_form = forms.PowerConnectionFilterForm
  1616. table = tables.PowerConnectionTable
  1617. template_name = 'dcim/power_connections_list.html'
  1618. def queryset_to_csv(self):
  1619. csv_data = [
  1620. # Headers
  1621. ','.join(['pdu', 'outlet', 'device', 'power_port', 'connection_status'])
  1622. ]
  1623. for obj in self.queryset:
  1624. csv = csv_format([
  1625. obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
  1626. obj.connected_endpoint.name if obj.connected_endpoint else None,
  1627. obj.device.identifier,
  1628. obj.name,
  1629. obj.get_connection_status_display(),
  1630. ])
  1631. csv_data.append(csv)
  1632. return '\n'.join(csv_data)
  1633. class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView):
  1634. permission_required = 'dcim.view_interface'
  1635. queryset = Interface.objects.prefetch_related(
  1636. 'device', 'cable', '_connected_interface__device'
  1637. ).filter(
  1638. # Avoid duplicate connections by only selecting the lower PK in a connected pair
  1639. _connected_interface__isnull=False,
  1640. pk__lt=F('_connected_interface')
  1641. ).order_by(
  1642. 'device'
  1643. )
  1644. filterset = filters.InterfaceConnectionFilterSet
  1645. filterset_form = forms.InterfaceConnectionFilterForm
  1646. table = tables.InterfaceConnectionTable
  1647. template_name = 'dcim/interface_connections_list.html'
  1648. def queryset_to_csv(self):
  1649. csv_data = [
  1650. # Headers
  1651. ','.join([
  1652. 'device_a', 'interface_a', 'interface_a_description',
  1653. 'device_b', 'interface_b', 'interface_b_description',
  1654. 'connection_status'
  1655. ])
  1656. ]
  1657. for obj in self.queryset:
  1658. csv = csv_format([
  1659. obj.connected_endpoint.device.identifier if obj.connected_endpoint else None,
  1660. obj.connected_endpoint.name if obj.connected_endpoint else None,
  1661. obj.connected_endpoint.description if obj.connected_endpoint else None,
  1662. obj.device.identifier,
  1663. obj.name,
  1664. obj.description,
  1665. obj.get_connection_status_display(),
  1666. ])
  1667. csv_data.append(csv)
  1668. return '\n'.join(csv_data)
  1669. #
  1670. # Inventory items
  1671. #
  1672. class InventoryItemListView(PermissionRequiredMixin, ObjectListView):
  1673. permission_required = 'dcim.view_inventoryitem'
  1674. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
  1675. filterset = filters.InventoryItemFilterSet
  1676. filterset_form = forms.InventoryItemFilterForm
  1677. table = tables.InventoryItemTable
  1678. action_buttons = ('import', 'export')
  1679. class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView):
  1680. permission_required = 'dcim.change_inventoryitem'
  1681. model = InventoryItem
  1682. model_form = forms.InventoryItemForm
  1683. class InventoryItemCreateView(PermissionRequiredMixin, ComponentCreateView):
  1684. permission_required = 'dcim.add_inventoryitem'
  1685. model = InventoryItem
  1686. form = forms.InventoryItemCreateForm
  1687. model_form = forms.InventoryItemForm
  1688. template_name = 'dcim/device_component_add.html'
  1689. class InventoryItemDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1690. permission_required = 'dcim.delete_inventoryitem'
  1691. model = InventoryItem
  1692. class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
  1693. permission_required = 'dcim.add_inventoryitem'
  1694. model_form = forms.InventoryItemCSVForm
  1695. table = tables.InventoryItemTable
  1696. default_return_url = 'dcim:inventoryitem_list'
  1697. class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
  1698. permission_required = 'dcim.change_inventoryitem'
  1699. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
  1700. filterset = filters.InventoryItemFilterSet
  1701. table = tables.InventoryItemTable
  1702. form = forms.InventoryItemBulkEditForm
  1703. default_return_url = 'dcim:inventoryitem_list'
  1704. class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1705. permission_required = 'dcim.delete_inventoryitem'
  1706. queryset = InventoryItem.objects.prefetch_related('device', 'manufacturer')
  1707. table = tables.InventoryItemTable
  1708. template_name = 'dcim/inventoryitem_bulk_delete.html'
  1709. default_return_url = 'dcim:inventoryitem_list'
  1710. #
  1711. # Virtual chassis
  1712. #
  1713. class VirtualChassisListView(PermissionRequiredMixin, ObjectListView):
  1714. permission_required = 'dcim.view_virtualchassis'
  1715. queryset = VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members'))
  1716. table = tables.VirtualChassisTable
  1717. filterset = filters.VirtualChassisFilterSet
  1718. filterset_form = forms.VirtualChassisFilterForm
  1719. action_buttons = ('export',)
  1720. class VirtualChassisCreateView(PermissionRequiredMixin, View):
  1721. permission_required = 'dcim.add_virtualchassis'
  1722. def post(self, request):
  1723. # Get the list of devices being added to a VirtualChassis
  1724. pk_form = forms.DeviceSelectionForm(request.POST)
  1725. pk_form.full_clean()
  1726. if not pk_form.cleaned_data.get('pk'):
  1727. messages.warning(request, "No devices were selected.")
  1728. return redirect('dcim:device_list')
  1729. device_queryset = Device.objects.filter(
  1730. pk__in=pk_form.cleaned_data.get('pk')
  1731. ).prefetch_related('rack').order_by('vc_position')
  1732. VCMemberFormSet = modelformset_factory(
  1733. model=Device,
  1734. formset=forms.BaseVCMemberFormSet,
  1735. form=forms.DeviceVCMembershipForm,
  1736. extra=0
  1737. )
  1738. if '_create' in request.POST:
  1739. vc_form = forms.VirtualChassisForm(request.POST)
  1740. vc_form.fields['master'].queryset = device_queryset
  1741. formset = VCMemberFormSet(request.POST, queryset=device_queryset)
  1742. if vc_form.is_valid() and formset.is_valid():
  1743. with transaction.atomic():
  1744. # Assign each device to the VirtualChassis before saving
  1745. virtual_chassis = vc_form.save()
  1746. devices = formset.save(commit=False)
  1747. for device in devices:
  1748. device.virtual_chassis = virtual_chassis
  1749. device.save()
  1750. return redirect(vc_form.cleaned_data['master'].get_absolute_url())
  1751. else:
  1752. vc_form = forms.VirtualChassisForm()
  1753. vc_form.fields['master'].queryset = device_queryset
  1754. formset = VCMemberFormSet(queryset=device_queryset)
  1755. return render(request, 'dcim/virtualchassis_edit.html', {
  1756. 'pk_form': pk_form,
  1757. 'vc_form': vc_form,
  1758. 'formset': formset,
  1759. 'return_url': reverse('dcim:device_list'),
  1760. })
  1761. class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1762. permission_required = 'dcim.change_virtualchassis'
  1763. def get(self, request, pk):
  1764. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1765. VCMemberFormSet = modelformset_factory(
  1766. model=Device,
  1767. form=forms.DeviceVCMembershipForm,
  1768. formset=forms.BaseVCMemberFormSet,
  1769. extra=0
  1770. )
  1771. members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
  1772. vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
  1773. vc_form.fields['master'].queryset = members_queryset
  1774. formset = VCMemberFormSet(queryset=members_queryset)
  1775. return render(request, 'dcim/virtualchassis_edit.html', {
  1776. 'vc_form': vc_form,
  1777. 'formset': formset,
  1778. 'return_url': self.get_return_url(request, virtual_chassis),
  1779. })
  1780. def post(self, request, pk):
  1781. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1782. VCMemberFormSet = modelformset_factory(
  1783. model=Device,
  1784. form=forms.DeviceVCMembershipForm,
  1785. formset=forms.BaseVCMemberFormSet,
  1786. extra=0
  1787. )
  1788. members_queryset = virtual_chassis.members.prefetch_related('rack').order_by('vc_position')
  1789. vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
  1790. vc_form.fields['master'].queryset = members_queryset
  1791. formset = VCMemberFormSet(request.POST, queryset=members_queryset)
  1792. if vc_form.is_valid() and formset.is_valid():
  1793. with transaction.atomic():
  1794. # Save the VirtualChassis
  1795. vc_form.save()
  1796. # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
  1797. # duplicate positions. Then save each member instance.
  1798. members = formset.save(commit=False)
  1799. devices = Device.objects.filter(pk__in=[m.pk for m in members])
  1800. for device in devices:
  1801. device.vc_position = None
  1802. device.save()
  1803. for member in members:
  1804. member.save()
  1805. return redirect(vc_form.cleaned_data['master'].get_absolute_url())
  1806. return render(request, 'dcim/virtualchassis_edit.html', {
  1807. 'vc_form': vc_form,
  1808. 'formset': formset,
  1809. 'return_url': self.get_return_url(request, virtual_chassis),
  1810. })
  1811. class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1812. permission_required = 'dcim.delete_virtualchassis'
  1813. model = VirtualChassis
  1814. default_return_url = 'dcim:device_list'
  1815. class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1816. permission_required = 'dcim.change_virtualchassis'
  1817. def get(self, request, pk):
  1818. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1819. initial_data = {k: request.GET[k] for k in request.GET}
  1820. member_select_form = forms.VCMemberSelectForm(initial=initial_data)
  1821. membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
  1822. return render(request, 'dcim/virtualchassis_add_member.html', {
  1823. 'virtual_chassis': virtual_chassis,
  1824. 'member_select_form': member_select_form,
  1825. 'membership_form': membership_form,
  1826. 'return_url': self.get_return_url(request, virtual_chassis),
  1827. })
  1828. def post(self, request, pk):
  1829. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1830. member_select_form = forms.VCMemberSelectForm(request.POST)
  1831. if member_select_form.is_valid():
  1832. device = member_select_form.cleaned_data['device']
  1833. device.virtual_chassis = virtual_chassis
  1834. data = {k: request.POST[k] for k in ['vc_position', 'vc_priority']}
  1835. membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
  1836. if membership_form.is_valid():
  1837. membership_form.save()
  1838. msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device))
  1839. messages.success(request, mark_safe(msg))
  1840. if '_addanother' in request.POST:
  1841. return redirect(request.get_full_path())
  1842. return redirect(self.get_return_url(request, device))
  1843. else:
  1844. membership_form = forms.DeviceVCMembershipForm(data=request.POST)
  1845. return render(request, 'dcim/virtualchassis_add_member.html', {
  1846. 'virtual_chassis': virtual_chassis,
  1847. 'member_select_form': member_select_form,
  1848. 'membership_form': membership_form,
  1849. 'return_url': self.get_return_url(request, virtual_chassis),
  1850. })
  1851. class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1852. permission_required = 'dcim.change_virtualchassis'
  1853. def get(self, request, pk):
  1854. device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
  1855. form = ConfirmationForm(initial=request.GET)
  1856. return render(request, 'dcim/virtualchassis_remove_member.html', {
  1857. 'device': device,
  1858. 'form': form,
  1859. 'return_url': self.get_return_url(request, device),
  1860. })
  1861. def post(self, request, pk):
  1862. device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
  1863. form = ConfirmationForm(request.POST)
  1864. # Protect master device from being removed
  1865. virtual_chassis = VirtualChassis.objects.filter(master=device).first()
  1866. if virtual_chassis is not None:
  1867. msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device))
  1868. messages.error(request, mark_safe(msg))
  1869. return redirect(device.get_absolute_url())
  1870. if form.is_valid():
  1871. devices = Device.objects.filter(pk=device.pk)
  1872. for device in devices:
  1873. device.virtual_chassis = None
  1874. device.vc_position = None
  1875. device.vc_priority = None
  1876. device.save()
  1877. msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
  1878. messages.success(request, msg)
  1879. return redirect(self.get_return_url(request, device))
  1880. return render(request, 'dcim/virtualchassis_remove_member.html', {
  1881. 'device': device,
  1882. 'form': form,
  1883. 'return_url': self.get_return_url(request, device),
  1884. })
  1885. #
  1886. # Power panels
  1887. #
  1888. class PowerPanelListView(PermissionRequiredMixin, ObjectListView):
  1889. permission_required = 'dcim.view_powerpanel'
  1890. queryset = PowerPanel.objects.prefetch_related(
  1891. 'site', 'rack_group'
  1892. ).annotate(
  1893. powerfeed_count=Count('powerfeeds')
  1894. )
  1895. filterset = filters.PowerPanelFilterSet
  1896. filterset_form = forms.PowerPanelFilterForm
  1897. table = tables.PowerPanelTable
  1898. class PowerPanelView(PermissionRequiredMixin, View):
  1899. permission_required = 'dcim.view_powerpanel'
  1900. def get(self, request, pk):
  1901. powerpanel = get_object_or_404(PowerPanel.objects.prefetch_related('site', 'rack_group'), pk=pk)
  1902. powerfeed_table = tables.PowerFeedTable(
  1903. data=PowerFeed.objects.filter(power_panel=powerpanel).prefetch_related('rack'),
  1904. orderable=False
  1905. )
  1906. powerfeed_table.exclude = ['power_panel']
  1907. return render(request, 'dcim/powerpanel.html', {
  1908. 'powerpanel': powerpanel,
  1909. 'powerfeed_table': powerfeed_table,
  1910. })
  1911. class PowerPanelCreateView(PermissionRequiredMixin, ObjectEditView):
  1912. permission_required = 'dcim.add_powerpanel'
  1913. model = PowerPanel
  1914. model_form = forms.PowerPanelForm
  1915. default_return_url = 'dcim:powerpanel_list'
  1916. class PowerPanelEditView(PowerPanelCreateView):
  1917. permission_required = 'dcim.change_powerpanel'
  1918. class PowerPanelDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1919. permission_required = 'dcim.delete_powerpanel'
  1920. model = PowerPanel
  1921. default_return_url = 'dcim:powerpanel_list'
  1922. class PowerPanelBulkImportView(PermissionRequiredMixin, BulkImportView):
  1923. permission_required = 'dcim.add_powerpanel'
  1924. model_form = forms.PowerPanelCSVForm
  1925. table = tables.PowerPanelTable
  1926. default_return_url = 'dcim:powerpanel_list'
  1927. class PowerPanelBulkEditView(PermissionRequiredMixin, BulkEditView):
  1928. permission_required = 'dcim.change_powerpanel'
  1929. queryset = PowerPanel.objects.prefetch_related('site', 'rack_group')
  1930. filterset = filters.PowerPanelFilterSet
  1931. table = tables.PowerPanelTable
  1932. form = forms.PowerPanelBulkEditForm
  1933. default_return_url = 'dcim:powerpanel_list'
  1934. class PowerPanelBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1935. permission_required = 'dcim.delete_powerpanel'
  1936. queryset = PowerPanel.objects.prefetch_related(
  1937. 'site', 'rack_group'
  1938. ).annotate(
  1939. rack_count=Count('powerfeeds')
  1940. )
  1941. filterset = filters.PowerPanelFilterSet
  1942. table = tables.PowerPanelTable
  1943. default_return_url = 'dcim:powerpanel_list'
  1944. #
  1945. # Power feeds
  1946. #
  1947. class PowerFeedListView(PermissionRequiredMixin, ObjectListView):
  1948. permission_required = 'dcim.view_powerfeed'
  1949. queryset = PowerFeed.objects.prefetch_related(
  1950. 'power_panel', 'rack'
  1951. )
  1952. filterset = filters.PowerFeedFilterSet
  1953. filterset_form = forms.PowerFeedFilterForm
  1954. table = tables.PowerFeedTable
  1955. class PowerFeedView(PermissionRequiredMixin, View):
  1956. permission_required = 'dcim.view_powerfeed'
  1957. def get(self, request, pk):
  1958. powerfeed = get_object_or_404(PowerFeed.objects.prefetch_related('power_panel', 'rack'), pk=pk)
  1959. return render(request, 'dcim/powerfeed.html', {
  1960. 'powerfeed': powerfeed,
  1961. })
  1962. class PowerFeedCreateView(PermissionRequiredMixin, ObjectEditView):
  1963. permission_required = 'dcim.add_powerfeed'
  1964. model = PowerFeed
  1965. model_form = forms.PowerFeedForm
  1966. template_name = 'dcim/powerfeed_edit.html'
  1967. default_return_url = 'dcim:powerfeed_list'
  1968. class PowerFeedEditView(PowerFeedCreateView):
  1969. permission_required = 'dcim.change_powerfeed'
  1970. class PowerFeedDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1971. permission_required = 'dcim.delete_powerfeed'
  1972. model = PowerFeed
  1973. default_return_url = 'dcim:powerfeed_list'
  1974. class PowerFeedBulkImportView(PermissionRequiredMixin, BulkImportView):
  1975. permission_required = 'dcim.add_powerfeed'
  1976. model_form = forms.PowerFeedCSVForm
  1977. table = tables.PowerFeedTable
  1978. default_return_url = 'dcim:powerfeed_list'
  1979. class PowerFeedBulkEditView(PermissionRequiredMixin, BulkEditView):
  1980. permission_required = 'dcim.change_powerfeed'
  1981. queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
  1982. filterset = filters.PowerFeedFilterSet
  1983. table = tables.PowerFeedTable
  1984. form = forms.PowerFeedBulkEditForm
  1985. default_return_url = 'dcim:powerfeed_list'
  1986. class PowerFeedBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1987. permission_required = 'dcim.delete_powerfeed'
  1988. queryset = PowerFeed.objects.prefetch_related('power_panel', 'rack')
  1989. filterset = filters.PowerFeedFilterSet
  1990. table = tables.PowerFeedTable
  1991. default_return_url = 'dcim:powerfeed_list'