views.py 90 KB

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