views.py 61 KB

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