views.py 75 KB

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