views.py 77 KB

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