2
0

views.py 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842
  1. from __future__ import unicode_literals
  2. import re
  3. from natsort import natsorted
  4. from operator import attrgetter
  5. from django.contrib import messages
  6. from django.contrib.auth.decorators import permission_required
  7. from django.contrib.auth.mixins import PermissionRequiredMixin
  8. from django.core.paginator import EmptyPage, PageNotAnInteger
  9. from django.db.models import Count, Q
  10. from django.http import HttpResponseRedirect
  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 urlencode
  15. from django.utils.safestring import mark_safe
  16. from django.views.generic import View
  17. from ipam.models import Prefix, Service, VLAN
  18. from circuits.models import Circuit
  19. from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE, UserAction
  20. from utilities.forms import ConfirmationForm
  21. from utilities.paginator import EnhancedPaginator
  22. from utilities.views import (
  23. BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, ComponentDeleteView,
  24. ComponentEditView, ObjectDeleteView, ObjectEditView, ObjectListView,
  25. )
  26. from . import filters, forms, tables
  27. from .models import (
  28. CONNECTION_STATUS_CONNECTED, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device,
  29. DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, Interface, InterfaceConnection, InterfaceTemplate,
  30. Manufacturer, InventoryItem, Platform, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack,
  31. RackGroup, RackReservation, RackRole, Region, Site,
  32. )
  33. EXPANSION_PATTERN = '\[(\d+-\d+)\]'
  34. def xstr(s):
  35. """
  36. Replace None with an empty string (for CSV export)
  37. """
  38. return '' if s is None else str(s)
  39. def expand_pattern(string):
  40. """
  41. Expand a numeric pattern into a list of strings. Examples:
  42. 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3']
  43. 'xe-0/[0-3]/[0-7]' => ['xe-0/0/0', 'xe-0/0/1', 'xe-0/0/2', ... 'xe-0/3/5', 'xe-0/3/6', 'xe-0/3/7']
  44. """
  45. lead, pattern, remnant = re.split(EXPANSION_PATTERN, string, maxsplit=1)
  46. x, y = pattern.split('-')
  47. for i in range(int(x), int(y) + 1):
  48. if remnant:
  49. for string in expand_pattern(remnant):
  50. yield "{0}{1}{2}".format(lead, i, string)
  51. else:
  52. yield "{0}{1}".format(lead, i)
  53. class BulkDisconnectView(View):
  54. """
  55. An extendable view for disconnection console/power/interface components in bulk.
  56. """
  57. model = None
  58. form = None
  59. template_name = 'dcim/bulk_disconnect.html'
  60. def disconnect_objects(self, objects):
  61. raise NotImplementedError()
  62. def post(self, request, pk):
  63. device = get_object_or_404(Device, pk=pk)
  64. selected_objects = []
  65. if '_confirm' in request.POST:
  66. form = self.form(request.POST)
  67. if form.is_valid():
  68. count = self.disconnect_objects(form.cleaned_data['pk'])
  69. messages.success(request, "Disconnected {} {} on {}".format(
  70. count, self.model._meta.verbose_name_plural, device
  71. ))
  72. return redirect(device.get_absolute_url())
  73. else:
  74. form = self.form(initial={'pk': request.POST.getlist('pk')})
  75. selected_objects = self.model.objects.filter(pk__in=form.initial['pk'])
  76. return render(request, self.template_name, {
  77. 'form': form,
  78. 'device': device,
  79. 'obj_type_plural': self.model._meta.verbose_name_plural,
  80. 'selected_objects': selected_objects,
  81. 'return_url': device.get_absolute_url(),
  82. })
  83. #
  84. # Regions
  85. #
  86. class RegionListView(ObjectListView):
  87. queryset = Region.objects.annotate(site_count=Count('sites'))
  88. table = tables.RegionTable
  89. template_name = 'dcim/region_list.html'
  90. class RegionCreateView(PermissionRequiredMixin, ObjectEditView):
  91. permission_required = 'dcim.add_region'
  92. model = Region
  93. model_form = forms.RegionForm
  94. def get_return_url(self, request, obj):
  95. return reverse('dcim:region_list')
  96. class RegionEditView(RegionCreateView):
  97. permission_required = 'dcim.change_region'
  98. class RegionBulkImportView(PermissionRequiredMixin, BulkImportView):
  99. permission_required = 'dcim.add_region'
  100. model_form = forms.RegionCSVForm
  101. table = tables.RegionTable
  102. default_return_url = 'dcim:region_list'
  103. class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  104. permission_required = 'dcim.delete_region'
  105. cls = Region
  106. queryset = Region.objects.annotate(site_count=Count('sites'))
  107. table = tables.RegionTable
  108. default_return_url = 'dcim:region_list'
  109. #
  110. # Sites
  111. #
  112. class SiteListView(ObjectListView):
  113. queryset = Site.objects.select_related('region', 'tenant')
  114. filter = filters.SiteFilter
  115. filter_form = forms.SiteFilterForm
  116. table = tables.SiteDetailTable
  117. template_name = 'dcim/site_list.html'
  118. class SiteView(View):
  119. def get(self, request, slug):
  120. site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug)
  121. stats = {
  122. 'rack_count': Rack.objects.filter(site=site).count(),
  123. 'device_count': Device.objects.filter(site=site).count(),
  124. 'prefix_count': Prefix.objects.filter(site=site).count(),
  125. 'vlan_count': VLAN.objects.filter(site=site).count(),
  126. 'circuit_count': Circuit.objects.filter(terminations__site=site).count(),
  127. }
  128. rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
  129. topology_maps = TopologyMap.objects.filter(site=site)
  130. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists()
  131. return render(request, 'dcim/site.html', {
  132. 'site': site,
  133. 'stats': stats,
  134. 'rack_groups': rack_groups,
  135. 'topology_maps': topology_maps,
  136. 'show_graphs': show_graphs,
  137. })
  138. class SiteCreateView(PermissionRequiredMixin, ObjectEditView):
  139. permission_required = 'dcim.add_site'
  140. model = Site
  141. model_form = forms.SiteForm
  142. template_name = 'dcim/site_edit.html'
  143. default_return_url = 'dcim:site_list'
  144. class SiteEditView(SiteCreateView):
  145. permission_required = 'dcim.change_site'
  146. class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  147. permission_required = 'dcim.delete_site'
  148. model = Site
  149. default_return_url = 'dcim:site_list'
  150. class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
  151. permission_required = 'dcim.add_site'
  152. model_form = forms.SiteCSVForm
  153. table = tables.SiteTable
  154. default_return_url = 'dcim:site_list'
  155. class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
  156. permission_required = 'dcim.change_site'
  157. cls = Site
  158. queryset = Site.objects.select_related('region', 'tenant')
  159. filter = filters.SiteFilter
  160. table = tables.SiteTable
  161. form = forms.SiteBulkEditForm
  162. default_return_url = 'dcim:site_list'
  163. #
  164. # Rack groups
  165. #
  166. class RackGroupListView(ObjectListView):
  167. queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
  168. filter = filters.RackGroupFilter
  169. filter_form = forms.RackGroupFilterForm
  170. table = tables.RackGroupTable
  171. template_name = 'dcim/rackgroup_list.html'
  172. class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView):
  173. permission_required = 'dcim.add_rackgroup'
  174. model = RackGroup
  175. model_form = forms.RackGroupForm
  176. def get_return_url(self, request, obj):
  177. return reverse('dcim:rackgroup_list')
  178. class RackGroupEditView(RackGroupCreateView):
  179. permission_required = 'dcim.change_rackgroup'
  180. class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView):
  181. permission_required = 'dcim.add_rackgroup'
  182. model_form = forms.RackGroupCSVForm
  183. table = tables.RackGroupTable
  184. default_return_url = 'dcim:rackgroup_list'
  185. class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  186. permission_required = 'dcim.delete_rackgroup'
  187. cls = RackGroup
  188. queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks'))
  189. filter = filters.RackGroupFilter
  190. table = tables.RackGroupTable
  191. default_return_url = 'dcim:rackgroup_list'
  192. #
  193. # Rack roles
  194. #
  195. class RackRoleListView(ObjectListView):
  196. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  197. table = tables.RackRoleTable
  198. template_name = 'dcim/rackrole_list.html'
  199. class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  200. permission_required = 'dcim.add_rackrole'
  201. model = RackRole
  202. model_form = forms.RackRoleForm
  203. def get_return_url(self, request, obj):
  204. return reverse('dcim:rackrole_list')
  205. class RackRoleEditView(RackRoleCreateView):
  206. permission_required = 'dcim.change_rackrole'
  207. class RackRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  208. permission_required = 'dcim.add_rackrole'
  209. model_form = forms.RackRoleCSVForm
  210. table = tables.RackRoleTable
  211. default_return_url = 'dcim:rackrole_list'
  212. class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  213. permission_required = 'dcim.delete_rackrole'
  214. cls = RackRole
  215. queryset = RackRole.objects.annotate(rack_count=Count('racks'))
  216. table = tables.RackRoleTable
  217. default_return_url = 'dcim:rackrole_list'
  218. #
  219. # Racks
  220. #
  221. class RackListView(ObjectListView):
  222. queryset = Rack.objects.select_related(
  223. 'site', 'group', 'tenant', 'role'
  224. ).prefetch_related(
  225. 'devices__device_type'
  226. ).annotate(
  227. device_count=Count('devices', distinct=True)
  228. )
  229. filter = filters.RackFilter
  230. filter_form = forms.RackFilterForm
  231. table = tables.RackDetailTable
  232. template_name = 'dcim/rack_list.html'
  233. class RackElevationListView(View):
  234. """
  235. Display a set of rack elevations side-by-side.
  236. """
  237. def get(self, request):
  238. racks = Rack.objects.select_related(
  239. 'site', 'group', 'tenant', 'role'
  240. ).prefetch_related(
  241. 'devices__device_type'
  242. )
  243. racks = filters.RackFilter(request.GET, racks).qs
  244. total_count = racks.count()
  245. # Pagination
  246. paginator = EnhancedPaginator(racks, 25)
  247. page_number = request.GET.get('page', 1)
  248. try:
  249. page = paginator.page(page_number)
  250. except PageNotAnInteger:
  251. page = paginator.page(1)
  252. except EmptyPage:
  253. page = paginator.page(paginator.num_pages)
  254. # Determine rack face
  255. if request.GET.get('face') == '1':
  256. face_id = 1
  257. else:
  258. face_id = 0
  259. return render(request, 'dcim/rack_elevation_list.html', {
  260. 'paginator': paginator,
  261. 'page': page,
  262. 'total_count': total_count,
  263. 'face_id': face_id,
  264. 'filter_form': forms.RackFilterForm(request.GET),
  265. })
  266. class RackView(View):
  267. def get(self, request, pk):
  268. rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk)
  269. nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True)\
  270. .select_related('device_type__manufacturer')
  271. next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
  272. prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
  273. reservations = RackReservation.objects.filter(rack=rack)
  274. return render(request, 'dcim/rack.html', {
  275. 'rack': rack,
  276. 'reservations': reservations,
  277. 'nonracked_devices': nonracked_devices,
  278. 'next_rack': next_rack,
  279. 'prev_rack': prev_rack,
  280. 'front_elevation': rack.get_front_elevation(),
  281. 'rear_elevation': rack.get_rear_elevation(),
  282. })
  283. class RackCreateView(PermissionRequiredMixin, ObjectEditView):
  284. permission_required = 'dcim.add_rack'
  285. model = Rack
  286. model_form = forms.RackForm
  287. template_name = 'dcim/rack_edit.html'
  288. default_return_url = 'dcim:rack_list'
  289. class RackEditView(RackCreateView):
  290. permission_required = 'dcim.change_rack'
  291. class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  292. permission_required = 'dcim.delete_rack'
  293. model = Rack
  294. default_return_url = 'dcim:rack_list'
  295. class RackBulkImportView(PermissionRequiredMixin, BulkImportView):
  296. permission_required = 'dcim.add_rack'
  297. model_form = forms.RackCSVForm
  298. table = tables.RackImportTable
  299. default_return_url = 'dcim:rack_list'
  300. class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
  301. permission_required = 'dcim.change_rack'
  302. cls = Rack
  303. queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role')
  304. filter = filters.RackFilter
  305. table = tables.RackTable
  306. form = forms.RackBulkEditForm
  307. default_return_url = 'dcim:rack_list'
  308. class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  309. permission_required = 'dcim.delete_rack'
  310. cls = Rack
  311. queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role')
  312. filter = filters.RackFilter
  313. table = tables.RackTable
  314. default_return_url = 'dcim:rack_list'
  315. #
  316. # Rack reservations
  317. #
  318. class RackReservationListView(ObjectListView):
  319. queryset = RackReservation.objects.all()
  320. filter = filters.RackReservationFilter
  321. filter_form = forms.RackReservationFilterForm
  322. table = tables.RackReservationTable
  323. template_name = 'dcim/rackreservation_list.html'
  324. class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView):
  325. permission_required = 'dcim.add_rackreservation'
  326. model = RackReservation
  327. model_form = forms.RackReservationForm
  328. def alter_obj(self, obj, request, args, kwargs):
  329. if not obj.pk:
  330. obj.rack = get_object_or_404(Rack, pk=kwargs['rack'])
  331. obj.user = request.user
  332. return obj
  333. def get_return_url(self, request, obj):
  334. return obj.rack.get_absolute_url()
  335. class RackReservationEditView(RackReservationCreateView):
  336. permission_required = 'dcim.change_rackreservation'
  337. class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  338. permission_required = 'dcim.delete_rackreservation'
  339. model = RackReservation
  340. def get_return_url(self, request, obj):
  341. return obj.rack.get_absolute_url()
  342. class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  343. permission_required = 'dcim.delete_rackreservation'
  344. cls = RackReservation
  345. table = tables.RackReservationTable
  346. default_return_url = 'dcim:rackreservation_list'
  347. #
  348. # Manufacturers
  349. #
  350. class ManufacturerListView(ObjectListView):
  351. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  352. table = tables.ManufacturerTable
  353. template_name = 'dcim/manufacturer_list.html'
  354. class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView):
  355. permission_required = 'dcim.add_manufacturer'
  356. model = Manufacturer
  357. model_form = forms.ManufacturerForm
  358. def get_return_url(self, request, obj):
  359. return reverse('dcim:manufacturer_list')
  360. class ManufacturerEditView(ManufacturerCreateView):
  361. permission_required = 'dcim.change_manufacturer'
  362. class ManufacturerBulkImportView(PermissionRequiredMixin, BulkImportView):
  363. permission_required = 'dcim.add_manufacturer'
  364. model_form = forms.ManufacturerCSVForm
  365. table = tables.ManufacturerTable
  366. default_return_url = 'dcim:manufacturer_list'
  367. class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  368. permission_required = 'dcim.delete_manufacturer'
  369. cls = Manufacturer
  370. queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types'))
  371. table = tables.ManufacturerTable
  372. default_return_url = 'dcim:manufacturer_list'
  373. #
  374. # Device types
  375. #
  376. class DeviceTypeListView(ObjectListView):
  377. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  378. filter = filters.DeviceTypeFilter
  379. filter_form = forms.DeviceTypeFilterForm
  380. table = tables.DeviceTypeTable
  381. template_name = 'dcim/devicetype_list.html'
  382. class DeviceTypeView(View):
  383. def get(self, request, pk):
  384. devicetype = get_object_or_404(DeviceType, pk=pk)
  385. # Component tables
  386. consoleport_table = tables.ConsolePortTemplateTable(
  387. natsorted(ConsolePortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  388. show_header=False
  389. )
  390. consoleserverport_table = tables.ConsoleServerPortTemplateTable(
  391. natsorted(ConsoleServerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  392. show_header=False
  393. )
  394. powerport_table = tables.PowerPortTemplateTable(
  395. natsorted(PowerPortTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  396. show_header=False
  397. )
  398. poweroutlet_table = tables.PowerOutletTemplateTable(
  399. natsorted(PowerOutletTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  400. show_header=False
  401. )
  402. interface_table = tables.InterfaceTemplateTable(
  403. list(InterfaceTemplate.objects.order_naturally(
  404. devicetype.interface_ordering
  405. ).filter(device_type=devicetype)),
  406. show_header=False
  407. )
  408. devicebay_table = tables.DeviceBayTemplateTable(
  409. natsorted(DeviceBayTemplate.objects.filter(device_type=devicetype), key=attrgetter('name')),
  410. show_header=False
  411. )
  412. if request.user.has_perm('dcim.change_devicetype'):
  413. consoleport_table.base_columns['pk'].visible = True
  414. consoleserverport_table.base_columns['pk'].visible = True
  415. powerport_table.base_columns['pk'].visible = True
  416. poweroutlet_table.base_columns['pk'].visible = True
  417. interface_table.base_columns['pk'].visible = True
  418. devicebay_table.base_columns['pk'].visible = True
  419. return render(request, 'dcim/devicetype.html', {
  420. 'devicetype': devicetype,
  421. 'consoleport_table': consoleport_table,
  422. 'consoleserverport_table': consoleserverport_table,
  423. 'powerport_table': powerport_table,
  424. 'poweroutlet_table': poweroutlet_table,
  425. 'interface_table': interface_table,
  426. 'devicebay_table': devicebay_table,
  427. })
  428. class DeviceTypeCreateView(PermissionRequiredMixin, ObjectEditView):
  429. permission_required = 'dcim.add_devicetype'
  430. model = DeviceType
  431. model_form = forms.DeviceTypeForm
  432. template_name = 'dcim/devicetype_edit.html'
  433. default_return_url = 'dcim:devicetype_list'
  434. class DeviceTypeEditView(DeviceTypeCreateView):
  435. permission_required = 'dcim.change_devicetype'
  436. class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  437. permission_required = 'dcim.delete_devicetype'
  438. model = DeviceType
  439. default_return_url = 'dcim:devicetype_list'
  440. class DeviceTypeBulkImportView(PermissionRequiredMixin, BulkImportView):
  441. permission_required = 'dcim.add_devicetype'
  442. model_form = forms.DeviceTypeCSVForm
  443. table = tables.DeviceTypeTable
  444. default_return_url = 'dcim:devicetype_list'
  445. class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView):
  446. permission_required = 'dcim.change_devicetype'
  447. cls = DeviceType
  448. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  449. filter = filters.DeviceTypeFilter
  450. table = tables.DeviceTypeTable
  451. form = forms.DeviceTypeBulkEditForm
  452. default_return_url = 'dcim:devicetype_list'
  453. class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  454. permission_required = 'dcim.delete_devicetype'
  455. cls = DeviceType
  456. queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances'))
  457. filter = filters.DeviceTypeFilter
  458. table = tables.DeviceTypeTable
  459. default_return_url = 'dcim:devicetype_list'
  460. #
  461. # Device type components
  462. #
  463. class ConsolePortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  464. permission_required = 'dcim.add_consoleporttemplate'
  465. parent_model = DeviceType
  466. parent_field = 'device_type'
  467. model = ConsolePortTemplate
  468. form = forms.ConsolePortTemplateCreateForm
  469. model_form = forms.ConsolePortTemplateForm
  470. template_name = 'dcim/device_component_add.html'
  471. class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  472. permission_required = 'dcim.delete_consoleporttemplate'
  473. parent_model = DeviceType
  474. parent_field = 'device_type'
  475. cls = ConsolePortTemplate
  476. parent_cls = DeviceType
  477. table = tables.ConsolePortTemplateTable
  478. class ConsoleServerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  479. permission_required = 'dcim.add_consoleserverporttemplate'
  480. parent_model = DeviceType
  481. parent_field = 'device_type'
  482. model = ConsoleServerPortTemplate
  483. form = forms.ConsoleServerPortTemplateCreateForm
  484. model_form = forms.ConsoleServerPortTemplateForm
  485. template_name = 'dcim/device_component_add.html'
  486. class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  487. permission_required = 'dcim.delete_consoleserverporttemplate'
  488. cls = ConsoleServerPortTemplate
  489. parent_cls = DeviceType
  490. table = tables.ConsoleServerPortTemplateTable
  491. class PowerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  492. permission_required = 'dcim.add_powerporttemplate'
  493. parent_model = DeviceType
  494. parent_field = 'device_type'
  495. model = PowerPortTemplate
  496. form = forms.PowerPortTemplateCreateForm
  497. model_form = forms.PowerPortTemplateForm
  498. template_name = 'dcim/device_component_add.html'
  499. class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  500. permission_required = 'dcim.delete_powerporttemplate'
  501. cls = PowerPortTemplate
  502. parent_cls = DeviceType
  503. table = tables.PowerPortTemplateTable
  504. class PowerOutletTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  505. permission_required = 'dcim.add_poweroutlettemplate'
  506. parent_model = DeviceType
  507. parent_field = 'device_type'
  508. model = PowerOutletTemplate
  509. form = forms.PowerOutletTemplateCreateForm
  510. model_form = forms.PowerOutletTemplateForm
  511. template_name = 'dcim/device_component_add.html'
  512. class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  513. permission_required = 'dcim.delete_poweroutlettemplate'
  514. cls = PowerOutletTemplate
  515. parent_cls = DeviceType
  516. table = tables.PowerOutletTemplateTable
  517. class InterfaceTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  518. permission_required = 'dcim.add_interfacetemplate'
  519. parent_model = DeviceType
  520. parent_field = 'device_type'
  521. model = InterfaceTemplate
  522. form = forms.InterfaceTemplateCreateForm
  523. model_form = forms.InterfaceTemplateForm
  524. template_name = 'dcim/device_component_add.html'
  525. class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView):
  526. permission_required = 'dcim.change_interfacetemplate'
  527. cls = InterfaceTemplate
  528. parent_cls = DeviceType
  529. table = tables.InterfaceTemplateTable
  530. form = forms.InterfaceTemplateBulkEditForm
  531. class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  532. permission_required = 'dcim.delete_interfacetemplate'
  533. cls = InterfaceTemplate
  534. parent_cls = DeviceType
  535. table = tables.InterfaceTemplateTable
  536. class DeviceBayTemplateCreateView(PermissionRequiredMixin, ComponentCreateView):
  537. permission_required = 'dcim.add_devicebaytemplate'
  538. parent_model = DeviceType
  539. parent_field = 'device_type'
  540. model = DeviceBayTemplate
  541. form = forms.DeviceBayTemplateCreateForm
  542. model_form = forms.DeviceBayTemplateForm
  543. template_name = 'dcim/device_component_add.html'
  544. class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  545. permission_required = 'dcim.delete_devicebaytemplate'
  546. cls = DeviceBayTemplate
  547. parent_cls = DeviceType
  548. table = tables.DeviceBayTemplateTable
  549. #
  550. # Device roles
  551. #
  552. class DeviceRoleListView(ObjectListView):
  553. queryset = DeviceRole.objects.annotate(device_count=Count('devices'))
  554. table = tables.DeviceRoleTable
  555. template_name = 'dcim/devicerole_list.html'
  556. class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView):
  557. permission_required = 'dcim.add_devicerole'
  558. model = DeviceRole
  559. model_form = forms.DeviceRoleForm
  560. def get_return_url(self, request, obj):
  561. return reverse('dcim:devicerole_list')
  562. class DeviceRoleEditView(DeviceRoleCreateView):
  563. permission_required = 'dcim.change_devicerole'
  564. class DeviceRoleBulkImportView(PermissionRequiredMixin, BulkImportView):
  565. permission_required = 'dcim.add_devicerole'
  566. model_form = forms.DeviceRoleCSVForm
  567. table = tables.DeviceRoleTable
  568. default_return_url = 'dcim:devicerole_list'
  569. class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  570. permission_required = 'dcim.delete_devicerole'
  571. cls = DeviceRole
  572. queryset = DeviceRole.objects.annotate(device_count=Count('devices'))
  573. table = tables.DeviceRoleTable
  574. default_return_url = 'dcim:devicerole_list'
  575. #
  576. # Platforms
  577. #
  578. class PlatformListView(ObjectListView):
  579. queryset = Platform.objects.annotate(device_count=Count('devices'))
  580. table = tables.PlatformTable
  581. template_name = 'dcim/platform_list.html'
  582. class PlatformCreateView(PermissionRequiredMixin, ObjectEditView):
  583. permission_required = 'dcim.add_platform'
  584. model = Platform
  585. model_form = forms.PlatformForm
  586. def get_return_url(self, request, obj):
  587. return reverse('dcim:platform_list')
  588. class PlatformEditView(PlatformCreateView):
  589. permission_required = 'dcim.change_platform'
  590. class PlatformBulkImportView(PermissionRequiredMixin, BulkImportView):
  591. permission_required = 'dcim.add_platform'
  592. model_form = forms.PlatformCSVForm
  593. table = tables.PlatformTable
  594. default_return_url = 'dcim:platform_list'
  595. class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  596. permission_required = 'dcim.delete_platform'
  597. cls = Platform
  598. queryset = Platform.objects.annotate(device_count=Count('devices'))
  599. table = tables.PlatformTable
  600. default_return_url = 'dcim:platform_list'
  601. #
  602. # Devices
  603. #
  604. class DeviceListView(ObjectListView):
  605. queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack',
  606. 'primary_ip4', 'primary_ip6')
  607. filter = filters.DeviceFilter
  608. filter_form = forms.DeviceFilterForm
  609. table = tables.DeviceDetailTable
  610. template_name = 'dcim/device_list.html'
  611. class DeviceView(View):
  612. def get(self, request, pk):
  613. device = get_object_or_404(Device.objects.select_related(
  614. 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform'
  615. ), pk=pk)
  616. console_ports = natsorted(
  617. ConsolePort.objects.filter(device=device).select_related('cs_port__device'), key=attrgetter('name')
  618. )
  619. cs_ports = natsorted(
  620. ConsoleServerPort.objects.filter(device=device).select_related('connected_console'), key=attrgetter('name')
  621. )
  622. power_ports = natsorted(
  623. PowerPort.objects.filter(device=device).select_related('power_outlet__device'), key=attrgetter('name')
  624. )
  625. power_outlets = natsorted(
  626. PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
  627. )
  628. interfaces = Interface.objects.order_naturally(
  629. device.device_type.interface_ordering
  630. ).filter(
  631. device=device
  632. ).select_related(
  633. 'connected_as_a__interface_b__device', 'connected_as_b__interface_a__device',
  634. 'circuit_termination__circuit'
  635. ).prefetch_related('ip_addresses')
  636. device_bays = natsorted(
  637. DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
  638. key=attrgetter('name')
  639. )
  640. services = Service.objects.filter(device=device)
  641. secrets = device.secrets.all()
  642. # Find up to ten devices in the same site with the same functional role for quick reference.
  643. related_devices = Device.objects.filter(
  644. site=device.site, device_role=device.device_role
  645. ).exclude(
  646. pk=device.pk
  647. ).select_related(
  648. 'rack', 'device_type__manufacturer'
  649. )[:10]
  650. # Show graph button on interfaces only if at least one graph has been created.
  651. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists()
  652. return render(request, 'dcim/device.html', {
  653. 'device': device,
  654. 'console_ports': console_ports,
  655. 'cs_ports': cs_ports,
  656. 'power_ports': power_ports,
  657. 'power_outlets': power_outlets,
  658. 'interfaces': interfaces,
  659. 'device_bays': device_bays,
  660. 'services': services,
  661. 'secrets': secrets,
  662. 'related_devices': related_devices,
  663. 'show_graphs': show_graphs,
  664. })
  665. class DeviceInventoryView(View):
  666. def get(self, request, pk):
  667. device = get_object_or_404(Device, pk=pk)
  668. inventory_items = InventoryItem.objects.filter(
  669. device=device, parent=None
  670. ).select_related(
  671. 'manufacturer'
  672. ).prefetch_related(
  673. 'child_items'
  674. )
  675. return render(request, 'dcim/device_inventory.html', {
  676. 'device': device,
  677. 'inventory_items': inventory_items,
  678. })
  679. class DeviceStatusView(PermissionRequiredMixin, View):
  680. permission_required = 'dcim.napalm_read'
  681. def get(self, request, pk):
  682. device = get_object_or_404(Device, pk=pk)
  683. return render(request, 'dcim/device_status.html', {
  684. 'device': device,
  685. })
  686. class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
  687. permission_required = 'dcim.napalm_read'
  688. def get(self, request, pk):
  689. device = get_object_or_404(Device, pk=pk)
  690. interfaces = Interface.objects.order_naturally(
  691. device.device_type.interface_ordering
  692. ).connectable().filter(
  693. device=device
  694. ).select_related(
  695. 'connected_as_a', 'connected_as_b'
  696. )
  697. return render(request, 'dcim/device_lldp_neighbors.html', {
  698. 'device': device,
  699. 'interfaces': interfaces,
  700. })
  701. class DeviceConfigView(PermissionRequiredMixin, View):
  702. permission_required = 'dcim.napalm_read'
  703. def get(self, request, pk):
  704. device = get_object_or_404(Device, pk=pk)
  705. return render(request, 'dcim/device_config.html', {
  706. 'device': device,
  707. })
  708. class DeviceCreateView(PermissionRequiredMixin, ObjectEditView):
  709. permission_required = 'dcim.add_device'
  710. model = Device
  711. model_form = forms.DeviceForm
  712. template_name = 'dcim/device_edit.html'
  713. default_return_url = 'dcim:device_list'
  714. class DeviceEditView(DeviceCreateView):
  715. permission_required = 'dcim.change_device'
  716. class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView):
  717. permission_required = 'dcim.delete_device'
  718. model = Device
  719. default_return_url = 'dcim:device_list'
  720. class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  721. permission_required = 'dcim.add_device'
  722. model_form = forms.DeviceCSVForm
  723. table = tables.DeviceImportTable
  724. template_name = 'dcim/device_import.html'
  725. default_return_url = 'dcim:device_list'
  726. class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView):
  727. permission_required = 'dcim.add_device'
  728. model_form = forms.ChildDeviceCSVForm
  729. table = tables.DeviceImportTable
  730. template_name = 'dcim/device_import_child.html'
  731. default_return_url = 'dcim:device_list'
  732. def _save_obj(self, obj_form):
  733. obj = obj_form.save()
  734. # Save the reverse relation to the parent device bay
  735. device_bay = obj.parent_bay
  736. device_bay.installed_device = obj
  737. device_bay.save()
  738. return obj
  739. class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
  740. permission_required = 'dcim.change_device'
  741. cls = Device
  742. queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  743. filter = filters.DeviceFilter
  744. table = tables.DeviceTable
  745. form = forms.DeviceBulkEditForm
  746. default_return_url = 'dcim:device_list'
  747. class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  748. permission_required = 'dcim.delete_device'
  749. cls = Device
  750. queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer')
  751. filter = filters.DeviceFilter
  752. table = tables.DeviceTable
  753. default_return_url = 'dcim:device_list'
  754. #
  755. # Console ports
  756. #
  757. class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView):
  758. permission_required = 'dcim.add_consoleport'
  759. parent_model = Device
  760. parent_field = 'device'
  761. model = ConsolePort
  762. form = forms.ConsolePortCreateForm
  763. model_form = forms.ConsolePortForm
  764. template_name = 'dcim/device_component_add.html'
  765. @permission_required('dcim.change_consoleport')
  766. def consoleport_connect(request, pk):
  767. consoleport = get_object_or_404(ConsolePort, pk=pk)
  768. if request.method == 'POST':
  769. form = forms.ConsolePortConnectionForm(request.POST, instance=consoleport)
  770. if form.is_valid():
  771. consoleport = form.save()
  772. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  773. consoleport.device.get_absolute_url(),
  774. escape(consoleport.device),
  775. escape(consoleport.name),
  776. consoleport.cs_port.device.get_absolute_url(),
  777. escape(consoleport.cs_port.device),
  778. escape(consoleport.cs_port.name),
  779. )
  780. messages.success(request, mark_safe(msg))
  781. UserAction.objects.log_edit(request.user, consoleport, msg)
  782. return redirect('dcim:device', pk=consoleport.device.pk)
  783. else:
  784. form = forms.ConsolePortConnectionForm(instance=consoleport, initial={
  785. 'site': request.GET.get('site'),
  786. 'rack': request.GET.get('rack'),
  787. 'console_server': request.GET.get('console_server'),
  788. 'connection_status': CONNECTION_STATUS_CONNECTED,
  789. })
  790. return render(request, 'dcim/consoleport_connect.html', {
  791. 'consoleport': consoleport,
  792. 'form': form,
  793. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  794. })
  795. @permission_required('dcim.change_consoleport')
  796. def consoleport_disconnect(request, pk):
  797. consoleport = get_object_or_404(ConsolePort, pk=pk)
  798. if not consoleport.cs_port:
  799. messages.warning(
  800. request, "Cannot disconnect console port {}: It is not connected to anything.".format(consoleport)
  801. )
  802. return redirect('dcim:device', pk=consoleport.device.pk)
  803. if request.method == 'POST':
  804. form = ConfirmationForm(request.POST)
  805. if form.is_valid():
  806. cs_port = consoleport.cs_port
  807. consoleport.cs_port = None
  808. consoleport.connection_status = None
  809. consoleport.save()
  810. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  811. consoleport.device.get_absolute_url(),
  812. escape(consoleport.device),
  813. escape(consoleport.name),
  814. cs_port.device.get_absolute_url(),
  815. escape(cs_port.device),
  816. escape(cs_port.name),
  817. )
  818. messages.success(request, mark_safe(msg))
  819. UserAction.objects.log_edit(request.user, consoleport, msg)
  820. return redirect('dcim:device', pk=consoleport.device.pk)
  821. else:
  822. form = ConfirmationForm()
  823. return render(request, 'dcim/consoleport_disconnect.html', {
  824. 'consoleport': consoleport,
  825. 'form': form,
  826. 'return_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}),
  827. })
  828. class ConsolePortEditView(PermissionRequiredMixin, ComponentEditView):
  829. permission_required = 'dcim.change_consoleport'
  830. model = ConsolePort
  831. parent_field = 'device'
  832. model_form = forms.ConsolePortForm
  833. class ConsolePortDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  834. permission_required = 'dcim.delete_consoleport'
  835. model = ConsolePort
  836. parent_field = 'device'
  837. class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  838. permission_required = 'dcim.delete_consoleport'
  839. cls = ConsolePort
  840. parent_cls = Device
  841. table = tables.ConsolePortTable
  842. class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  843. permission_required = 'dcim.change_consoleport'
  844. model_form = forms.ConsoleConnectionCSVForm
  845. table = tables.ConsoleConnectionTable
  846. default_return_url = 'dcim:console_connections_list'
  847. #
  848. # Console server ports
  849. #
  850. class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  851. permission_required = 'dcim.add_consoleserverport'
  852. parent_model = Device
  853. parent_field = 'device'
  854. model = ConsoleServerPort
  855. form = forms.ConsoleServerPortCreateForm
  856. model_form = forms.ConsoleServerPortForm
  857. template_name = 'dcim/device_component_add.html'
  858. @permission_required('dcim.change_consoleserverport')
  859. def consoleserverport_connect(request, pk):
  860. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  861. if request.method == 'POST':
  862. form = forms.ConsoleServerPortConnectionForm(request.POST)
  863. if form.is_valid():
  864. consoleport = form.cleaned_data['port']
  865. consoleport.cs_port = consoleserverport
  866. consoleport.connection_status = form.cleaned_data['connection_status']
  867. consoleport.save()
  868. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  869. consoleport.device.get_absolute_url(),
  870. escape(consoleport.device),
  871. escape(consoleport.name),
  872. consoleserverport.device.get_absolute_url(),
  873. escape(consoleserverport.device),
  874. escape(consoleserverport.name),
  875. )
  876. messages.success(request, mark_safe(msg))
  877. UserAction.objects.log_edit(request.user, consoleport, msg)
  878. return redirect('dcim:device', pk=consoleserverport.device.pk)
  879. else:
  880. form = forms.ConsoleServerPortConnectionForm(initial={
  881. 'site': request.GET.get('site'),
  882. 'rack': request.GET.get('rack'),
  883. 'device': request.GET.get('device'),
  884. 'connection_status': CONNECTION_STATUS_CONNECTED,
  885. })
  886. return render(request, 'dcim/consoleserverport_connect.html', {
  887. 'consoleserverport': consoleserverport,
  888. 'form': form,
  889. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  890. })
  891. @permission_required('dcim.change_consoleserverport')
  892. def consoleserverport_disconnect(request, pk):
  893. consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk)
  894. if not hasattr(consoleserverport, 'connected_console'):
  895. messages.warning(
  896. request, "Cannot disconnect console server port {}: Nothing is connected to it.".format(consoleserverport)
  897. )
  898. return redirect('dcim:device', pk=consoleserverport.device.pk)
  899. if request.method == 'POST':
  900. form = ConfirmationForm(request.POST)
  901. if form.is_valid():
  902. consoleport = consoleserverport.connected_console
  903. consoleport.cs_port = None
  904. consoleport.connection_status = None
  905. consoleport.save()
  906. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  907. consoleport.device.get_absolute_url(),
  908. escape(consoleport.device),
  909. escape(consoleport.name),
  910. consoleserverport.device.get_absolute_url(),
  911. escape(consoleserverport.device),
  912. escape(consoleserverport.name),
  913. )
  914. messages.success(request, mark_safe(msg))
  915. UserAction.objects.log_edit(request.user, consoleport, msg)
  916. return redirect('dcim:device', pk=consoleserverport.device.pk)
  917. else:
  918. form = ConfirmationForm()
  919. return render(request, 'dcim/consoleserverport_disconnect.html', {
  920. 'consoleserverport': consoleserverport,
  921. 'form': form,
  922. 'return_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}),
  923. })
  924. class ConsoleServerPortEditView(PermissionRequiredMixin, ComponentEditView):
  925. permission_required = 'dcim.change_consoleserverport'
  926. model = ConsoleServerPort
  927. parent_field = 'device'
  928. model_form = forms.ConsoleServerPortForm
  929. class ConsoleServerPortDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  930. permission_required = 'dcim.delete_consoleserverport'
  931. model = ConsoleServerPort
  932. parent_field = 'device'
  933. class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  934. permission_required = 'dcim.change_consoleserverport'
  935. model = ConsoleServerPort
  936. form = forms.ConsoleServerPortBulkDisconnectForm
  937. def disconnect_objects(self, cs_ports):
  938. return ConsolePort.objects.filter(cs_port__in=cs_ports).update(cs_port=None, connection_status=None)
  939. class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  940. permission_required = 'dcim.delete_consoleserverport'
  941. cls = ConsoleServerPort
  942. parent_cls = Device
  943. table = tables.ConsoleServerPortTable
  944. #
  945. # Power ports
  946. #
  947. class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView):
  948. permission_required = 'dcim.add_powerport'
  949. parent_model = Device
  950. parent_field = 'device'
  951. model = PowerPort
  952. form = forms.PowerPortCreateForm
  953. model_form = forms.PowerPortForm
  954. template_name = 'dcim/device_component_add.html'
  955. @permission_required('dcim.change_powerport')
  956. def powerport_connect(request, pk):
  957. powerport = get_object_or_404(PowerPort, pk=pk)
  958. if request.method == 'POST':
  959. form = forms.PowerPortConnectionForm(request.POST, instance=powerport)
  960. if form.is_valid():
  961. powerport = form.save()
  962. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  963. powerport.device.get_absolute_url(),
  964. escape(powerport.device),
  965. escape(powerport.name),
  966. powerport.power_outlet.device.get_absolute_url(),
  967. escape(powerport.power_outlet.device),
  968. escape(powerport.power_outlet.name),
  969. )
  970. messages.success(request, mark_safe(msg))
  971. UserAction.objects.log_edit(request.user, powerport, msg)
  972. return redirect('dcim:device', pk=powerport.device.pk)
  973. else:
  974. form = forms.PowerPortConnectionForm(instance=powerport, initial={
  975. 'site': request.GET.get('site'),
  976. 'rack': request.GET.get('rack'),
  977. 'pdu': request.GET.get('pdu'),
  978. 'connection_status': CONNECTION_STATUS_CONNECTED,
  979. })
  980. return render(request, 'dcim/powerport_connect.html', {
  981. 'powerport': powerport,
  982. 'form': form,
  983. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  984. })
  985. @permission_required('dcim.change_powerport')
  986. def powerport_disconnect(request, pk):
  987. powerport = get_object_or_404(PowerPort, pk=pk)
  988. if not powerport.power_outlet:
  989. messages.warning(
  990. request, "Cannot disconnect power port {}: It is not connected to an outlet.".format(powerport)
  991. )
  992. return redirect('dcim:device', pk=powerport.device.pk)
  993. if request.method == 'POST':
  994. form = ConfirmationForm(request.POST)
  995. if form.is_valid():
  996. power_outlet = powerport.power_outlet
  997. powerport.power_outlet = None
  998. powerport.connection_status = None
  999. powerport.save()
  1000. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  1001. powerport.device.get_absolute_url(),
  1002. escape(powerport.device),
  1003. escape(powerport.name),
  1004. power_outlet.device.get_absolute_url(),
  1005. escape(power_outlet.device),
  1006. escape(power_outlet.name),
  1007. )
  1008. messages.success(request, mark_safe(msg))
  1009. UserAction.objects.log_edit(request.user, powerport, msg)
  1010. return redirect('dcim:device', pk=powerport.device.pk)
  1011. else:
  1012. form = ConfirmationForm()
  1013. return render(request, 'dcim/powerport_disconnect.html', {
  1014. 'powerport': powerport,
  1015. 'form': form,
  1016. 'return_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}),
  1017. })
  1018. class PowerPortEditView(PermissionRequiredMixin, ComponentEditView):
  1019. permission_required = 'dcim.change_powerport'
  1020. model = PowerPort
  1021. parent_field = 'device'
  1022. model_form = forms.PowerPortForm
  1023. class PowerPortDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  1024. permission_required = 'dcim.delete_powerport'
  1025. model = PowerPort
  1026. parent_field = 'device'
  1027. class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1028. permission_required = 'dcim.delete_powerport'
  1029. cls = PowerPort
  1030. parent_cls = Device
  1031. table = tables.PowerPortTable
  1032. class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1033. permission_required = 'dcim.change_powerport'
  1034. model_form = forms.PowerConnectionCSVForm
  1035. table = tables.PowerConnectionTable
  1036. default_return_url = 'dcim:power_connections_list'
  1037. #
  1038. # Power outlets
  1039. #
  1040. class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView):
  1041. permission_required = 'dcim.add_poweroutlet'
  1042. parent_model = Device
  1043. parent_field = 'device'
  1044. model = PowerOutlet
  1045. form = forms.PowerOutletCreateForm
  1046. model_form = forms.PowerOutletForm
  1047. template_name = 'dcim/device_component_add.html'
  1048. @permission_required('dcim.change_poweroutlet')
  1049. def poweroutlet_connect(request, pk):
  1050. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1051. if request.method == 'POST':
  1052. form = forms.PowerOutletConnectionForm(request.POST)
  1053. if form.is_valid():
  1054. powerport = form.cleaned_data['port']
  1055. powerport.power_outlet = poweroutlet
  1056. powerport.connection_status = form.cleaned_data['connection_status']
  1057. powerport.save()
  1058. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  1059. powerport.device.get_absolute_url(),
  1060. escape(powerport.device),
  1061. escape(powerport.name),
  1062. poweroutlet.device.get_absolute_url(),
  1063. escape(poweroutlet.device),
  1064. escape(poweroutlet.name),
  1065. )
  1066. messages.success(request, mark_safe(msg))
  1067. UserAction.objects.log_edit(request.user, powerport, msg)
  1068. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1069. else:
  1070. form = forms.PowerOutletConnectionForm(initial={
  1071. 'site': request.GET.get('site'),
  1072. 'rack': request.GET.get('rack'),
  1073. 'device': request.GET.get('device'),
  1074. 'connection_status': CONNECTION_STATUS_CONNECTED,
  1075. })
  1076. return render(request, 'dcim/poweroutlet_connect.html', {
  1077. 'poweroutlet': poweroutlet,
  1078. 'form': form,
  1079. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1080. })
  1081. @permission_required('dcim.change_poweroutlet')
  1082. def poweroutlet_disconnect(request, pk):
  1083. poweroutlet = get_object_or_404(PowerOutlet, pk=pk)
  1084. if not hasattr(poweroutlet, 'connected_port'):
  1085. messages.warning(
  1086. request, "Cannot disconnect power outlet {}: Nothing is connected to it.".format(poweroutlet)
  1087. )
  1088. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1089. if request.method == 'POST':
  1090. form = ConfirmationForm(request.POST)
  1091. if form.is_valid():
  1092. powerport = poweroutlet.connected_port
  1093. powerport.power_outlet = None
  1094. powerport.connection_status = None
  1095. powerport.save()
  1096. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  1097. powerport.device.get_absolute_url(),
  1098. escape(powerport.device),
  1099. escape(powerport.name),
  1100. poweroutlet.device.get_absolute_url(),
  1101. escape(poweroutlet.device),
  1102. escape(poweroutlet.name),
  1103. )
  1104. messages.success(request, mark_safe(msg))
  1105. UserAction.objects.log_edit(request.user, powerport, msg)
  1106. return redirect('dcim:device', pk=poweroutlet.device.pk)
  1107. else:
  1108. form = ConfirmationForm()
  1109. return render(request, 'dcim/poweroutlet_disconnect.html', {
  1110. 'poweroutlet': poweroutlet,
  1111. 'form': form,
  1112. 'return_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}),
  1113. })
  1114. class PowerOutletEditView(PermissionRequiredMixin, ComponentEditView):
  1115. permission_required = 'dcim.change_poweroutlet'
  1116. model = PowerOutlet
  1117. parent_field = 'device'
  1118. model_form = forms.PowerOutletForm
  1119. class PowerOutletDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  1120. permission_required = 'dcim.delete_poweroutlet'
  1121. model = PowerOutlet
  1122. parent_field = 'device'
  1123. class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1124. permission_required = 'dcim.change_poweroutlet'
  1125. model = PowerOutlet
  1126. form = forms.PowerOutletBulkDisconnectForm
  1127. def disconnect_objects(self, power_outlets):
  1128. return PowerPort.objects.filter(power_outlet__in=power_outlets).update(
  1129. power_outlet=None, connection_status=None
  1130. )
  1131. class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1132. permission_required = 'dcim.delete_poweroutlet'
  1133. cls = PowerOutlet
  1134. parent_cls = Device
  1135. table = tables.PowerOutletTable
  1136. #
  1137. # Interfaces
  1138. #
  1139. class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView):
  1140. permission_required = 'dcim.add_interface'
  1141. parent_model = Device
  1142. parent_field = 'device'
  1143. model = Interface
  1144. form = forms.InterfaceCreateForm
  1145. model_form = forms.InterfaceForm
  1146. template_name = 'dcim/device_component_add.html'
  1147. class InterfaceEditView(PermissionRequiredMixin, ComponentEditView):
  1148. permission_required = 'dcim.change_interface'
  1149. model = Interface
  1150. parent_field = 'device'
  1151. model_form = forms.InterfaceForm
  1152. class InterfaceDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  1153. permission_required = 'dcim.delete_interface'
  1154. model = Interface
  1155. parent_field = 'device'
  1156. class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
  1157. permission_required = 'dcim.change_interface'
  1158. model = Interface
  1159. form = forms.InterfaceBulkDisconnectForm
  1160. def disconnect_objects(self, interfaces):
  1161. count, _ = InterfaceConnection.objects.filter(
  1162. Q(interface_a__in=interfaces) | Q(interface_b__in=interfaces)
  1163. ).delete()
  1164. return count
  1165. class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView):
  1166. permission_required = 'dcim.change_interface'
  1167. cls = Interface
  1168. parent_cls = Device
  1169. table = tables.InterfaceTable
  1170. form = forms.InterfaceBulkEditForm
  1171. class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1172. permission_required = 'dcim.delete_interface'
  1173. cls = Interface
  1174. parent_cls = Device
  1175. table = tables.InterfaceTable
  1176. #
  1177. # Device bays
  1178. #
  1179. class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView):
  1180. permission_required = 'dcim.add_devicebay'
  1181. parent_model = Device
  1182. parent_field = 'device'
  1183. model = DeviceBay
  1184. form = forms.DeviceBayCreateForm
  1185. model_form = forms.DeviceBayForm
  1186. template_name = 'dcim/device_component_add.html'
  1187. class DeviceBayEditView(PermissionRequiredMixin, ComponentEditView):
  1188. permission_required = 'dcim.change_devicebay'
  1189. model = DeviceBay
  1190. parent_field = 'device'
  1191. model_form = forms.DeviceBayForm
  1192. class DeviceBayDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  1193. permission_required = 'dcim.delete_devicebay'
  1194. model = DeviceBay
  1195. parent_field = 'device'
  1196. @permission_required('dcim.change_devicebay')
  1197. def devicebay_populate(request, pk):
  1198. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1199. if request.method == 'POST':
  1200. form = forms.PopulateDeviceBayForm(device_bay, request.POST)
  1201. if form.is_valid():
  1202. device_bay.installed_device = form.cleaned_data['installed_device']
  1203. device_bay.save()
  1204. if not form.errors:
  1205. messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay))
  1206. return redirect('dcim:device', pk=device_bay.device.pk)
  1207. else:
  1208. form = forms.PopulateDeviceBayForm(device_bay)
  1209. return render(request, 'dcim/devicebay_populate.html', {
  1210. 'device_bay': device_bay,
  1211. 'form': form,
  1212. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1213. })
  1214. @permission_required('dcim.change_devicebay')
  1215. def devicebay_depopulate(request, pk):
  1216. device_bay = get_object_or_404(DeviceBay, pk=pk)
  1217. if request.method == 'POST':
  1218. form = ConfirmationForm(request.POST)
  1219. if form.is_valid():
  1220. removed_device = device_bay.installed_device
  1221. device_bay.installed_device = None
  1222. device_bay.save()
  1223. messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay))
  1224. return redirect('dcim:device', pk=device_bay.device.pk)
  1225. else:
  1226. form = ConfirmationForm()
  1227. return render(request, 'dcim/devicebay_depopulate.html', {
  1228. 'device_bay': device_bay,
  1229. 'form': form,
  1230. 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}),
  1231. })
  1232. class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
  1233. permission_required = 'dcim.delete_devicebay'
  1234. cls = DeviceBay
  1235. parent_cls = Device
  1236. table = tables.DeviceBayTable
  1237. #
  1238. # Bulk Device component creation
  1239. #
  1240. class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateView):
  1241. permission_required = 'dcim.add_consoleport'
  1242. parent_model = Device
  1243. parent_field = 'device'
  1244. form = forms.DeviceBulkAddComponentForm
  1245. model = ConsolePort
  1246. model_form = forms.ConsolePortForm
  1247. table = tables.DeviceTable
  1248. default_return_url = 'dcim:device_list'
  1249. class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1250. permission_required = 'dcim.add_consoleserverport'
  1251. parent_model = Device
  1252. parent_field = 'device'
  1253. form = forms.DeviceBulkAddComponentForm
  1254. model = ConsoleServerPort
  1255. model_form = forms.ConsoleServerPortForm
  1256. table = tables.DeviceTable
  1257. default_return_url = 'dcim:device_list'
  1258. class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateView):
  1259. permission_required = 'dcim.add_powerport'
  1260. parent_model = Device
  1261. parent_field = 'device'
  1262. form = forms.DeviceBulkAddComponentForm
  1263. model = PowerPort
  1264. model_form = forms.PowerPortForm
  1265. table = tables.DeviceTable
  1266. default_return_url = 'dcim:device_list'
  1267. class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateView):
  1268. permission_required = 'dcim.add_poweroutlet'
  1269. parent_model = Device
  1270. parent_field = 'device'
  1271. form = forms.DeviceBulkAddComponentForm
  1272. model = PowerOutlet
  1273. model_form = forms.PowerOutletForm
  1274. table = tables.DeviceTable
  1275. default_return_url = 'dcim:device_list'
  1276. class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView):
  1277. permission_required = 'dcim.add_interface'
  1278. parent_model = Device
  1279. parent_field = 'device'
  1280. form = forms.DeviceBulkAddInterfaceForm
  1281. model = Interface
  1282. model_form = forms.InterfaceForm
  1283. table = tables.DeviceTable
  1284. default_return_url = 'dcim:device_list'
  1285. class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateView):
  1286. permission_required = 'dcim.add_devicebay'
  1287. parent_model = Device
  1288. parent_field = 'device'
  1289. form = forms.DeviceBulkAddComponentForm
  1290. model = DeviceBay
  1291. model_form = forms.DeviceBayForm
  1292. table = tables.DeviceTable
  1293. default_return_url = 'dcim:device_list'
  1294. #
  1295. # Interface connections
  1296. #
  1297. @permission_required('dcim.add_interfaceconnection')
  1298. def interfaceconnection_add(request, pk):
  1299. device = get_object_or_404(Device, pk=pk)
  1300. if request.method == 'POST':
  1301. form = forms.InterfaceConnectionForm(device, request.POST)
  1302. if form.is_valid():
  1303. interfaceconnection = form.save()
  1304. msg = 'Connected <a href="{}">{}</a> {} to <a href="{}">{}</a> {}'.format(
  1305. interfaceconnection.interface_a.device.get_absolute_url(),
  1306. escape(interfaceconnection.interface_a.device),
  1307. escape(interfaceconnection.interface_a.name),
  1308. interfaceconnection.interface_b.device.get_absolute_url(),
  1309. escape(interfaceconnection.interface_b.device),
  1310. escape(interfaceconnection.interface_b.name),
  1311. )
  1312. messages.success(request, mark_safe(msg))
  1313. UserAction.objects.log_edit(request.user, interfaceconnection, msg)
  1314. if '_addanother' in request.POST:
  1315. base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk})
  1316. device_b = interfaceconnection.interface_b.device
  1317. params = urlencode({
  1318. 'rack_b': device_b.rack.pk if device_b.rack else '',
  1319. 'device_b': device_b.pk,
  1320. })
  1321. return HttpResponseRedirect('{}?{}'.format(base_url, params))
  1322. else:
  1323. return redirect('dcim:device', pk=device.pk)
  1324. else:
  1325. form = forms.InterfaceConnectionForm(device, initial={
  1326. 'interface_a': request.GET.get('interface_a'),
  1327. 'site_b': request.GET.get('site_b'),
  1328. 'rack_b': request.GET.get('rack_b'),
  1329. 'device_b': request.GET.get('device_b'),
  1330. 'interface_b': request.GET.get('interface_b'),
  1331. })
  1332. return render(request, 'dcim/interfaceconnection_edit.html', {
  1333. 'device': device,
  1334. 'form': form,
  1335. 'return_url': reverse('dcim:device', kwargs={'pk': device.pk}),
  1336. })
  1337. @permission_required('dcim.delete_interfaceconnection')
  1338. def interfaceconnection_delete(request, pk):
  1339. interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk)
  1340. device_id = request.GET.get('device', None)
  1341. if request.method == 'POST':
  1342. form = forms.InterfaceConnectionDeletionForm(request.POST)
  1343. if form.is_valid():
  1344. interfaceconnection.delete()
  1345. msg = 'Disconnected <a href="{}">{}</a> {} from <a href="{}">{}</a> {}'.format(
  1346. interfaceconnection.interface_a.device.get_absolute_url(),
  1347. escape(interfaceconnection.interface_a.device),
  1348. escape(interfaceconnection.interface_a.name),
  1349. interfaceconnection.interface_b.device.get_absolute_url(),
  1350. escape(interfaceconnection.interface_b.device),
  1351. escape(interfaceconnection.interface_b.name),
  1352. )
  1353. messages.success(request, mark_safe(msg))
  1354. UserAction.objects.log_edit(request.user, interfaceconnection, msg)
  1355. if form.cleaned_data['device']:
  1356. return redirect('dcim:device', pk=form.cleaned_data['device'].pk)
  1357. else:
  1358. return redirect('dcim:device_list')
  1359. else:
  1360. form = forms.InterfaceConnectionDeletionForm(initial={
  1361. 'device': device_id,
  1362. })
  1363. # Determine where to direct user upon cancellation
  1364. if device_id:
  1365. return_url = reverse('dcim:device', kwargs={'pk': device_id})
  1366. else:
  1367. return_url = reverse('dcim:device_list')
  1368. return render(request, 'dcim/interfaceconnection_delete.html', {
  1369. 'interfaceconnection': interfaceconnection,
  1370. 'device_id': device_id,
  1371. 'form': form,
  1372. 'return_url': return_url,
  1373. })
  1374. class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView):
  1375. permission_required = 'dcim.change_interface'
  1376. model_form = forms.InterfaceConnectionCSVForm
  1377. table = tables.InterfaceConnectionTable
  1378. default_return_url = 'dcim:interface_connections_list'
  1379. #
  1380. # Connections
  1381. #
  1382. class ConsoleConnectionsListView(ObjectListView):
  1383. queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\
  1384. .order_by('cs_port__device__name', 'cs_port__name')
  1385. filter = filters.ConsoleConnectionFilter
  1386. filter_form = forms.ConsoleConnectionFilterForm
  1387. table = tables.ConsoleConnectionTable
  1388. template_name = 'dcim/console_connections_list.html'
  1389. class PowerConnectionsListView(ObjectListView):
  1390. queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\
  1391. .order_by('power_outlet__device__name', 'power_outlet__name')
  1392. filter = filters.PowerConnectionFilter
  1393. filter_form = forms.PowerConnectionFilterForm
  1394. table = tables.PowerConnectionTable
  1395. template_name = 'dcim/power_connections_list.html'
  1396. class InterfaceConnectionsListView(ObjectListView):
  1397. queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\
  1398. .order_by('interface_a__device__name', 'interface_a__name')
  1399. filter = filters.InterfaceConnectionFilter
  1400. filter_form = forms.InterfaceConnectionFilterForm
  1401. table = tables.InterfaceConnectionTable
  1402. template_name = 'dcim/interface_connections_list.html'
  1403. #
  1404. # Inventory items
  1405. #
  1406. class InventoryItemEditView(PermissionRequiredMixin, ComponentEditView):
  1407. permission_required = 'dcim.change_inventoryitem'
  1408. model = InventoryItem
  1409. parent_field = 'device'
  1410. model_form = forms.InventoryItemForm
  1411. def alter_obj(self, obj, request, url_args, url_kwargs):
  1412. if 'device' in url_kwargs:
  1413. obj.device = get_object_or_404(Device, pk=url_kwargs['device'])
  1414. return obj
  1415. class InventoryItemDeleteView(PermissionRequiredMixin, ComponentDeleteView):
  1416. permission_required = 'dcim.delete_inventoryitem'
  1417. model = InventoryItem
  1418. parent_field = 'device'