views.py 53 KB

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