views.py 92 KB

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