views.py 75 KB

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