import re from django.contrib import messages from django.contrib.auth.decorators import permission_required from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.urlresolvers import reverse from django.db.models import Count, ProtectedError from django.http import HttpResponseRedirect from django.shortcuts import get_object_or_404, redirect, render from django.utils.http import urlencode from ipam.models import Prefix, IPAddress, VLAN from circuits.models import Circuit from utilities.error_handlers import handle_protectederror from utilities.forms import ConfirmationForm from utilities.views import ObjectListView, BulkImportView, BulkEditView, BulkDeleteView from .filters import RackFilter, DeviceTypeFilter, DeviceFilter, ConsoleConnectionFilter, PowerConnectionFilter, \ InterfaceConnectionFilter from .forms import SiteForm, SiteImportForm, RackForm, RackImportForm, RackBulkEditForm, RackBulkDeleteForm, \ RackFilterForm, DeviceTypeForm, DeviceTypeBulkEditForm, DeviceTypeBulkDeleteForm, DeviceTypeFilterForm, \ DeviceForm, DeviceImportForm, DeviceBulkEditForm, DeviceBulkDeleteForm, DeviceFilterForm, \ ConsolePortForm, ConsolePortCreateForm, ConsolePortConnectionForm, ConsoleConnectionImportForm, \ ConsoleServerPortForm, ConsoleServerPortCreateForm, ConsoleServerPortConnectionForm, PowerPortForm, \ PowerPortCreateForm, PowerPortConnectionForm, PowerConnectionImportForm, PowerOutletForm, PowerOutletCreateForm, \ PowerOutletConnectionForm, InterfaceForm, InterfaceCreateForm, InterfaceBulkCreateForm, InterfaceConnectionForm, \ InterfaceConnectionDeletionForm, InterfaceConnectionImportForm, ConsoleConnectionFilterForm, \ PowerConnectionFilterForm, InterfaceConnectionFilterForm, IPAddressForm from .models import Site, Rack, DeviceType, Device, ConsolePort, ConsoleServerPort, PowerPort, PowerOutlet, Interface, \ InterfaceConnection, Module, CONNECTION_STATUS_CONNECTED from .tables import SiteTable, RackTable, RackBulkEditTable, DeviceTypeTable, DeviceTypeBulkEditTable, DeviceTable, \ DeviceBulkEditTable, DeviceImportTable, ConsoleConnectionTable, PowerConnectionTable, InterfaceConnectionTable EXPANSION_PATTERN = '\[(\d+-\d+)\]' def xstr(s): """ Replace None with an empty string (for CSV export) """ return '' if s is None else str(s) def expand_pattern(string): """ Expand a numeric pattern into a list of strings. Examples: 'ge-0/0/[0-3]' => ['ge-0/0/0', 'ge-0/0/1', 'ge-0/0/2', 'ge-0/0/3'] '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'] """ lead, pattern, remnant = re.split(EXPANSION_PATTERN, string, maxsplit=1) x, y = pattern.split('-') for i in range(int(x), int(y) + 1): if remnant: for string in expand_pattern(remnant): yield "{0}{1}{2}".format(lead, i, string) else: yield "{0}{1}".format(lead, i) # # Sites # class SiteListView(ObjectListView): queryset = Site.objects.all() table = SiteTable template_name = 'dcim/site_list.html' def site(request, slug): site = get_object_or_404(Site, slug=slug) stats = { 'rack_count': Rack.objects.filter(site=site).count(), 'device_count': Device.objects.filter(rack__site=site).count(), 'prefix_count': Prefix.objects.filter(site=site).count(), 'vlan_count': VLAN.objects.filter(site=site).count(), 'circuit_count': Circuit.objects.filter(site=site).count(), } return render(request, 'dcim/site.html', { 'site': site, 'stats': stats, }) @permission_required('dcim.add_site') def site_add(request): if request.method == 'POST': form = SiteForm(request.POST) if form.is_valid(): site = form.save() messages.success(request, "Added new site: {0}".format(site.name)) if '_addanother' in request.POST: return redirect('dcim:site_add') else: return redirect('dcim:site', slug=site.slug) else: form = SiteForm() return render(request, 'dcim/site_edit.html', { 'form': form, 'cancel_url': reverse('dcim:site_list'), }) @permission_required('dcim.change_site') def site_edit(request, slug): site = get_object_or_404(Site, slug=slug) if request.method == 'POST': form = SiteForm(request.POST, instance=site) if form.is_valid(): site = form.save() messages.success(request, "Modified site {0}".format(site.name)) return redirect('dcim:site', slug=site.slug) else: form = SiteForm(instance=site) return render(request, 'dcim/site_edit.html', { 'site': site, 'form': form, 'cancel_url': reverse('dcim:site', kwargs={'slug': site.slug}), }) @permission_required('dcim.delete_site') def site_delete(request, slug): site = get_object_or_404(Site, slug=slug) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): try: site.delete() messages.success(request, "Site {0} has been deleted".format(site)) return redirect('dcim:site_list') except ProtectedError, e: handle_protectederror(site, request, e) return redirect('dcim:site', slug=site.slug) else: form = ConfirmationForm() return render(request, 'dcim/site_delete.html', { 'site': site, 'form': form, 'cancel_url': reverse('dcim:site', kwargs={'slug': site.slug}), }) class SiteBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_site' form = SiteImportForm table = SiteTable template_name = 'dcim/site_import.html' obj_list_url = 'dcim:site_list' # # Racks # class RackListView(ObjectListView): queryset = Rack.objects.select_related('site').annotate(device_count=Count('devices', distinct=True)) filter = RackFilter filter_form = RackFilterForm table = RackTable edit_table = RackBulkEditTable edit_table_permissions = ['dcim.change_rack', 'dcim.delete_rack'] template_name = 'dcim/rack_list.html' def rack(request, pk): rack = get_object_or_404(Rack, pk=pk) nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True) try: next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name')[0] except IndexError: next_rack = None try: prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name')[0] except IndexError: prev_rack = None return render(request, 'dcim/rack.html', { 'rack': rack, 'nonracked_devices': nonracked_devices, 'next_rack': next_rack, 'prev_rack': prev_rack, 'front_elevation': rack.get_front_elevation(), 'rear_elevation': rack.get_rear_elevation(), }) @permission_required('dcim.add_rack') def rack_add(request): if request.method == 'POST': form = RackForm(request.POST) if form.is_valid(): rack = form.save() messages.success(request, "Added new rack to {}: {}".format(rack.site.name, rack)) if '_addanother' in request.POST: base_url = reverse('dcim:rack_add') params = urlencode({ 'site': rack.site.pk, }) return HttpResponseRedirect('{}?{}'.format(base_url, params)) else: return redirect('dcim:rack', pk=rack.pk) else: form = RackForm() return render(request, 'dcim/rack_edit.html', { 'form': form, 'cancel_url': reverse('dcim:rack_list'), }) @permission_required('dcim.change_rack') def rack_edit(request, pk): rack = get_object_or_404(Rack, pk=pk) if request.method == 'POST': form = RackForm(request.POST, instance=rack) if form.is_valid(): rack = form.save() messages.success(request, "Modified rack {0}".format(rack.name)) return redirect('dcim:rack', pk=rack.pk) else: form = RackForm(instance=rack) return render(request, 'dcim/rack_edit.html', { 'rack': rack, 'form': form, 'cancel_url': reverse('dcim:rack', kwargs={'pk': rack.pk}), }) @permission_required('dcim.delete_rack') def rack_delete(request, pk): rack = get_object_or_404(Rack, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): try: rack.delete() messages.success(request, "Rack {0} has been deleted".format(rack)) return redirect('dcim:rack_list') except ProtectedError, e: handle_protectederror(rack, request, e) return redirect('dcim:rack', pk=rack.pk) else: form = ConfirmationForm() return render(request, 'dcim/rack_delete.html', { 'rack': rack, 'form': form, 'cancel_url': reverse('dcim:rack', kwargs={'pk': rack.pk}), }) class RackBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_rack' form = RackImportForm table = RackTable template_name = 'dcim/rack_import.html' obj_list_url = 'dcim:rack_list' class RackBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rack' cls = Rack form = RackBulkEditForm template_name = 'dcim/rack_bulk_edit.html' redirect_url = 'dcim:rack_list' def update_objects(self, pk_list, form): fields_to_update = {} for field in ['site', 'group', 'u_height', 'comments']: if form.cleaned_data[field]: fields_to_update[field] = form.cleaned_data[field] updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update) messages.success(self.request, "Updated {} racks".format(updated_count)) class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rack' cls = Rack form = RackBulkDeleteForm template_name = 'dcim/rack_bulk_delete.html' redirect_url = 'dcim:rack_list' # # Device types # class DeviceTypeListView(ObjectListView): queryset = DeviceType.objects.select_related('manufacturer') filter = DeviceTypeFilter filter_form = DeviceTypeFilterForm table = DeviceTypeTable edit_table = DeviceTypeBulkEditTable edit_table_permissions = ['dcim.change_devicetype', 'dcim.delete_devicetype'] template_name = 'dcim/devicetype_list.html' def devicetype(request, pk): devicetype = get_object_or_404(DeviceType, pk=pk) return render(request, 'dcim/devicetype.html', { 'devicetype': devicetype, }) @permission_required('dcim.add_devicetype') def devicetype_add(request): if request.method == 'POST': form = DeviceTypeForm(request.POST) if form.is_valid(): devicetype = form.save() messages.success(request, "Added new device type: {}".format(devicetype)) if '_addanother' in request.POST: return redirect('dcim:devicetype_add') else: return redirect('dcim:devicetype', pk=devicetype.pk) else: form = DeviceTypeForm() return render(request, 'dcim/devicetype_edit.html', { 'form': form, 'cancel_url': reverse('dcim:devicetype_list'), }) @permission_required('dcim.change_devicetype') def devicetype_edit(request, pk): devicetype = get_object_or_404(DeviceType, pk=pk) if request.method == 'POST': form = DeviceTypeForm(request.POST, instance=devicetype) if form.is_valid(): devicetype = form.save() messages.success(request, "Modified device type {}".format(devicetype)) return redirect('dcim:devicetype', pk=devicetype.pk) else: form = DeviceTypeForm(instance=devicetype) return render(request, 'dcim/devicetype_edit.html', { 'devicetype': devicetype, 'form': form, 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}), }) @permission_required('dcim.delete_devicetype') def devicetype_delete(request, pk): devicetype = get_object_or_404(DeviceType, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): try: devicetype.delete() messages.success(request, "Device type {} has been deleted".format(devicetype)) return redirect('dcim:devicetype_list') except ProtectedError, e: handle_protectederror(devicetype, request, e) return redirect('dcim:devicetype', pk=devicetype.pk) else: form = ConfirmationForm() return render(request, 'dcim/devicetype_delete.html', { 'devicetype': device, 'form': form, 'cancel_url': reverse('dcim:devicetype', kwargs={'pk': devicetype.pk}), }) class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_devicetype' cls = DeviceType form = DeviceTypeBulkEditForm template_name = 'dcim/devicetype_bulk_edit.html' redirect_url = 'dcim:devicetype_list' def update_objects(self, pk_list, form): fields_to_update = {} for field in ['manufacturer', 'u_height']: if form.cleaned_data[field]: fields_to_update[field] = form.cleaned_data[field] updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update) messages.success(self.request, "Updated {} device types".format(updated_count)) class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicetype' cls = DeviceType form = DeviceTypeBulkDeleteForm template_name = 'dcim/devicetype_bulk_delete.html' redirect_url = 'dcim:devicetype_list' # # Devices # class DeviceListView(ObjectListView): queryset = Device.objects.select_related('device_type', 'device_type__manufacturer', 'device_role', 'rack', 'rack__site', 'primary_ip') filter = DeviceFilter filter_form = DeviceFilterForm table = DeviceTable edit_table = DeviceBulkEditTable edit_table_permissions = ['dcim.change_device', 'dcim.delete_device'] template_name = 'dcim/device_list.html' def device(request, pk): device = get_object_or_404(Device, pk=pk) console_ports = ConsolePort.objects.filter(device=device).select_related('cs_port__device') cs_ports = ConsoleServerPort.objects.filter(device=device).select_related('connected_console') power_ports = PowerPort.objects.filter(device=device).select_related('power_outlet__device') power_outlets = PowerOutlet.objects.filter(device=device).select_related('connected_port') interfaces = Interface.objects.filter(device=device, mgmt_only=False).select_related('connected_as_a', 'connected_as_b', 'circuit') mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True).select_related('connected_as_a', 'connected_as_b', 'circuit') # Gather any secrets which belong to this device secrets = device.secrets.all() # Find all IP addresses assigned to this device ip_addresses = IPAddress.objects.filter(interface__device=device).select_related('interface').order_by('interface') # Find any related devices for convenient linking in the UI related_devices = [] if device.name: if re.match('.+[0-9]+$', device.name): # Strip 1 or more trailing digits (e.g. core-switch1) base_name = re.match('(.*?)[0-9]+$', device.name).group(1) elif re.match('.+\d[a-z]+$', device.name.lower()): # Strip a trailing letter if preceded by a digit (e.g. dist-switch3a -> dist-switch3) base_name = re.match('(.*\d+)[a-z]$', device.name.lower()).group(1) else: base_name = None if base_name: related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk).select_related('rack', 'device_type__manufacturer')[:10] return render(request, 'dcim/device.html', { 'device': device, 'console_ports': console_ports, 'cs_ports': cs_ports, 'power_ports': power_ports, 'power_outlets': power_outlets, 'interfaces': interfaces, 'mgmt_interfaces': mgmt_interfaces, 'ip_addresses': ip_addresses, 'secrets': secrets, 'related_devices': related_devices, }) @permission_required('dcim.add_device') def device_add(request): if request.method == 'POST': form = DeviceForm(request.POST) if form.is_valid(): device = form.save() messages.success(request, "Added new device: {0} ({1})".format(device.name, device.device_type)) if '_addanother' in request.POST: base_url = reverse('dcim:device_add') params = urlencode({ 'site': device.rack.site.pk, 'rack': device.rack.pk, }) return HttpResponseRedirect('{}?{}'.format(base_url, params)) else: return redirect('dcim:device', pk=device.pk) else: initial_data = {} if request.GET.get('rack', None): try: rack = Rack.objects.get(pk=request.GET.get('rack', None)) initial_data['rack'] = rack.pk initial_data['site'] = rack.site.pk initial_data['position'] = request.GET.get('position') initial_data['face'] = request.GET.get('face') except Rack.DoesNotExist: pass form = DeviceForm(initial=initial_data) return render(request, 'dcim/device_edit.html', { 'form': form, 'cancel_url': reverse('dcim:device_list'), }) @permission_required('dcim.change_device') def device_edit(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = DeviceForm(request.POST, instance=device) if form.is_valid(): device = form.save() messages.success(request, "Modified device {0}".format(device.name)) return redirect('dcim:device', pk=device.pk) else: form = DeviceForm(instance=device) return render(request, 'dcim/device_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.delete_device') def device_delete(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): try: device.delete() messages.success(request, "Device {0} has been deleted".format(device)) return redirect('dcim:device_list') except ProtectedError, e: handle_protectederror(device, request, e) return redirect('dcim:device', pk=device.pk) else: form = ConfirmationForm() return render(request, 'dcim/device_delete.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_device' form = DeviceImportForm table = DeviceImportTable template_name = 'dcim/device_import.html' obj_list_url = 'dcim:device_list' class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_device' cls = Device form = DeviceBulkEditForm template_name = 'dcim/device_bulk_edit.html' redirect_url = 'dcim:device_list' def update_objects(self, pk_list, form): fields_to_update = {} if form.cleaned_data['platform']: fields_to_update['platform'] = form.cleaned_data['platform'] elif form.cleaned_data['platform_delete']: fields_to_update['platform'] = None if form.cleaned_data['status']: status = form.cleaned_data['status'] fields_to_update['status'] = True if status == 'True' else False for field in ['device_type', 'device_role', 'serial', 'ro_snmp']: if form.cleaned_data[field]: fields_to_update[field] = form.cleaned_data[field] updated_count = self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update) messages.success(self.request, "Updated {} devices".format(updated_count)) class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_device' cls = Device form = DeviceBulkDeleteForm template_name = 'dcim/device_bulk_delete.html' redirect_url = 'dcim:device_list' def device_inventory(request, pk): device = get_object_or_404(Device, pk=pk) modules = Module.objects.filter(device=device) return render(request, 'dcim/device_inventory.html', { 'device': device, 'modules': modules, }) def device_lldp_neighbors(request, pk): device = get_object_or_404(Device, pk=pk) interfaces = Interface.objects.filter(device=device).select_related('connected_as_a', 'connected_as_b') return render(request, 'dcim/device_lldp_neighbors.html', { 'device': device, 'interfaces': interfaces, }) # # Console ports # @permission_required('dcim.add_consoleport') def consoleport_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = ConsolePortCreateForm(request.POST) if form.is_valid(): console_ports = [] for name in form.cleaned_data['name_pattern']: cp_form = ConsolePortForm({ 'device': device.pk, 'name': name, }) if cp_form.is_valid(): console_ports.append(cp_form.save(commit=False)) else: form.add_error('name_pattern', "Duplicate console port name for this device: {}".format(name)) if not form.errors: ConsolePort.objects.bulk_create(console_ports) messages.success(request, "Added {} console port(s) to {}".format(len(console_ports), device)) if '_addanother' in request.POST: return redirect('dcim:consoleport_add', pk=device.pk) else: return redirect('dcim:device', pk=device.pk) else: form = ConsolePortCreateForm() return render(request, 'dcim/consoleport_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.change_consoleport') def consoleport_connect(request, pk): consoleport = get_object_or_404(ConsolePort, pk=pk) if request.method == 'POST': form = ConsolePortConnectionForm(request.POST, instance=consoleport) if form.is_valid(): consoleport = form.save() messages.success(request, "Connected {0} {1} to {2} {3}".format( consoleport.device, consoleport.name, consoleport.cs_port.device, consoleport.cs_port.name, )) return redirect('dcim:device', pk=consoleport.device.pk) else: form = ConsolePortConnectionForm(instance=consoleport, initial={ 'rack': consoleport.device.rack, 'connection_status': CONNECTION_STATUS_CONNECTED, }) return render(request, 'dcim/consoleport_connect.html', { 'consoleport': consoleport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}), }) @permission_required('dcim.change_consoleport') def consoleport_disconnect(request, pk): consoleport = get_object_or_404(ConsolePort, pk=pk) if not consoleport.cs_port: messages.warning(request, "Cannot disconnect console port {0}: It is not connected to anything".format(consoleport)) return redirect('dcim:device', pk=consoleport.device.pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): consoleport.cs_port = None consoleport.connection_status = None consoleport.save() messages.success(request, "Console port {0} has been disconnected".format(consoleport)) return redirect('dcim:device', pk=consoleport.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/consoleport_disconnect.html', { 'consoleport': consoleport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}), }) @permission_required('dcim.change_consoleport') def consoleport_edit(request, pk): consoleport = get_object_or_404(ConsolePort, pk=pk) if request.method == 'POST': form = ConsolePortForm(request.POST, instance=consoleport) if form.is_valid(): consoleport = form.save() messages.success(request, "Modified {0} {1}".format(consoleport.device.name, consoleport.name)) return redirect('dcim:device', pk=consoleport.device.pk) else: form = ConsolePortForm(instance=consoleport) return render(request, 'dcim/consoleport_edit.html', { 'consoleport': consoleport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}), }) @permission_required('dcim.delete_consoleport') def consoleport_delete(request, pk): consoleport = get_object_or_404(ConsolePort, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): consoleport.delete() messages.success(request, "Console port {0} has been deleted from {1}".format(consoleport, consoleport.device)) return redirect('dcim:device', pk=consoleport.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/consoleport_delete.html', { 'consoleport': consoleport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleport.device.pk}), }) class ConsoleConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.change_consoleport' form = ConsoleConnectionImportForm table = ConsoleConnectionTable template_name = 'dcim/console_connections_import.html' # # Console server ports # @permission_required('dcim.add_consoleserverport') def consoleserverport_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = ConsoleServerPortCreateForm(request.POST) if form.is_valid(): cs_ports = [] for name in form.cleaned_data['name_pattern']: csp_form = ConsoleServerPortForm({ 'device': device.pk, 'name': name, }) if csp_form.is_valid(): cs_ports.append(csp_form.save(commit=False)) else: form.add_error('name_pattern', "Duplicate console server port name for this device: {}" .format(name)) if not form.errors: ConsoleServerPort.objects.bulk_create(cs_ports) messages.success(request, "Added {} console server port(s) to {}".format(len(cs_ports), device)) if '_addanother' in request.POST: return redirect('dcim:consoleserverport_add', pk=device.pk) else: return redirect('dcim:device', pk=device.pk) else: form = ConsoleServerPortCreateForm() return render(request, 'dcim/consoleserverport_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.change_consoleserverport') def consoleserverport_connect(request, pk): consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk) if request.method == 'POST': form = ConsoleServerPortConnectionForm(consoleserverport, request.POST) if form.is_valid(): consoleport = form.cleaned_data['port'] consoleport.cs_port = consoleserverport consoleport.connection_status = form.cleaned_data['connection_status'] consoleport.save() messages.success(request, "Connected {0} {1} to {2} {3}".format( consoleport.device, consoleport.name, consoleserverport.device, consoleserverport.name, )) return redirect('dcim:device', pk=consoleserverport.device.pk) else: form = ConsoleServerPortConnectionForm(consoleserverport, initial={'rack': consoleserverport.device.rack}) return render(request, 'dcim/consoleserverport_connect.html', { 'consoleserverport': consoleserverport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}), }) @permission_required('dcim.change_consoleserverport') def consoleserverport_disconnect(request, pk): consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk) if not hasattr(consoleserverport, 'connected_console'): messages.warning(request, "Cannot disconnect console server port {0}: Nothing is connected to it".format(consoleserverport)) return redirect('dcim:device', pk=consoleserverport.device.pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): consoleport = consoleserverport.connected_console consoleport.cs_port = None consoleport.connection_status = None consoleport.save() messages.success(request, "Console server port {0} has been disconnected".format(consoleserverport)) return redirect('dcim:device', pk=consoleserverport.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/consoleserverport_disconnect.html', { 'consoleserverport': consoleserverport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}), }) @permission_required('dcim.change_consoleserverport') def consoleserverport_edit(request, pk): consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk) if request.method == 'POST': form = ConsoleServerPortForm(request.POST, instance=consoleserverport) if form.is_valid(): consoleserverport = form.save() messages.success(request, "Modified {0} {1}".format(consoleserverport.device.name, consoleserverport.name)) return redirect('dcim:device', pk=consoleserverport.device.pk) else: form = ConsoleServerPortForm(instance=consoleserverport) return render(request, 'dcim/consoleserverport_edit.html', { 'consoleserverport': consoleserverport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}), }) @permission_required('dcim.delete_consoleserverport') def consoleserverport_delete(request, pk): consoleserverport = get_object_or_404(ConsoleServerPort, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): consoleserverport.delete() messages.success(request, "Console server port {0} has been deleted from {1}".format(consoleserverport, consoleserverport.device)) return redirect('dcim:device', pk=consoleserverport.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/consoleserverport_delete.html', { 'consoleserverport': consoleserverport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': consoleserverport.device.pk}), }) # # Power ports # @permission_required('dcim.add_powerport') def powerport_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = PowerPortCreateForm(request.POST) if form.is_valid(): power_ports = [] for name in form.cleaned_data['name_pattern']: pp_form = PowerPortForm({ 'device': device.pk, 'name': name, }) if pp_form.is_valid(): power_ports.append(pp_form.save(commit=False)) else: form.add_error('name_pattern', "Duplicate power port name for this device: {}".format(name)) if not form.errors: PowerPort.objects.bulk_create(power_ports) messages.success(request, "Added {} power port(s) to {}".format(len(power_ports), device)) if '_addanother' in request.POST: return redirect('dcim:powerport_add', pk=device.pk) else: return redirect('dcim:device', pk=device.pk) else: form = PowerPortCreateForm() return render(request, 'dcim/powerport_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.change_powerport') def powerport_connect(request, pk): powerport = get_object_or_404(PowerPort, pk=pk) if request.method == 'POST': form = PowerPortConnectionForm(request.POST, instance=powerport) if form.is_valid(): powerport = form.save() messages.success(request, "Connected {0} {1} to {2} {3}".format( powerport.device, powerport.name, powerport.power_outlet.device, powerport.power_outlet.name, )) return redirect('dcim:device', pk=powerport.device.pk) else: form = PowerPortConnectionForm(instance=powerport, initial={ 'rack': powerport.device.rack, 'connection_status': CONNECTION_STATUS_CONNECTED, }) return render(request, 'dcim/powerport_connect.html', { 'powerport': powerport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}), }) @permission_required('dcim.change_powerport') def powerport_disconnect(request, pk): powerport = get_object_or_404(PowerPort, pk=pk) if not powerport.power_outlet: messages.warning(request, "Cannot disconnect power port {0}: It is not connected to an outlet".format(powerport)) return redirect('dcim:device', pk=powerport.device.pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): powerport.power_outlet = None powerport.connection_status = None powerport.save() messages.success(request, "Power port {0} has been disconnected".format(powerport)) return redirect('dcim:device', pk=powerport.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/powerport_disconnect.html', { 'powerport': powerport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}), }) @permission_required('dcim.change_powerport') def powerport_edit(request, pk): powerport = get_object_or_404(PowerPort, pk=pk) if request.method == 'POST': form = PowerPortForm(request.POST, instance=powerport) if form.is_valid(): powerport = form.save() messages.success(request, "Modified {0} power port {1}".format(powerport.device.name, powerport.name)) return redirect('dcim:device', pk=powerport.device.pk) else: form = PowerPortForm(instance=powerport) return render(request, 'dcim/powerport_edit.html', { 'powerport': powerport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}), }) @permission_required('dcim.delete_powerport') def powerport_delete(request, pk): powerport = get_object_or_404(PowerPort, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): powerport.delete() messages.success(request, "Power port {0} has been deleted from {1}".format(powerport, powerport.device)) return redirect('dcim:device', pk=powerport.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/powerport_delete.html', { 'powerport': powerport, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': powerport.device.pk}), }) class PowerConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.change_powerport' form = PowerConnectionImportForm table = PowerConnectionTable template_name = 'dcim/power_connections_import.html' # # Power outlets # @permission_required('dcim.add_poweroutlet') def poweroutlet_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = PowerOutletCreateForm(request.POST) if form.is_valid(): power_outlets = [] for name in form.cleaned_data['name_pattern']: po_form = PowerOutletForm({ 'device': device.pk, 'name': name, }) if po_form.is_valid(): power_outlets.append(po_form.save(commit=False)) else: form.add_error('name_pattern', "Duplicate power outlet name for this device: {}".format(name)) if not form.errors: PowerOutlet.objects.bulk_create(power_outlets) messages.success(request, "Added {} power outlet(s) to {}".format(len(power_outlets), device)) if '_addanother' in request.POST: return redirect('dcim:poweroutlet_add', pk=device.pk) else: return redirect('dcim:device', pk=device.pk) else: form = PowerOutletCreateForm() return render(request, 'dcim/poweroutlet_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.change_poweroutlet') def poweroutlet_connect(request, pk): poweroutlet = get_object_or_404(PowerOutlet, pk=pk) if request.method == 'POST': form = PowerOutletConnectionForm(poweroutlet, request.POST) if form.is_valid(): powerport = form.cleaned_data['port'] powerport.power_outlet = poweroutlet powerport.connection_status = form.cleaned_data['connection_status'] powerport.save() messages.success(request, "Connected {0} {1} to {2} {3}".format( powerport.device, powerport.name, poweroutlet.device, poweroutlet.name, )) return redirect('dcim:device', pk=poweroutlet.device.pk) else: form = PowerOutletConnectionForm(poweroutlet, initial={'rack': poweroutlet.device.rack}) return render(request, 'dcim/poweroutlet_connect.html', { 'poweroutlet': poweroutlet, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}), }) @permission_required('dcim.change_poweroutlet') def poweroutlet_disconnect(request, pk): poweroutlet = get_object_or_404(PowerOutlet, pk=pk) if not hasattr(poweroutlet, 'connected_port'): messages.warning(request, "Cannot disconnectpower outlet {0}: Nothing is connected to it".format(poweroutlet)) return redirect('dcim:device', pk=poweroutlet.device.pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): powerport = poweroutlet.connected_port powerport.power_outlet = None powerport.connection_status = None powerport.save() messages.success(request, "Power outlet {0} has been disconnected".format(poweroutlet)) return redirect('dcim:device', pk=poweroutlet.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/poweroutlet_disconnect.html', { 'poweroutlet': poweroutlet, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}), }) @permission_required('dcim.change_poweroutlet') def poweroutlet_edit(request, pk): poweroutlet = get_object_or_404(PowerOutlet, pk=pk) if request.method == 'POST': form = PowerOutletForm(request.POST, instance=poweroutlet) if form.is_valid(): poweroutlet = form.save() messages.success(request, "Modified {0} power outlet {1}".format(poweroutlet.device.name, poweroutlet.name)) return redirect('dcim:device', pk=poweroutlet.device.pk) else: form = PowerOutletForm(instance=poweroutlet) return render(request, 'dcim/poweroutlet_edit.html', { 'poweroutlet': poweroutlet, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}), }) @permission_required('dcim.delete_poweroutlet') def poweroutlet_delete(request, pk): poweroutlet = get_object_or_404(PowerOutlet, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): poweroutlet.delete() messages.success(request, "Power outlet {0} has been deleted from {1}".format(poweroutlet, poweroutlet.device)) return redirect('dcim:device', pk=poweroutlet.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/poweroutlet_delete.html', { 'poweroutlet': poweroutlet, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': poweroutlet.device.pk}), }) # # Interfaces # @permission_required('dcim.add_interface') def interface_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = InterfaceCreateForm(request.POST) if form.is_valid(): interfaces = [] for name in form.cleaned_data['name_pattern']: iface_form = InterfaceForm({ 'device': device.pk, 'name': name, 'form_factor': form.cleaned_data['form_factor'], 'mgmt_only': form.cleaned_data['mgmt_only'], 'description': form.cleaned_data['description'], }) if iface_form.is_valid(): interfaces.append(iface_form.save(commit=False)) else: form.add_error('name_pattern', "Duplicate interface name for this device: {}".format(name)) if not form.errors: Interface.objects.bulk_create(interfaces) messages.success(request, "Added {} interface(s) to {}".format(len(interfaces), device)) if '_addanother' in request.POST: return redirect('dcim:interface_add', pk=device.pk) else: return redirect('dcim:device', pk=device.pk) else: form = InterfaceCreateForm() return render(request, 'dcim/interface_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.change_interface') def interface_edit(request, pk): interface = get_object_or_404(Interface, pk=pk) if request.method == 'POST': form = InterfaceForm(request.POST, instance=interface) if form.is_valid(): interface = form.save() messages.success(request, "Modified {0} interface {1}".format(interface.device.name, interface.name)) return redirect('dcim:device', pk=interface.device.pk) else: form = InterfaceForm(instance=interface) return render(request, 'dcim/interface_edit.html', { 'interface': interface, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}), }) @permission_required('dcim.delete_interface') def interface_delete(request, pk): interface = get_object_or_404(Interface, pk=pk) if request.method == 'POST': form = ConfirmationForm(request.POST) if form.is_valid(): interface.delete() messages.success(request, "Interface {0} has been deleted from {1}".format(interface, interface.device)) return redirect('dcim:device', pk=interface.device.pk) else: form = ConfirmationForm() return render(request, 'dcim/interface_delete.html', { 'interface': interface, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': interface.device.pk}), }) class InterfaceBulkAddView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.add_interface' cls = Device form = InterfaceBulkCreateForm template_name = 'dcim/interface_bulk_add.html' redirect_url = 'dcim:device_list' def update_objects(self, pk_list, form): selected_devices = Device.objects.filter(pk__in=pk_list) interfaces = [] for device in selected_devices: for name in form.cleaned_data['name_pattern']: iface_form = InterfaceForm({ 'device': device.pk, 'name': name, 'form_factor': form.cleaned_data['form_factor'], 'mgmt_only': form.cleaned_data['mgmt_only'], 'description': form.cleaned_data['description'], }) if iface_form.is_valid(): interfaces.append(iface_form.save(commit=False)) else: form.add_error(None, "Duplicate interface {} found for device {}".format(name, device)) if not form.errors: Interface.objects.bulk_create(interfaces) messages.success(self.request, "Added {} interfaces to {} devices".format(len(interfaces), len(selected_devices))) # # Interface connections # @permission_required('dcim.add_interfaceconnection') def interfaceconnection_add(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = InterfaceConnectionForm(device, request.POST) if form.is_valid(): interfaceconnection = form.save() messages.success(request, "Connected {0} {1} to {2} {3}".format( interfaceconnection.interface_a.device, interfaceconnection.interface_a, interfaceconnection.interface_b.device, interfaceconnection.interface_b, )) if '_addanother' in request.POST: base_url = reverse('dcim:interfaceconnection_add', kwargs={'pk': device.pk}) params = urlencode({ 'rack_b': interfaceconnection.interface_b.device.rack.pk, 'device_b': interfaceconnection.interface_b.device.pk, }) return HttpResponseRedirect('{}?{}'.format(base_url, params)) else: return redirect('dcim:device', pk=device.pk) else: form = InterfaceConnectionForm(device, initial={ 'interface_a': request.GET.get('interface', None), 'rack_b': request.GET.get('rack_b', None), 'device_b': request.GET.get('device_b', None), }) return render(request, 'dcim/interfaceconnection_edit.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), }) @permission_required('dcim.delete_interfaceconnection') def interfaceconnection_delete(request, pk): interfaceconnection = get_object_or_404(InterfaceConnection, pk=pk) device_id = request.GET.get('device', None) if request.method == 'POST': form = InterfaceConnectionDeletionForm(request.POST) if form.is_valid(): interfaceconnection.delete() messages.success(request, "Deleted the connection between {0} {1} and {2} {3}".format( interfaceconnection.interface_a.device, interfaceconnection.interface_a, interfaceconnection.interface_b.device, interfaceconnection.interface_b, )) if form.cleaned_data['device']: return redirect('dcim:device', pk=form.cleaned_data['device'].pk) else: return redirect('dcim:device_list') else: form = InterfaceConnectionDeletionForm(initial={ 'device': device_id, }) # Determine where to direct user upon cancellation if device_id: cancel_url = reverse('dcim:device', kwargs={'pk': device_id}) else: cancel_url = reverse('dcim:device_list') return render(request, 'dcim/interfaceconnection_delete.html', { 'interfaceconnection': interfaceconnection, 'device_id': device_id, 'form': form, 'cancel_url': cancel_url, }) class InterfaceConnectionsBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.change_interface' form = InterfaceConnectionImportForm table = InterfaceConnectionTable template_name = 'dcim/interface_connections_import.html' # # Connections # class ConsoleConnectionsListView(ObjectListView): queryset = ConsolePort.objects.select_related('device', 'cs_port__device').filter(cs_port__isnull=False)\ .order_by('cs_port__device__name', 'cs_port__name') filter = ConsoleConnectionFilter filter_form = ConsoleConnectionFilterForm table = ConsoleConnectionTable template_name = 'dcim/console_connections_list.html' class PowerConnectionsListView(ObjectListView): queryset = PowerPort.objects.select_related('device', 'power_outlet__device').filter(power_outlet__isnull=False)\ .order_by('power_outlet__device__name', 'power_outlet__name') filter = PowerConnectionFilter filter_form = PowerConnectionFilterForm table = PowerConnectionTable template_name = 'dcim/power_connections_list.html' class InterfaceConnectionsListView(ObjectListView): queryset = InterfaceConnection.objects.select_related('interface_a__device', 'interface_b__device')\ .order_by('interface_a__device__name', 'interface_a__name') filter = InterfaceConnectionFilter filter_form = InterfaceConnectionFilterForm table = InterfaceConnectionTable template_name = 'dcim/interface_connections_list.html' # # IP addresses # @permission_required('ipam.add_ipaddress') def ipaddress_assign(request, pk): device = get_object_or_404(Device, pk=pk) if request.method == 'POST': form = IPAddressForm(device, request.POST) if form.is_valid(): ipaddress = form.save(commit=False) ipaddress.interface = form.cleaned_data['interface'] ipaddress.save() messages.success(request, "Added new IP address {0} to interface {1}".format(ipaddress, ipaddress.interface)) if form.cleaned_data['set_as_primary']: device.primary_ip = ipaddress device.save() if '_addanother' in request.POST: return redirect('dcim:ipaddress_assign', pk=device.pk) else: return redirect('dcim:device', pk=device.pk) else: form = IPAddressForm(device) return render(request, 'dcim/ipaddress_assign.html', { 'device': device, 'form': form, 'cancel_url': reverse('dcim:device', kwargs={'pk': device.pk}), })