views.py 80 KB

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