views.py 79 KB

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