views.py 62 KB

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