views.py 88 KB

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