views.py 79 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313
  1. from __future__ import unicode_literals
  2. from operator import attrgetter
  3. from django.contrib import messages
  4. from django.contrib.auth.mixins import PermissionRequiredMixin
  5. from django.core.paginator import EmptyPage, PageNotAnInteger
  6. from django.db import transaction
  7. from django.db.models import Count, Q
  8. from django.forms import modelformset_factory
  9. from django.http import HttpResponseRedirect
  10. from django.shortcuts import get_object_or_404, redirect, render
  11. from django.urls import reverse
  12. from django.utils.html import escape
  13. from django.utils.http import urlencode
  14. from django.utils.safestring import mark_safe
  15. from django.views.generic import View
  16. from natsort import natsorted
  17. from circuits.models import Circuit
  18. from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
  19. from extras.views import ObjectConfigContextView
  20. from ipam.models import Prefix, Service, VLAN
  21. from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable
  22. from utilities.forms import ConfirmationForm
  23. from utilities.paginator import EnhancedPaginator
  24. from utilities.views import (
  25. BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin,
  26. ObjectDeleteView, ObjectEditView, ObjectListView,
  27. )
  28. from virtualization.models import VirtualMachine
  29. from . import filters, forms, tables
  30. from .constants import CONNECTION_STATUS_CONNECTED
  31. from .models import (
  32. ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay,
  33. DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer,
  34. InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup,
  35. RackReservation, RackRole, Region, Site, VirtualChassis,
  36. )
  37. class BulkRenameView(GetReturnURLMixin, View):
  38. """
  39. An extendable view for renaming device components in bulk.
  40. """
  41. queryset = None
  42. form = None
  43. template_name = 'dcim/bulk_rename.html'
  44. def post(self, request):
  45. model = self.queryset.model
  46. if '_preview' in request.POST or '_apply' in request.POST:
  47. form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')})
  48. selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
  49. if form.is_valid():
  50. for obj in selected_objects:
  51. obj.new_name = obj.name.replace(form.cleaned_data['find'], form.cleaned_data['replace'])
  52. if '_apply' in request.POST:
  53. for obj in selected_objects:
  54. obj.name = obj.new_name
  55. obj.save()
  56. messages.success(request, "Renamed {} {}".format(
  57. len(selected_objects),
  58. model._meta.verbose_name_plural
  59. ))
  60. return redirect(self.get_return_url(request))
  61. else:
  62. form = self.form(initial={'pk': request.POST.getlist('pk')})
  63. selected_objects = self.queryset.filter(pk__in=form.initial['pk'])
  64. return render(request, self.template_name, {
  65. 'form': form,
  66. 'obj_type_plural': model._meta.verbose_name_plural,
  67. 'selected_objects': selected_objects,
  68. 'return_url': self.get_return_url(request),
  69. })
  70. class BulkDisconnectView(View):
  71. """
  72. An extendable view for disconnection console/power/interface components in bulk.
  73. """
  74. model = None
  75. form = None
  76. template_name = 'dcim/bulk_disconnect.html'
  77. def disconnect_objects(self, objects):
  78. raise NotImplementedError()
  79. def post(self, request, pk):
  80. device = get_object_or_404(Device, pk=pk)
  81. selected_objects = []
  82. if '_confirm' in request.POST:
  83. form = self.form(request.POST)
  84. if form.is_valid():
  85. count = self.disconnect_objects(form.cleaned_data['pk'])
  86. messages.success(request, "Disconnected {} {} on {}".format(
  87. count, self.model._meta.verbose_name_plural, device
  88. ))
  89. return redirect(device.get_absolute_url())
  90. else:
  91. form = self.form(initial={'pk': request.POST.getlist('pk')})
  92. selected_objects = self.model.objects.filter(pk__in=form.initial['pk'])
  93. return render(request, self.template_name, {
  94. 'form': form,
  95. 'device': device,
  96. 'obj_type_plural': self.model._meta.verbose_name_plural,
  97. 'selected_objects': selected_objects,
  98. 'return_url': device.get_absolute_url(),
  99. })
  100. #
  101. # Regions
  102. #
  103. class RegionListView(ObjectListView):
  104. queryset = Region.objects.annotate(site_count=Count('sites'))
  105. filter = filters.RegionFilter
  106. filter_form = forms.RegionFilterForm
  107. table = tables.RegionTable
  108. template_name = 'dcim/region_list.html'
  109. class RegionCreateView(PermissionRequiredMixin, ObjectEditView):
  110. permission_required = 'dcim.add_region'
  111. model = Region
  112. model_form = forms.RegionForm
  113. default_return_url = 'dcim:region_list'
  114. class RegionEditView(RegionCreateView):
  115. permission_required = 'dcim.change_region'
  116. class RegionBulkImportView(PermissionRequiredMixin, BulkImportView):
  117. permission_required = 'dcim.add_region'
  118. model_form = forms.RegionCSVForm
  119. table = tables.RegionTable
  120. default_return_url = 'dcim:region_list'
  121. class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  122. permission_required = 'dcim.delete_region'
  123. queryset = Region.objects.annotate(site_count=Count('sites'))
  124. filter = filters.RegionFilter
  125. table = tables.RegionTable
  126. default_return_url = 'dcim:region_list'
  127. #
  128. # Sites
  129. #
  130. class SiteListView(ObjectListView):
  131. queryset = Site.objects.select_related('region', 'tenant')
  132. filter = filters.SiteFilter
  133. filter_form = forms.SiteFilterForm
  134. table = tables.SiteTable
  135. template_name = 'dcim/site_list.html'
  136. class SiteView(View):
  137. def get(self, request, slug):
  138. site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug)
  139. stats = {
  140. 'rack_count': Rack.objects.filter(site=site).count(),
  141. 'device_count': Device.objects.filter(site=site).count(),
  142. 'prefix_count': Prefix.objects.filter(site=site).count(),
  143. 'vlan_count': VLAN.objects.filter(site=site).count(),
  144. 'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
  145. 'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(),
  146. }
  147. rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
  148. topology_maps = TopologyMap.objects.filter(site=site)
  149. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists()
  150. return render(request, 'dcim/site.html', {
  151. 'site': site,
  152. 'stats': stats,
  153. 'rack_groups': rack_groups,
  154. 'topology_maps': topology_maps,
  155. 'show_graphs': show_graphs,
  156. })
  157. class SiteCreateView(PermissionRequiredMixin, ObjectEditView):
  158. permission_required = 'dcim.add_site'
  159. model = Site
  160. model_form = forms.SiteForm
  161. template_name = 'dcim/site_edit.html'
  162. default_return_url = 'dcim:site_list'
  163. class SiteEditView(SiteCreateView):
  164. permission_required = 'dcim.change_site'
  165. class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  166. permission_required = 'dcim.delete_site'
  167. model = Site
  168. default_return_url = 'dcim:site_list'
  169. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  170. permission_required = 'dcim.add_site'
  171. model_form = forms.SiteCSVForm
  172. table = tables.SiteTable
  173. default_return_url = 'dcim:site_list'
  174. class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
  175. permission_required = 'dcim.change_site'
  176. queryset = Site.objects.select_related('region', 'tenant')
  177. filter = filters.SiteFilter
  178. table = tables.SiteTable
  179. form = forms.SiteBulkEditForm
  180. default_return_url = 'dcim:site_list'
  181. #
  182. # Rack groups
  183. #
  184. class RackGroupListView(ObjectListView):
  185. queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
  186. filter = filters.RackGroupFilter
  187. filter_form = forms.RackGroupFilterForm
  188. table = tables.RackGroupTable
  189. template_name = 'dcim/rackgroup_list.html'
  190. class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView):
  191. permission_required = 'dcim.add_rackgroup'
  192. model = RackGroup
  193. model_form = forms.RackGroupForm
  194. default_return_url = 'dcim:rackgroup_list'
  195. class RackGroupEditView(RackGroupCreateView):
  196. permission_required = 'dcim.change_rackgroup'
  197. class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
  198. permission_required = 'dcim.add_rackgroup'
  199. model_form = forms.RackGroupCSVForm
  200. table = tables.RackGroupTable
  201. default_return_url = 'dcim:rackgroup_list'
  202. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  203. permission_required = 'dcim.delete_rackgroup'
  204. queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
  205. filter = filters.RackGroupFilter
  206. table = tables.RackGroupTable
  207. default_return_url = 'dcim:rackgroup_list'
  208. #
  209. # Rack roles
  210. #
  211. class RackRoleListView(ObjectListView):
  212. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  213. table = tables.RackRoleTable
  214. template_name = 'dcim/rackrole_list.html'
  215. class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  216. permission_required = 'dcim.add_rackrole'
  217. model = RackRole
  218. model_form = forms.RackRoleForm
  219. default_return_url = 'dcim:rackrole_list'
  220. class RackRoleEditView(RackRoleCreateView):
  221. permission_required = 'dcim.change_rackrole'
  222. class RackRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  223. permission_required = 'dcim.add_rackrole'
  224. model_form = forms.RackRoleCSVForm
  225. table = tables.RackRoleTable
  226. default_return_url = 'dcim:rackrole_list'
  227. class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  228. permission_required = 'dcim.delete_rackrole'
  229. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  230. table = tables.RackRoleTable
  231. default_return_url = 'dcim:rackrole_list'
  232. #
  233. # Racks
  234. #
  235. class RackListView(ObjectListView):
  236. queryset = Rack.objects.select_related(
  237. 'site', 'group', 'tenant', 'role'
  238. ).prefetch_related(
  239. 'devices__device_type'
  240. ).annotate(
  241. device_count=Count('devices')
  242. )
  243. filter = filters.RackFilter
  244. filter_form = forms.RackFilterForm
  245. table = tables.RackDetailTable
  246. template_name = 'dcim/rack_list.html'
  247. class RackElevationListView(View):
  248. """
  249. Display a set of rack elevations side-by-side.
  250. """
  251. def get(self, request):
  252. racks = Rack.objects.select_related(
  253. 'site', 'group', 'tenant', 'role'
  254. ).prefetch_related(
  255. 'devices__device_type'
  256. )
  257. racks = filters.RackFilter(request.GET, racks).qs
  258. total_count = racks.count()
  259. # Pagination
  260. paginator = EnhancedPaginator(racks, 25)
  261. page_number = request.GET.get('page', 1)
  262. try:
  263. page = paginator.page(page_number)
  264. except PageNotAnInteger:
  265. page = paginator.page(1)
  266. except EmptyPage:
  267. page = paginator.page(paginator.num_pages)
  268. # Determine rack face
  269. if request.GET.get('face') == '1':
  270. face_id = 1
  271. else:
  272. face_id = 0
  273. return render(request, 'dcim/rack_elevation_list.html', {
  274. 'paginator': paginator,
  275. 'page': page,
  276. 'total_count': total_count,
  277. 'face_id': face_id,
  278. 'filter_form': forms.RackFilterForm(request.GET),
  279. })
  280. class RackView(View):
  281. def get(self, request, pk):
  282. rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
  283. nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True) \
  284. .select_related('device_type__manufacturer')
  285. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  286. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  287. reservations = RackReservation.objects.filter(rack=rack)
  288. return render(request, 'dcim/rack.html', {
  289. 'rack': rack,
  290. 'reservations': reservations,
  291. 'nonracked_devices': nonracked_devices,
  292. 'next_rack': next_rack,
  293. 'prev_rack': prev_rack,
  294. 'front_elevation': rack.get_front_elevation(),
  295. 'rear_elevation': rack.get_rear_elevation(),
  296. })
  297. class RackCreateView(PermissionRequiredMixin, ObjectEditView):
  298. permission_required = 'dcim.add_rack'
  299. model = Rack
  300. model_form = forms.RackForm
  301. template_name = 'dcim/rack_edit.html'
  302. default_return_url = 'dcim:rack_list'
  303. class RackEditView(RackCreateView):
  304. permission_required = 'dcim.change_rack'
  305. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  306. permission_required = 'dcim.delete_rack'
  307. model = Rack
  308. default_return_url = 'dcim:rack_list'
  309. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  310. permission_required = 'dcim.add_rack'
  311. model_form = forms.RackCSVForm
  312. table = tables.RackImportTable
  313. default_return_url = 'dcim:rack_list'
  314. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  315. permission_required = 'dcim.change_rack'
  316. queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role')
  317. filter = filters.RackFilter
  318. table = tables.RackTable
  319. form = forms.RackBulkEditForm
  320. default_return_url = 'dcim:rack_list'
  321. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  322. permission_required = 'dcim.delete_rack'
  323. queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role')
  324. filter = filters.RackFilter
  325. table = tables.RackTable
  326. default_return_url = 'dcim:rack_list'
  327. #
  328. # Rack reservations
  329. #
  330. class RackReservationListView(ObjectListView):
  331. queryset = RackReservation.objects.all()
  332. filter = filters.RackReservationFilter
  333. filter_form = forms.RackReservationFilterForm
  334. table = tables.RackReservationTable
  335. template_name = 'dcim/rackreservation_list.html'
  336. class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView):
  337. permission_required = 'dcim.add_rackreservation'
  338. model = RackReservation
  339. model_form = forms.RackReservationForm
  340. def alter_obj(self, obj, request, args, kwargs):
  341. if not obj.pk:
  342. obj.rack = get_object_or_404(Rack, pk=kwargs['rack'])
  343. obj.user = request.user
  344. return obj
  345. def get_return_url(self, request, obj):
  346. return obj.rack.get_absolute_url()
  347. class RackReservationEditView(RackReservationCreateView):
  348. permission_required = 'dcim.change_rackreservation'
  349. class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  350. permission_required = 'dcim.delete_rackreservation'
  351. model = RackReservation
  352. def get_return_url(self, request, obj):
  353. return obj.rack.get_absolute_url()
  354. class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView):
  355. permission_required = 'dcim.change_rackreservation'
  356. queryset = RackReservation.objects.select_related('rack', 'user')
  357. filter = filters.RackReservationFilter
  358. table = tables.RackReservationTable
  359. form = forms.RackReservationBulkEditForm
  360. default_return_url = 'dcim:rackreservation_list'
  361. class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  362. permission_required = 'dcim.delete_rackreservation'
  363. queryset = RackReservation.objects.select_related('rack', 'user')
  364. filter = filters.RackReservationFilter
  365. table = tables.RackReservationTable
  366. default_return_url = 'dcim:rackreservation_list'
  367. #
  368. # Manufacturers
  369. #
  370. class ManufacturerListView(ObjectListView):
  371. queryset = Manufacturer.objects.annotate(
  372. devicetype_count=Count('device_types', distinct=True),
  373. platform_count=Count('platforms', distinct=True),
  374. )
  375. table = tables.ManufacturerTable
  376. template_name = 'dcim/manufacturer_list.html'
  377. class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView):
  378. permission_required = 'dcim.add_manufacturer'
  379. model = Manufacturer
  380. model_form = forms.ManufacturerForm
  381. default_return_url = 'dcim:manufacturer_list'
  382. class ManufacturerEditView(ManufacturerCreateView):
  383. permission_required = 'dcim.change_manufacturer'
  384. class ManufacturerBulkImportView(PermissionRequiredMixin, BulkImportView):
  385. permission_required = 'dcim.add_manufacturer'
  386. model_form = forms.ManufacturerCSVForm
  387. table = tables.ManufacturerTable
  388. default_return_url = 'dcim:manufacturer_list'
  389. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  390. permission_required = 'dcim.delete_manufacturer'
  391. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  392. table = tables.ManufacturerTable
  393. default_return_url = 'dcim:manufacturer_list'
  394. #
  395. # Device types
  396. #
  397. class DeviceTypeListView(ObjectListView):
  398. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  399. filter = filters.DeviceTypeFilter
  400. filter_form = forms.DeviceTypeFilterForm
  401. table = tables.DeviceTypeTable
  402. template_name = 'dcim/devicetype_list.html'
  403. class DeviceTypeView(View):
  404. def get(self, request, pk):
  405. devicetype = get_object_or_404(DeviceType, pk=pk)
  406. # Component tables
  407. consoleport_table = tables.ConsolePortTemplateTable(
  408. natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  409. orderable=False
  410. )
  411. consoleserverport_table = tables.ConsoleServerPortTemplateTable(
  412. natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  413. orderable=False
  414. )
  415. powerport_table = tables.PowerPortTemplateTable(
  416. natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  417. orderable=False
  418. )
  419. poweroutlet_table = tables.PowerOutletTemplateTable(
  420. natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  421. orderable=False
  422. )
  423. interface_table = tables.InterfaceTemplateTable(
  424. list(InterfaceTemplate.objects.order_naturally(
  425. devicetype.interface_ordering
  426. ).filter(device_type=devicetype)),
  427. orderable=False
  428. )
  429. devicebay_table = tables.DeviceBayTemplateTable(
  430. natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  431. orderable=False
  432. )
  433. if request.user.has_perm('dcim.change_devicetype'):
  434. consoleport_table.columns.show('pk')
  435. consoleserverport_table.columns.show('pk')
  436. powerport_table.columns.show('pk')
  437. poweroutlet_table.columns.show('pk')
  438. interface_table.columns.show('pk')
  439. devicebay_table.columns.show('pk')
  440. return render(request, 'dcim/devicetype.html', {
  441. 'devicetype': devicetype,
  442. 'consoleport_table': consoleport_table,
  443. 'consoleserverport_table': consoleserverport_table,
  444. 'powerport_table': powerport_table,
  445. 'poweroutlet_table': poweroutlet_table,
  446. 'interface_table': interface_table,
  447. 'devicebay_table': devicebay_table,
  448. })
  449. class DeviceTypeCreateView(PermissionRequiredMixin, ObjectEditView):
  450. permission_required = 'dcim.add_devicetype'
  451. model = DeviceType
  452. model_form = forms.DeviceTypeForm
  453. template_name = 'dcim/devicetype_edit.html'
  454. default_return_url = 'dcim:devicetype_list'
  455. class DeviceTypeEditView(DeviceTypeCreateView):
  456. permission_required = 'dcim.change_devicetype'
  457. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  458. permission_required = 'dcim.delete_devicetype'
  459. model = DeviceType
  460. default_return_url = 'dcim:devicetype_list'
  461. class DeviceTypeBulkImportView(PermissionRequiredMixin, BulkImportView):
  462. permission_required = 'dcim.add_devicetype'
  463. model_form = forms.DeviceTypeCSVForm
  464. table = tables.DeviceTypeTable
  465. default_return_url = 'dcim:devicetype_list'
  466. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  467. permission_required = 'dcim.change_devicetype'
  468. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  469. filter = filters.DeviceTypeFilter
  470. table = tables.DeviceTypeTable
  471. form = forms.DeviceTypeBulkEditForm
  472. default_return_url = 'dcim:devicetype_list'
  473. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  474. permission_required = 'dcim.delete_devicetype'
  475. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  476. filter = filters.DeviceTypeFilter
  477. table = tables.DeviceTypeTable
  478. default_return_url = 'dcim:devicetype_list'
  479. #
  480. # Device type components
  481. #
  482. class ConsolePortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  483. permission_required = 'dcim.add_consoleporttemplate'
  484. parent_model = DeviceType
  485. parent_field = 'device_type'
  486. model = ConsolePortTemplate
  487. form = forms.ConsolePortTemplateCreateForm
  488. model_form = forms.ConsolePortTemplateForm
  489. template_name = 'dcim/device_component_add.html'
  490. class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  491. permission_required = 'dcim.delete_consoleporttemplate'
  492. queryset = ConsolePortTemplate.objects.all()
  493. parent_model = DeviceType
  494. table = tables.ConsolePortTemplateTable
  495. class ConsoleServerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  496. permission_required = 'dcim.add_consoleserverporttemplate'
  497. parent_model = DeviceType
  498. parent_field = 'device_type'
  499. model = ConsoleServerPortTemplate
  500. form = forms.ConsoleServerPortTemplateCreateForm
  501. model_form = forms.ConsoleServerPortTemplateForm
  502. template_name = 'dcim/device_component_add.html'
  503. class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  504. permission_required = 'dcim.delete_consoleserverporttemplate'
  505. queryset = ConsoleServerPortTemplate.objects.all()
  506. parent_model = DeviceType
  507. table = tables.ConsoleServerPortTemplateTable
  508. class PowerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  509. permission_required = 'dcim.add_powerporttemplate'
  510. parent_model = DeviceType
  511. parent_field = 'device_type'
  512. model = PowerPortTemplate
  513. form = forms.PowerPortTemplateCreateForm
  514. model_form = forms.PowerPortTemplateForm
  515. template_name = 'dcim/device_component_add.html'
  516. class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  517. permission_required = 'dcim.delete_powerporttemplate'
  518. queryset = PowerPortTemplate.objects.all()
  519. parent_model = DeviceType
  520. table = tables.PowerPortTemplateTable
  521. class PowerOutletTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  522. permission_required = 'dcim.add_poweroutlettemplate'
  523. parent_model = DeviceType
  524. parent_field = 'device_type'
  525. model = PowerOutletTemplate
  526. form = forms.PowerOutletTemplateCreateForm
  527. model_form = forms.PowerOutletTemplateForm
  528. template_name = 'dcim/device_component_add.html'
  529. class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  530. permission_required = 'dcim.delete_poweroutlettemplate'
  531. queryset = PowerOutletTemplate.objects.all()
  532. parent_model = DeviceType
  533. table = tables.PowerOutletTemplateTable
  534. class InterfaceTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  535. permission_required = 'dcim.add_interfacetemplate'
  536. parent_model = DeviceType
  537. parent_field = 'device_type'
  538. model = InterfaceTemplate
  539. form = forms.InterfaceTemplateCreateForm
  540. model_form = forms.InterfaceTemplateForm
  541. template_name = 'dcim/device_component_add.html'
  542. class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  543. permission_required = 'dcim.change_interfacetemplate'
  544. queryset = InterfaceTemplate.objects.all()
  545. parent_model = DeviceType
  546. table = tables.InterfaceTemplateTable
  547. form = forms.InterfaceTemplateBulkEditForm
  548. class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  549. permission_required = 'dcim.delete_interfacetemplate'
  550. queryset = InterfaceTemplate.objects.all()
  551. parent_model = DeviceType
  552. table = tables.InterfaceTemplateTable
  553. class DeviceBayTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  554. permission_required = 'dcim.add_devicebaytemplate'
  555. parent_model = DeviceType
  556. parent_field = 'device_type'
  557. model = DeviceBayTemplate
  558. form = forms.DeviceBayTemplateCreateForm
  559. model_form = forms.DeviceBayTemplateForm
  560. template_name = 'dcim/device_component_add.html'
  561. class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  562. permission_required = 'dcim.delete_devicebaytemplate'
  563. queryset = DeviceBayTemplate.objects.all()
  564. parent_model = DeviceType
  565. table = tables.DeviceBayTemplateTable
  566. #
  567. # Device roles
  568. #
  569. class DeviceRoleListView(ObjectListView):
  570. queryset = DeviceRole.objects.all()
  571. table = tables.DeviceRoleTable
  572. template_name = 'dcim/devicerole_list.html'
  573. class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  574. permission_required = 'dcim.add_devicerole'
  575. model = DeviceRole
  576. model_form = forms.DeviceRoleForm
  577. default_return_url = 'dcim:devicerole_list'
  578. class DeviceRoleEditView(DeviceRoleCreateView):
  579. permission_required = 'dcim.change_devicerole'
  580. class DeviceRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  581. permission_required = 'dcim.add_devicerole'
  582. model_form = forms.DeviceRoleCSVForm
  583. table = tables.DeviceRoleTable
  584. default_return_url = 'dcim:devicerole_list'
  585. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  586. permission_required = 'dcim.delete_devicerole'
  587. queryset = DeviceRole.objects.all()
  588. table = tables.DeviceRoleTable
  589. default_return_url = 'dcim:devicerole_list'
  590. #
  591. # Platforms
  592. #
  593. class PlatformListView(ObjectListView):
  594. queryset = Platform.objects.all()
  595. table = tables.PlatformTable
  596. template_name = 'dcim/platform_list.html'
  597. class PlatformCreateView(PermissionRequiredMixin, ObjectEditView):
  598. permission_required = 'dcim.add_platform'
  599. model = Platform
  600. model_form = forms.PlatformForm
  601. default_return_url = 'dcim:platform_list'
  602. class PlatformEditView(PlatformCreateView):
  603. permission_required = 'dcim.change_platform'
  604. class PlatformBulkImportView(PermissionRequiredMixin, BulkImportView):
  605. permission_required = 'dcim.add_platform'
  606. model_form = forms.PlatformCSVForm
  607. table = tables.PlatformTable
  608. default_return_url = 'dcim:platform_list'
  609. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  610. permission_required = 'dcim.delete_platform'
  611. queryset = Platform.objects.all()
  612. table = tables.PlatformTable
  613. default_return_url = 'dcim:platform_list'
  614. #
  615. # Devices
  616. #
  617. class DeviceListView(ObjectListView):
  618. queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack',
  619. 'primary_ip4', 'primary_ip6')
  620. filter = filters.DeviceFilter
  621. filter_form = forms.DeviceFilterForm
  622. table = tables.DeviceDetailTable
  623. template_name = 'dcim/device_list.html'
  624. class DeviceView(View):
  625. def get(self, request, pk):
  626. device = get_object_or_404(Device.objects.select_related(
  627. 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
  628. ), pk=pk)
  629. # VirtualChassis members
  630. if device.virtual_chassis is not None:
  631. vc_members = Device.objects.filter(virtual_chassis=device.virtual_chassis).order_by('vc_position')
  632. else:
  633. vc_members = []
  634. # Console ports
  635. console_ports = natsorted(
  636. ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
  637. )
  638. # Console server ports
  639. cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console')
  640. # Power ports
  641. power_ports = natsorted(
  642. PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
  643. )
  644. # Power outlets
  645. power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port')
  646. # Interfaces
  647. interfaces = device.vc_interfaces.order_naturally(
  648. device.device_type.interface_ordering
  649. ).select_related(
  650. 'connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
  651. 'circuit_termination__circuit'
  652. ).prefetch_related('ip_addresses')
  653. # Device bays
  654. device_bays = natsorted(
  655. DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
  656. key=attrgetter('name')
  657. )
  658. # Services
  659. services = Service.objects.filter(device=device)
  660. # Secrets
  661. secrets = device.secrets.all()
  662. # Find up to ten devices in the same site with the same functional role for quick reference.
  663. related_devices = Device.objects.filter(
  664. site=device.site, device_role=device.device_role
  665. ).exclude(
  666. pk=device.pk
  667. ).select_related(
  668. 'rack', 'device_type__manufacturer'
  669. )[:10]
  670. # Show graph button on interfaces only if at least one graph has been created.
  671. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists()
  672. return render(request, 'dcim/device.html', {
  673. 'device': device,
  674. 'console_ports': console_ports,
  675. 'cs_ports': cs_ports,
  676. 'power_ports': power_ports,
  677. 'power_outlets': power_outlets,
  678. 'interfaces': interfaces,
  679. 'device_bays': device_bays,
  680. 'services': services,
  681. 'secrets': secrets,
  682. 'vc_members': vc_members,
  683. 'related_devices': related_devices,
  684. 'show_graphs': show_graphs,
  685. })
  686. class DeviceInventoryView(View):
  687. def get(self, request, pk):
  688. device = get_object_or_404(Device, pk=pk)
  689. inventory_items = InventoryItem.objects.filter(
  690. device=device, parent=None
  691. ).select_related(
  692. 'manufacturer'
  693. ).prefetch_related(
  694. 'child_items'
  695. )
  696. return render(request, 'dcim/device_inventory.html', {
  697. 'device': device,
  698. 'inventory_items': inventory_items,
  699. 'active_tab': 'inventory',
  700. })
  701. class DeviceStatusView(PermissionRequiredMixin, View):
  702. permission_required = 'dcim.napalm_read'
  703. def get(self, request, pk):
  704. device = get_object_or_404(Device, pk=pk)
  705. return render(request, 'dcim/device_status.html', {
  706. 'device': device,
  707. 'active_tab': 'status',
  708. })
  709. class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
  710. permission_required = 'dcim.napalm_read'
  711. def get(self, request, pk):
  712. device = get_object_or_404(Device, pk=pk)
  713. interfaces = device.vc_interfaces.order_naturally(
  714. device.device_type.interface_ordering
  715. ).connectable().select_related(
  716. 'connected_as_a', 'connected_as_b'
  717. )
  718. return render(request, 'dcim/device_lldp_neighbors.html', {
  719. 'device': device,
  720. 'interfaces': interfaces,
  721. 'active_tab': 'lldp-neighbors',
  722. })
  723. class DeviceConfigView(PermissionRequiredMixin, View):
  724. permission_required = 'dcim.napalm_read'
  725. def get(self, request, pk):
  726. device = get_object_or_404(Device, pk=pk)
  727. return render(request, 'dcim/device_config.html', {
  728. 'device': device,
  729. 'active_tab': 'config',
  730. })
  731. class DeviceConfigContextView(ObjectConfigContextView):
  732. object_class = Device
  733. base_template = 'dcim/device.html'
  734. class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
  735. permission_required = 'dcim.add_device'
  736. model = Device
  737. model_form = forms.DeviceForm
  738. template_name = 'dcim/device_edit.html'
  739. default_return_url = 'dcim:device_list'
  740. class DeviceEditView(DeviceCreateView):
  741. permission_required = 'dcim.change_device'
  742. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  743. permission_required = 'dcim.delete_device'
  744. model = Device
  745. default_return_url = 'dcim:device_list'
  746. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  747. permission_required = 'dcim.add_device'
  748. model_form = forms.DeviceCSVForm
  749. table = tables.DeviceImportTable
  750. template_name = 'dcim/device_import.html'
  751. default_return_url = 'dcim:device_list'
  752. class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  753. permission_required = 'dcim.add_device'
  754. model_form = forms.ChildDeviceCSVForm
  755. table = tables.DeviceImportTable
  756. template_name = 'dcim/device_import_child.html'
  757. default_return_url = 'dcim:device_list'
  758. def _save_obj(self, obj_form):
  759. obj = obj_form.save()
  760. # Save the reverse relation to the parent device bay
  761. device_bay = obj.parent_bay
  762. device_bay.installed_device = obj
  763. device_bay.save()
  764. return obj
  765. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  766. permission_required = 'dcim.change_device'
  767. queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  768. filter = filters.DeviceFilter
  769. table = tables.DeviceTable
  770. form = forms.DeviceBulkEditForm
  771. default_return_url = 'dcim:device_list'
  772. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  773. permission_required = 'dcim.delete_device'
  774. queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  775. filter = filters.DeviceFilter
  776. table = tables.DeviceTable
  777. default_return_url = 'dcim:device_list'
  778. #
  779. # Console ports
  780. #
  781. class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView):
  782. permission_required = 'dcim.add_consoleport'
  783. parent_model = Device
  784. parent_field = 'device'
  785. model = ConsolePort
  786. form = forms.ConsolePortCreateForm
  787. model_form = forms.ConsolePortForm
  788. template_name = 'dcim/device_component_add.html'
  789. class ConsolePortConnectView(PermissionRequiredMixin, View):
  790. permission_required = 'dcim.change_consoleport'
  791. def get(self, request, pk):
  792. consoleport = get_object_or_404(ConsolePort, pk=pk)
  793. form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
  794. 'site': request.GET.get('site'),
  795. 'rack': request.GET.get('rack'),
  796. 'console_server': request.GET.get('console_server'),
  797. 'connection_status': CONNECTION_STATUS_CONNECTED,
  798. })
  799. return render(request, 'dcim/consoleport_connect.html', {
  800. 'consoleport': consoleport,
  801. 'form': form,
  802. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  803. })
  804. def post(self, request, pk):
  805. consoleport = get_object_or_404(ConsolePort, pk=pk)
  806. form = forms.ConsolePortConnectionForm(request.POST, instance=consoleport)
  807. if form.is_valid():
  808. consoleport = form.save()
  809. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  810. consoleport.device.get_absolute_url(),
  811. escape(consoleport.device),
  812. escape(consoleport.name),
  813. consoleport.cs_port.device.get_absolute_url(),
  814. escape(consoleport.cs_port.device),
  815. escape(consoleport.cs_port.name),
  816. )
  817. messages.success(request, mark_safe(msg))
  818. return redirect('dcim:device', pk=consoleport.device.pk)
  819. return render(request, 'dcim/consoleport_connect.html', {
  820. 'consoleport': consoleport,
  821. 'form': form,
  822. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  823. })
  824. class ConsolePortDisconnectView(PermissionRequiredMixin, View):
  825. permission_required = 'dcim.change_consoleport'
  826. def get(self, request, pk):
  827. consoleport = get_object_or_404(ConsolePort, pk=pk)
  828. form = ConfirmationForm()
  829. if not consoleport.cs_port:
  830. messages.warning(
  831. request, "Cannot disconnect console port {}: It is not connected to anything.".format(consoleport)
  832. )
  833. return redirect('dcim:device', pk=consoleport.device.pk)
  834. return render(request, 'dcim/consoleport_disconnect.html', {
  835. 'consoleport': consoleport,
  836. 'form': form,
  837. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  838. })
  839. def post(self, request, pk):
  840. consoleport = get_object_or_404(ConsolePort, pk=pk)
  841. form = ConfirmationForm(request.POST)
  842. if form.is_valid():
  843. cs_port = consoleport.cs_port
  844. consoleport.cs_port = None
  845. consoleport.connection_status = None
  846. consoleport.save()
  847. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  848. consoleport.device.get_absolute_url(),
  849. escape(consoleport.device),
  850. escape(consoleport.name),
  851. cs_port.device.get_absolute_url(),
  852. escape(cs_port.device),
  853. escape(cs_port.name),
  854. )
  855. messages.success(request, mark_safe(msg))
  856. return redirect('dcim:device', pk=consoleport.device.pk)
  857. return render(request, 'dcim/consoleport_disconnect.html', {
  858. 'consoleport': consoleport,
  859. 'form': form,
  860. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  861. })
  862. class ConsolePortEditView(PermissionRequiredMixin, ObjectEditView):
  863. permission_required = 'dcim.change_consoleport'
  864. model = ConsolePort
  865. model_form = forms.ConsolePortForm
  866. class ConsolePortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  867. permission_required = 'dcim.delete_consoleport'
  868. model = ConsolePort
  869. class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  870. permission_required = 'dcim.delete_consoleport'
  871. queryset = ConsolePort.objects.all()
  872. parent_model = Device
  873. table = tables.ConsolePortTable
  874. class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  875. permission_required = 'dcim.change_consoleport'
  876. model_form = forms.ConsoleConnectionCSVForm
  877. table = tables.ConsoleConnectionTable
  878. default_return_url = 'dcim:console_connections_list'
  879. #
  880. # Console server ports
  881. #
  882. class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  883. permission_required = 'dcim.add_consoleserverport'
  884. parent_model = Device
  885. parent_field = 'device'
  886. model = ConsoleServerPort
  887. form = forms.ConsoleServerPortCreateForm
  888. model_form = forms.ConsoleServerPortForm
  889. template_name = 'dcim/device_component_add.html'
  890. class ConsoleServerPortConnectView(PermissionRequiredMixin, View):
  891. permission_required = 'dcim.change_consoleserverport'
  892. def get(self, request, pk):
  893. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  894. form = forms.ConsoleServerPortConnectionForm(initial={
  895. 'site': request.GET.get('site'),
  896. 'rack': request.GET.get('rack'),
  897. 'device': request.GET.get('device'),
  898. 'connection_status': CONNECTION_STATUS_CONNECTED,
  899. })
  900. return render(request, 'dcim/consoleserverport_connect.html', {
  901. 'consoleserverport': consoleserverport,
  902. 'form': form,
  903. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  904. })
  905. def post(self, request, pk):
  906. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  907. form = forms.ConsoleServerPortConnectionForm(request.POST)
  908. if form.is_valid():
  909. consoleport = form.cleaned_data['port']
  910. consoleport.cs_port = consoleserverport
  911. consoleport.connection_status = form.cleaned_data['connection_status']
  912. consoleport.save()
  913. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  914. consoleport.device.get_absolute_url(),
  915. escape(consoleport.device),
  916. escape(consoleport.name),
  917. consoleserverport.device.get_absolute_url(),
  918. escape(consoleserverport.device),
  919. escape(consoleserverport.name),
  920. )
  921. messages.success(request, mark_safe(msg))
  922. return redirect('dcim:device', pk=consoleserverport.device.pk)
  923. return render(request, 'dcim/consoleserverport_connect.html', {
  924. 'consoleserverport': consoleserverport,
  925. 'form': form,
  926. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  927. })
  928. class ConsoleServerPortDisconnectView(PermissionRequiredMixin, View):
  929. permission_required = 'dcim.change_consoleserverport'
  930. def get(self, request, pk):
  931. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  932. form = ConfirmationForm()
  933. if not hasattr(consoleserverport, 'connected_console'):
  934. messages.warning(
  935. request,
  936. "Cannot disconnect console server port {}: Nothing is connected to it.".format(consoleserverport)
  937. )
  938. return redirect('dcim:device', pk=consoleserverport.device.pk)
  939. return render(request, 'dcim/consoleserverport_disconnect.html', {
  940. 'consoleserverport': consoleserverport,
  941. 'form': form,
  942. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  943. })
  944. def post(self, request, pk):
  945. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  946. form = ConfirmationForm(request.POST)
  947. if form.is_valid():
  948. consoleport = consoleserverport.connected_console
  949. consoleport.cs_port = None
  950. consoleport.connection_status = None
  951. consoleport.save()
  952. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  953. consoleport.device.get_absolute_url(),
  954. escape(consoleport.device),
  955. escape(consoleport.name),
  956. consoleserverport.device.get_absolute_url(),
  957. escape(consoleserverport.device),
  958. escape(consoleserverport.name),
  959. )
  960. messages.success(request, mark_safe(msg))
  961. return redirect('dcim:device', pk=consoleserverport.device.pk)
  962. return render(request, 'dcim/consoleserverport_disconnect.html', {
  963. 'consoleserverport': consoleserverport,
  964. 'form': form,
  965. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  966. })
  967. class ConsoleServerPortEditView(PermissionRequiredMixin, ObjectEditView):
  968. permission_required = 'dcim.change_consoleserverport'
  969. model = ConsoleServerPort
  970. model_form = forms.ConsoleServerPortForm
  971. class ConsoleServerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  972. permission_required = 'dcim.delete_consoleserverport'
  973. model = ConsoleServerPort
  974. class ConsoleServerPortBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  975. permission_required = 'dcim.change_consoleserverport'
  976. queryset = ConsoleServerPort.objects.all()
  977. form = forms.ConsoleServerPortBulkRenameForm
  978. class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  979. permission_required = 'dcim.change_consoleserverport'
  980. model = ConsoleServerPort
  981. form = forms.ConsoleServerPortBulkDisconnectForm
  982. def disconnect_objects(self, cs_ports):
  983. return ConsolePort.objects.filter(cs_port__in=cs_ports).update(cs_port=None, connection_status=None)
  984. class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  985. permission_required = 'dcim.delete_consoleserverport'
  986. queryset = ConsoleServerPort.objects.all()
  987. parent_model = Device
  988. table = tables.ConsoleServerPortTable
  989. #
  990. # Power ports
  991. #
  992. class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  993. permission_required = 'dcim.add_powerport'
  994. parent_model = Device
  995. parent_field = 'device'
  996. model = PowerPort
  997. form = forms.PowerPortCreateForm
  998. model_form = forms.PowerPortForm
  999. template_name = 'dcim/device_component_add.html'
  1000. class PowerPortConnectView(PermissionRequiredMixin, View):
  1001. permission_required = 'dcim.change_powerport'
  1002. def get(self, request, pk):
  1003. powerport = get_object_or_404(PowerPort, pk=pk)
  1004. form = forms.PowerPortConnectionForm(instance=powerport, initial={
  1005. 'site': request.GET.get('site'),
  1006. 'rack': request.GET.get('rack'),
  1007. 'pdu': request.GET.get('pdu'),
  1008. 'connection_status': CONNECTION_STATUS_CONNECTED,
  1009. })
  1010. return render(request, 'dcim/powerport_connect.html', {
  1011. 'powerport': powerport,
  1012. 'form': form,
  1013. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  1014. })
  1015. def post(self, request, pk):
  1016. powerport = get_object_or_404(PowerPort, pk=pk)
  1017. form = forms.PowerPortConnectionForm(request.POST, instance=powerport)
  1018. if form.is_valid():
  1019. powerport = form.save()
  1020. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  1021. powerport.device.get_absolute_url(),
  1022. escape(powerport.device),
  1023. escape(powerport.name),
  1024. powerport.power_outlet.device.get_absolute_url(),
  1025. escape(powerport.power_outlet.device),
  1026. escape(powerport.power_outlet.name),
  1027. )
  1028. messages.success(request, mark_safe(msg))
  1029. return redirect('dcim:device', pk=powerport.device.pk)
  1030. return render(request, 'dcim/powerport_connect.html', {
  1031. 'powerport': powerport,
  1032. 'form': form,
  1033. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  1034. })
  1035. class PowerPortDisconnectView(PermissionRequiredMixin, View):
  1036. permission_required = 'dcim.change_powerport'
  1037. def get(self, request, pk):
  1038. powerport = get_object_or_404(PowerPort, pk=pk)
  1039. form = ConfirmationForm()
  1040. if not powerport.power_outlet:
  1041. messages.warning(
  1042. request, "Cannot disconnect power port {}: It is not connected to an outlet.".format(powerport)
  1043. )
  1044. return redirect('dcim:device', pk=powerport.device.pk)
  1045. return render(request, 'dcim/powerport_disconnect.html', {
  1046. 'powerport': powerport,
  1047. 'form': form,
  1048. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  1049. })
  1050. def post(self, request, pk):
  1051. powerport = get_object_or_404(PowerPort, pk=pk)
  1052. form = ConfirmationForm(request.POST)
  1053. if form.is_valid():
  1054. power_outlet = powerport.power_outlet
  1055. powerport.power_outlet = None
  1056. powerport.connection_status = None
  1057. powerport.save()
  1058. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  1059. powerport.device.get_absolute_url(),
  1060. escape(powerport.device),
  1061. escape(powerport.name),
  1062. power_outlet.device.get_absolute_url(),
  1063. escape(power_outlet.device),
  1064. escape(power_outlet.name),
  1065. )
  1066. messages.success(request, mark_safe(msg))
  1067. return redirect('dcim:device', pk=powerport.device.pk)
  1068. return render(request, 'dcim/powerport_disconnect.html', {
  1069. 'powerport': powerport,
  1070. 'form': form,
  1071. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  1072. })
  1073. class PowerPortEditView(PermissionRequiredMixin, ObjectEditView):
  1074. permission_required = 'dcim.change_powerport'
  1075. model = PowerPort
  1076. model_form = forms.PowerPortForm
  1077. class PowerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1078. permission_required = 'dcim.delete_powerport'
  1079. model = PowerPort
  1080. class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1081. permission_required = 'dcim.delete_powerport'
  1082. queryset = PowerPort.objects.all()
  1083. parent_model = Device
  1084. table = tables.PowerPortTable
  1085. class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1086. permission_required = 'dcim.change_powerport'
  1087. model_form = forms.PowerConnectionCSVForm
  1088. table = tables.PowerConnectionTable
  1089. default_return_url = 'dcim:power_connections_list'
  1090. #
  1091. # Power outlets
  1092. #
  1093. class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView):
  1094. permission_required = 'dcim.add_poweroutlet'
  1095. parent_model = Device
  1096. parent_field = 'device'
  1097. model = PowerOutlet
  1098. form = forms.PowerOutletCreateForm
  1099. model_form = forms.PowerOutletForm
  1100. template_name = 'dcim/device_component_add.html'
  1101. class PowerOutletConnectView(PermissionRequiredMixin, View):
  1102. permission_required = 'dcim.change_poweroutlet'
  1103. def get(self, request, pk):
  1104. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1105. form = forms.PowerOutletConnectionForm(initial={
  1106. 'site': request.GET.get('site'),
  1107. 'rack': request.GET.get('rack'),
  1108. 'device': request.GET.get('device'),
  1109. 'connection_status': CONNECTION_STATUS_CONNECTED,
  1110. })
  1111. return render(request, 'dcim/poweroutlet_connect.html', {
  1112. 'poweroutlet': poweroutlet,
  1113. 'form': form,
  1114. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1115. })
  1116. def post(self, request, pk):
  1117. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1118. form = forms.PowerOutletConnectionForm(request.POST)
  1119. if form.is_valid():
  1120. powerport = form.cleaned_data['port']
  1121. powerport.power_outlet = poweroutlet
  1122. powerport.connection_status = form.cleaned_data['connection_status']
  1123. powerport.save()
  1124. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  1125. powerport.device.get_absolute_url(),
  1126. escape(powerport.device),
  1127. escape(powerport.name),
  1128. poweroutlet.device.get_absolute_url(),
  1129. escape(poweroutlet.device),
  1130. escape(poweroutlet.name),
  1131. )
  1132. messages.success(request, mark_safe(msg))
  1133. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1134. return render(request, 'dcim/poweroutlet_connect.html', {
  1135. 'poweroutlet': poweroutlet,
  1136. 'form': form,
  1137. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1138. })
  1139. class PowerOutletDisconnectView(PermissionRequiredMixin, View):
  1140. permission_required = 'dcim.change_poweroutlet'
  1141. def get(self, request, pk):
  1142. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1143. form = ConfirmationForm()
  1144. if not hasattr(poweroutlet, 'connected_port'):
  1145. messages.warning(
  1146. request, "Cannot disconnect power outlet {}: Nothing is connected to it.".format(poweroutlet)
  1147. )
  1148. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1149. return render(request, 'dcim/poweroutlet_disconnect.html', {
  1150. 'poweroutlet': poweroutlet,
  1151. 'form': form,
  1152. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1153. })
  1154. def post(self, request, pk):
  1155. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1156. form = ConfirmationForm(request.POST)
  1157. if form.is_valid():
  1158. powerport = poweroutlet.connected_port
  1159. powerport.power_outlet = None
  1160. powerport.connection_status = None
  1161. powerport.save()
  1162. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  1163. powerport.device.get_absolute_url(),
  1164. escape(powerport.device),
  1165. escape(powerport.name),
  1166. poweroutlet.device.get_absolute_url(),
  1167. escape(poweroutlet.device),
  1168. escape(poweroutlet.name),
  1169. )
  1170. messages.success(request, mark_safe(msg))
  1171. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1172. return render(request, 'dcim/poweroutlet_disconnect.html', {
  1173. 'poweroutlet': poweroutlet,
  1174. 'form': form,
  1175. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1176. })
  1177. class PowerOutletEditView(PermissionRequiredMixin, ObjectEditView):
  1178. permission_required = 'dcim.change_poweroutlet'
  1179. model = PowerOutlet
  1180. model_form = forms.PowerOutletForm
  1181. class PowerOutletDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1182. permission_required = 'dcim.delete_poweroutlet'
  1183. model = PowerOutlet
  1184. class PowerOutletBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1185. permission_required = 'dcim.change_poweroutlet'
  1186. queryset = PowerOutlet.objects.all()
  1187. form = forms.PowerOutletBulkRenameForm
  1188. class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1189. permission_required = 'dcim.change_poweroutlet'
  1190. model = PowerOutlet
  1191. form = forms.PowerOutletBulkDisconnectForm
  1192. def disconnect_objects(self, power_outlets):
  1193. return PowerPort.objects.filter(power_outlet__in=power_outlets).update(
  1194. power_outlet=None, connection_status=None
  1195. )
  1196. class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1197. permission_required = 'dcim.delete_poweroutlet'
  1198. queryset = PowerOutlet.objects.all()
  1199. parent_model = Device
  1200. table = tables.PowerOutletTable
  1201. #
  1202. # Interfaces
  1203. #
  1204. class InterfaceView(View):
  1205. def get(self, request, pk):
  1206. interface = get_object_or_404(Interface, pk=pk)
  1207. # Get connected interface
  1208. connected_interface = interface.connected_interface
  1209. if connected_interface is None and hasattr(interface, 'circuit_termination'):
  1210. peer_termination = interface.circuit_termination.get_peer_termination()
  1211. if peer_termination is not None:
  1212. connected_interface = peer_termination.interface
  1213. # Get assigned IP addresses
  1214. ipaddress_table = InterfaceIPAddressTable(
  1215. data=interface.ip_addresses.select_related('vrf', 'tenant'),
  1216. orderable=False
  1217. )
  1218. # Get assigned VLANs and annotate whether each is tagged or untagged
  1219. vlans = []
  1220. if interface.untagged_vlan is not None:
  1221. vlans.append(interface.untagged_vlan)
  1222. vlans[0].tagged = False
  1223. for vlan in interface.tagged_vlans.select_related('site', 'group', 'tenant', 'role'):
  1224. vlan.tagged = True
  1225. vlans.append(vlan)
  1226. vlan_table = InterfaceVLANTable(
  1227. interface=interface,
  1228. data=vlans,
  1229. orderable=False
  1230. )
  1231. return render(request, 'dcim/interface.html', {
  1232. 'interface': interface,
  1233. 'connected_interface': connected_interface,
  1234. 'ipaddress_table': ipaddress_table,
  1235. 'vlan_table': vlan_table,
  1236. })
  1237. class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
  1238. permission_required = 'dcim.add_interface'
  1239. parent_model = Device
  1240. parent_field = 'device'
  1241. model = Interface
  1242. form = forms.InterfaceCreateForm
  1243. model_form = forms.InterfaceForm
  1244. template_name = 'dcim/device_component_add.html'
  1245. class InterfaceEditView(PermissionRequiredMixin, ObjectEditView):
  1246. permission_required = 'dcim.change_interface'
  1247. model = Interface
  1248. model_form = forms.InterfaceForm
  1249. template_name = 'dcim/interface_edit.html'
  1250. class InterfaceAssignVLANsView(PermissionRequiredMixin, ObjectEditView):
  1251. permission_required = 'dcim.change_interface'
  1252. model = Interface
  1253. model_form = forms.InterfaceAssignVLANsForm
  1254. class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1255. permission_required = 'dcim.delete_interface'
  1256. model = Interface
  1257. class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1258. permission_required = 'dcim.change_interface'
  1259. model = Interface
  1260. form = forms.InterfaceBulkDisconnectForm
  1261. def disconnect_objects(self, interfaces):
  1262. count, _ = InterfaceConnection.objects.filter(
  1263. Q(interface_a__in=interfaces) | Q(interface_b__in=interfaces)
  1264. ).delete()
  1265. return count
  1266. class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
  1267. permission_required = 'dcim.change_interface'
  1268. queryset = Interface.objects.all()
  1269. parent_model = Device
  1270. table = tables.InterfaceTable
  1271. form = forms.InterfaceBulkEditForm
  1272. class InterfaceBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1273. permission_required = 'dcim.change_interface'
  1274. queryset = Interface.objects.order_naturally()
  1275. form = forms.InterfaceBulkRenameForm
  1276. class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1277. permission_required = 'dcim.delete_interface'
  1278. queryset = Interface.objects.all()
  1279. parent_model = Device
  1280. table = tables.InterfaceTable
  1281. #
  1282. # Device bays
  1283. #
  1284. class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView):
  1285. permission_required = 'dcim.add_devicebay'
  1286. parent_model = Device
  1287. parent_field = 'device'
  1288. model = DeviceBay
  1289. form = forms.DeviceBayCreateForm
  1290. model_form = forms.DeviceBayForm
  1291. template_name = 'dcim/device_component_add.html'
  1292. class DeviceBayEditView(PermissionRequiredMixin, ObjectEditView):
  1293. permission_required = 'dcim.change_devicebay'
  1294. model = DeviceBay
  1295. model_form = forms.DeviceBayForm
  1296. class DeviceBayDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1297. permission_required = 'dcim.delete_devicebay'
  1298. model = DeviceBay
  1299. class DeviceBayPopulateView(PermissionRequiredMixin, View):
  1300. permission_required = 'dcim.change_devicebay'
  1301. def get(self, request, pk):
  1302. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1303. form = forms.PopulateDeviceBayForm(device_bay)
  1304. return render(request, 'dcim/devicebay_populate.html', {
  1305. 'device_bay': device_bay,
  1306. 'form': form,
  1307. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1308. })
  1309. def post(self, request, pk):
  1310. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1311. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  1312. if form.is_valid():
  1313. device_bay.installed_device = form.cleaned_data['installed_device']
  1314. device_bay.save()
  1315. messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
  1316. return redirect('dcim:device', pk=device_bay.device.pk)
  1317. return render(request, 'dcim/devicebay_populate.html', {
  1318. 'device_bay': device_bay,
  1319. 'form': form,
  1320. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1321. })
  1322. class DeviceBayDepopulateView(PermissionRequiredMixin, View):
  1323. permission_required = 'dcim.change_devicebay'
  1324. def get(self, request, pk):
  1325. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1326. form = ConfirmationForm()
  1327. return render(request, 'dcim/devicebay_depopulate.html', {
  1328. 'device_bay': device_bay,
  1329. 'form': form,
  1330. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1331. })
  1332. def post(self, request, pk):
  1333. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1334. form = ConfirmationForm(request.POST)
  1335. if form.is_valid():
  1336. removed_device = device_bay.installed_device
  1337. device_bay.installed_device = None
  1338. device_bay.save()
  1339. messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay))
  1340. return redirect('dcim:device', pk=device_bay.device.pk)
  1341. return render(request, 'dcim/devicebay_depopulate.html', {
  1342. 'device_bay': device_bay,
  1343. 'form': form,
  1344. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1345. })
  1346. class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView):
  1347. permission_required = 'dcim.change_devicebay'
  1348. queryset = DeviceBay.objects.all()
  1349. form = forms.DeviceBayBulkRenameForm
  1350. class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1351. permission_required = 'dcim.delete_devicebay'
  1352. queryset = DeviceBay.objects.all()
  1353. parent_model = Device
  1354. table = tables.DeviceBayTable
  1355. #
  1356. # Bulk Device component creation
  1357. #
  1358. class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateView):
  1359. permission_required = 'dcim.add_consoleport'
  1360. parent_model = Device
  1361. parent_field = 'device'
  1362. form = forms.DeviceBulkAddComponentForm
  1363. model = ConsolePort
  1364. model_form = forms.ConsolePortForm
  1365. table = tables.DeviceTable
  1366. default_return_url = 'dcim:device_list'
  1367. class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1368. permission_required = 'dcim.add_consoleserverport'
  1369. parent_model = Device
  1370. parent_field = 'device'
  1371. form = forms.DeviceBulkAddComponentForm
  1372. model = ConsoleServerPort
  1373. model_form = forms.ConsoleServerPortForm
  1374. table = tables.DeviceTable
  1375. default_return_url = 'dcim:device_list'
  1376. class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1377. permission_required = 'dcim.add_powerport'
  1378. parent_model = Device
  1379. parent_field = 'device'
  1380. form = forms.DeviceBulkAddComponentForm
  1381. model = PowerPort
  1382. model_form = forms.PowerPortForm
  1383. table = tables.DeviceTable
  1384. default_return_url = 'dcim:device_list'
  1385. class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateView):
  1386. permission_required = 'dcim.add_poweroutlet'
  1387. parent_model = Device
  1388. parent_field = 'device'
  1389. form = forms.DeviceBulkAddComponentForm
  1390. model = PowerOutlet
  1391. model_form = forms.PowerOutletForm
  1392. table = tables.DeviceTable
  1393. default_return_url = 'dcim:device_list'
  1394. class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView):
  1395. permission_required = 'dcim.add_interface'
  1396. parent_model = Device
  1397. parent_field = 'device'
  1398. form = forms.DeviceBulkAddInterfaceForm
  1399. model = Interface
  1400. model_form = forms.InterfaceForm
  1401. table = tables.DeviceTable
  1402. default_return_url = 'dcim:device_list'
  1403. class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateView):
  1404. permission_required = 'dcim.add_devicebay'
  1405. parent_model = Device
  1406. parent_field = 'device'
  1407. form = forms.DeviceBulkAddComponentForm
  1408. model = DeviceBay
  1409. model_form = forms.DeviceBayForm
  1410. table = tables.DeviceTable
  1411. default_return_url = 'dcim:device_list'
  1412. #
  1413. # Interface connections
  1414. #
  1415. class InterfaceConnectionAddView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1416. permission_required = 'dcim.add_interfaceconnection'
  1417. default_return_url = 'dcim:device_list'
  1418. def get(self, request, pk):
  1419. device = get_object_or_404(Device, pk=pk)
  1420. form = forms.InterfaceConnectionForm(device, initial={
  1421. 'interface_a': request.GET.get('interface_a'),
  1422. 'site_b': request.GET.get('site_b'),
  1423. 'rack_b': request.GET.get('rack_b'),
  1424. 'device_b': request.GET.get('device_b'),
  1425. 'interface_b': request.GET.get('interface_b'),
  1426. })
  1427. return render(request, 'dcim/interfaceconnection_edit.html', {
  1428. 'device': device,
  1429. 'form': form,
  1430. 'return_url': device.get_absolute_url(),
  1431. })
  1432. def post(self, request, pk):
  1433. device = get_object_or_404(Device, pk=pk)
  1434. form = forms.InterfaceConnectionForm(device, request.POST)
  1435. if form.is_valid():
  1436. interfaceconnection = form.save()
  1437. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  1438. interfaceconnection.interface_a.device.get_absolute_url(),
  1439. escape(interfaceconnection.interface_a.device),
  1440. escape(interfaceconnection.interface_a.name),
  1441. interfaceconnection.interface_b.device.get_absolute_url(),
  1442. escape(interfaceconnection.interface_b.device),
  1443. escape(interfaceconnection.interface_b.name),
  1444. )
  1445. messages.success(request, mark_safe(msg))
  1446. if '_addanother' in request.POST:
  1447. base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
  1448. device_b = interfaceconnection.interface_b.device
  1449. params = urlencode({
  1450. 'rack_b': device_b.rack.pk if device_b.rack else '',
  1451. 'device_b': device_b.pk,
  1452. })
  1453. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  1454. else:
  1455. return redirect('dcim:device', pk=device.pk)
  1456. return render(request, 'dcim/interfaceconnection_edit.html', {
  1457. 'device': device,
  1458. 'form': form,
  1459. 'return_url': device.get_absolute_url(),
  1460. })
  1461. class InterfaceConnectionDeleteView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1462. permission_required = 'dcim.delete_interfaceconnection'
  1463. default_return_url = 'dcim:device_list'
  1464. def get(self, request, pk):
  1465. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1466. form = forms.ConfirmationForm()
  1467. return render(request, 'dcim/interfaceconnection_delete.html', {
  1468. 'interfaceconnection': interfaceconnection,
  1469. 'form': form,
  1470. 'return_url': self.get_return_url(request, interfaceconnection),
  1471. })
  1472. def post(self, request, pk):
  1473. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1474. form = forms.ConfirmationForm(request.POST)
  1475. if form.is_valid():
  1476. interfaceconnection.delete()
  1477. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  1478. interfaceconnection.interface_a.device.get_absolute_url(),
  1479. escape(interfaceconnection.interface_a.device),
  1480. escape(interfaceconnection.interface_a.name),
  1481. interfaceconnection.interface_b.device.get_absolute_url(),
  1482. escape(interfaceconnection.interface_b.device),
  1483. escape(interfaceconnection.interface_b.name),
  1484. )
  1485. messages.success(request, mark_safe(msg))
  1486. return redirect(self.get_return_url(request, interfaceconnection))
  1487. return render(request, 'dcim/interfaceconnection_delete.html', {
  1488. 'interfaceconnection': interfaceconnection,
  1489. 'form': form,
  1490. 'return_url': self.get_return_url(request, interfaceconnection),
  1491. })
  1492. class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1493. permission_required = 'dcim.change_interface'
  1494. model_form = forms.InterfaceConnectionCSVForm
  1495. table = tables.InterfaceConnectionTable
  1496. default_return_url = 'dcim:interface_connections_list'
  1497. #
  1498. # Connections
  1499. #
  1500. class ConsoleConnectionsListView(ObjectListView):
  1501. queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False) \
  1502. .order_by('cs_port__device__name', 'cs_port__name')
  1503. filter = filters.ConsoleConnectionFilter
  1504. filter_form = forms.ConsoleConnectionFilterForm
  1505. table = tables.ConsoleConnectionTable
  1506. template_name = 'dcim/console_connections_list.html'
  1507. class PowerConnectionsListView(ObjectListView):
  1508. queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False) \
  1509. .order_by('power_outlet__device__name', 'power_outlet__name')
  1510. filter = filters.PowerConnectionFilter
  1511. filter_form = forms.PowerConnectionFilterForm
  1512. table = tables.PowerConnectionTable
  1513. template_name = 'dcim/power_connections_list.html'
  1514. class InterfaceConnectionsListView(ObjectListView):
  1515. queryset = InterfaceConnection.objects.select_related(
  1516. 'interface_a__device', 'interface_b__device'
  1517. ).order_by(
  1518. 'interface_a__device__name', 'interface_a__name'
  1519. )
  1520. filter = filters.InterfaceConnectionFilter
  1521. filter_form = forms.InterfaceConnectionFilterForm
  1522. table = tables.InterfaceConnectionTable
  1523. template_name = 'dcim/interface_connections_list.html'
  1524. #
  1525. # Inventory items
  1526. #
  1527. class InventoryItemListView(ObjectListView):
  1528. queryset = InventoryItem.objects.select_related('device', 'manufacturer')
  1529. filter = filters.InventoryItemFilter
  1530. filter_form = forms.InventoryItemFilterForm
  1531. table = tables.InventoryItemTable
  1532. template_name = 'dcim/inventoryitem_list.html'
  1533. class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView):
  1534. permission_required = 'dcim.change_inventoryitem'
  1535. model = InventoryItem
  1536. model_form = forms.InventoryItemForm
  1537. def alter_obj(self, obj, request, url_args, url_kwargs):
  1538. if 'device' in url_kwargs:
  1539. obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
  1540. return obj
  1541. def get_return_url(self, request, obj):
  1542. return reverse('dcim:device_inventory', kwargs={'pk': obj.device.pk})
  1543. class InventoryItemDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1544. permission_required = 'dcim.delete_inventoryitem'
  1545. model = InventoryItem
  1546. class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView):
  1547. permission_required = 'dcim.add_inventoryitem'
  1548. model_form = forms.InventoryItemCSVForm
  1549. table = tables.InventoryItemTable
  1550. default_return_url = 'dcim:inventoryitem_list'
  1551. class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView):
  1552. permission_required = 'dcim.change_inventoryitem'
  1553. queryset = InventoryItem.objects.select_related('device', 'manufacturer')
  1554. filter = filters.InventoryItemFilter
  1555. table = tables.InventoryItemTable
  1556. form = forms.InventoryItemBulkEditForm
  1557. default_return_url = 'dcim:inventoryitem_list'
  1558. class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1559. permission_required = 'dcim.delete_inventoryitem'
  1560. queryset = InventoryItem.objects.select_related('device', 'manufacturer')
  1561. table = tables.InventoryItemTable
  1562. template_name = 'dcim/inventoryitem_bulk_delete.html'
  1563. default_return_url = 'dcim:inventoryitem_list'
  1564. #
  1565. # Virtual chassis
  1566. #
  1567. class VirtualChassisListView(ObjectListView):
  1568. queryset = VirtualChassis.objects.select_related('master').annotate(member_count=Count('members'))
  1569. table = tables.VirtualChassisTable
  1570. filter = filters.VirtualChassisFilter
  1571. filter_form = forms.VirtualChassisFilterForm
  1572. template_name = 'dcim/virtualchassis_list.html'
  1573. class VirtualChassisCreateView(PermissionRequiredMixin, View):
  1574. permission_required = 'dcim.add_virtualchassis'
  1575. def post(self, request):
  1576. # Get the list of devices being added to a VirtualChassis
  1577. pk_form = forms.DeviceSelectionForm(request.POST)
  1578. pk_form.full_clean()
  1579. if not pk_form.cleaned_data.get('pk'):
  1580. messages.warning(request, "No devices were selected.")
  1581. return redirect('dcim:device_list')
  1582. device_queryset = Device.objects.filter(
  1583. pk__in=pk_form.cleaned_data.get('pk')
  1584. ).select_related('rack').order_by('vc_position')
  1585. VCMemberFormSet = modelformset_factory(
  1586. model=Device,
  1587. formset=forms.BaseVCMemberFormSet,
  1588. form=forms.DeviceVCMembershipForm,
  1589. extra=0
  1590. )
  1591. if '_create' in request.POST:
  1592. vc_form = forms.VirtualChassisForm(request.POST)
  1593. vc_form.fields['master'].queryset = device_queryset
  1594. formset = VCMemberFormSet(request.POST, queryset=device_queryset)
  1595. if vc_form.is_valid() and formset.is_valid():
  1596. with transaction.atomic():
  1597. # Assign each device to the VirtualChassis before saving
  1598. virtual_chassis = vc_form.save()
  1599. devices = formset.save(commit=False)
  1600. for device in devices:
  1601. device.virtual_chassis = virtual_chassis
  1602. device.save()
  1603. return redirect(vc_form.cleaned_data['master'].get_absolute_url())
  1604. else:
  1605. vc_form = forms.VirtualChassisForm()
  1606. vc_form.fields['master'].queryset = device_queryset
  1607. formset = VCMemberFormSet(queryset=device_queryset)
  1608. return render(request, 'dcim/virtualchassis_edit.html', {
  1609. 'pk_form': pk_form,
  1610. 'vc_form': vc_form,
  1611. 'formset': formset,
  1612. 'return_url': reverse('dcim:device_list'),
  1613. })
  1614. class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1615. permission_required = 'dcim.change_virtualchassis'
  1616. def get(self, request, pk):
  1617. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1618. VCMemberFormSet = modelformset_factory(
  1619. model=Device,
  1620. form=forms.DeviceVCMembershipForm,
  1621. formset=forms.BaseVCMemberFormSet,
  1622. extra=0
  1623. )
  1624. members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position')
  1625. vc_form = forms.VirtualChassisForm(instance=virtual_chassis)
  1626. vc_form.fields['master'].queryset = members_queryset
  1627. formset = VCMemberFormSet(queryset=members_queryset)
  1628. return render(request, 'dcim/virtualchassis_edit.html', {
  1629. 'vc_form': vc_form,
  1630. 'formset': formset,
  1631. 'return_url': self.get_return_url(request, virtual_chassis),
  1632. })
  1633. def post(self, request, pk):
  1634. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1635. VCMemberFormSet = modelformset_factory(
  1636. model=Device,
  1637. form=forms.DeviceVCMembershipForm,
  1638. formset=forms.BaseVCMemberFormSet,
  1639. extra=0
  1640. )
  1641. members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position')
  1642. vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis)
  1643. vc_form.fields['master'].queryset = members_queryset
  1644. formset = VCMemberFormSet(request.POST, queryset=members_queryset)
  1645. if vc_form.is_valid() and formset.is_valid():
  1646. with transaction.atomic():
  1647. # Save the VirtualChassis
  1648. vc_form.save()
  1649. # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on
  1650. # duplicate positions. Then save each member instance.
  1651. members = formset.save(commit=False)
  1652. Device.objects.filter(pk__in=[m.pk for m in members]).update(vc_position=None)
  1653. for member in members:
  1654. member.save()
  1655. return redirect(vc_form.cleaned_data['master'].get_absolute_url())
  1656. return render(request, 'dcim/virtualchassis_edit.html', {
  1657. 'vc_form': vc_form,
  1658. 'formset': formset,
  1659. 'return_url': self.get_return_url(request, virtual_chassis),
  1660. })
  1661. class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  1662. permission_required = 'dcim.delete_virtualchassis'
  1663. model = VirtualChassis
  1664. default_return_url = 'dcim:device_list'
  1665. class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1666. permission_required = 'dcim.change_virtualchassis'
  1667. def get(self, request, pk):
  1668. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1669. initial_data = {k: request.GET[k] for k in request.GET}
  1670. member_select_form = forms.VCMemberSelectForm(initial=initial_data)
  1671. membership_form = forms.DeviceVCMembershipForm(initial=initial_data)
  1672. return render(request, 'dcim/virtualchassis_add_member.html', {
  1673. 'virtual_chassis': virtual_chassis,
  1674. 'member_select_form': member_select_form,
  1675. 'membership_form': membership_form,
  1676. 'return_url': self.get_return_url(request, virtual_chassis),
  1677. })
  1678. def post(self, request, pk):
  1679. virtual_chassis = get_object_or_404(VirtualChassis, pk=pk)
  1680. member_select_form = forms.VCMemberSelectForm(request.POST)
  1681. if member_select_form.is_valid():
  1682. device = member_select_form.cleaned_data['device']
  1683. device.virtual_chassis = virtual_chassis
  1684. data = {k: request.POST[k] for k in ['vc_position', 'vc_priority']}
  1685. membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device)
  1686. if membership_form.is_valid():
  1687. membership_form.save()
  1688. msg = 'Added member <a href="{}">{}</a>'.format(device.get_absolute_url(), escape(device))
  1689. messages.success(request, mark_safe(msg))
  1690. if '_addanother' in request.POST:
  1691. return redirect(request.get_full_path())
  1692. return redirect(self.get_return_url(request, device))
  1693. else:
  1694. membership_form = forms.DeviceVCMembershipForm(data=request.POST)
  1695. return render(request, 'dcim/virtualchassis_add_member.html', {
  1696. 'virtual_chassis': virtual_chassis,
  1697. 'member_select_form': member_select_form,
  1698. 'membership_form': membership_form,
  1699. 'return_url': self.get_return_url(request, virtual_chassis),
  1700. })
  1701. class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, View):
  1702. permission_required = 'dcim.change_virtualchassis'
  1703. def get(self, request, pk):
  1704. device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
  1705. form = ConfirmationForm(initial=request.GET)
  1706. return render(request, 'dcim/virtualchassis_remove_member.html', {
  1707. 'device': device,
  1708. 'form': form,
  1709. 'return_url': self.get_return_url(request, device),
  1710. })
  1711. def post(self, request, pk):
  1712. device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False)
  1713. form = ConfirmationForm(request.POST)
  1714. # Protect master device from being removed
  1715. virtual_chassis = VirtualChassis.objects.filter(master=device).first()
  1716. if virtual_chassis is not None:
  1717. msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device))
  1718. messages.error(request, mark_safe(msg))
  1719. return redirect(device.get_absolute_url())
  1720. if form.is_valid():
  1721. Device.objects.filter(pk=device.pk).update(
  1722. virtual_chassis=None,
  1723. vc_position=None,
  1724. vc_priority=None
  1725. )
  1726. msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis)
  1727. messages.success(request, msg)
  1728. return redirect(self.get_return_url(request, device))
  1729. return render(request, 'dcim/virtualchassis_remove_member.html', {
  1730. 'device': device,
  1731. 'form': form,
  1732. 'return_url': self.get_return_url(request, device),
  1733. })