views.py 55 KB

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