views.py 54 KB

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