from django.contrib import messages from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.paginator import EmptyPage, PageNotAnInteger from django.db import transaction from django.db.models import Count, F from django.forms import modelformset_factory from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils.html import escape from django.utils.safestring import mark_safe from django.views.generic import View from circuits.models import Circuit from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE from extras.views import ObjectConfigContextView from ipam.models import Prefix, VLAN from ipam.tables import InterfaceIPAddressTable, InterfaceVLANTable from utilities.forms import ConfirmationForm from utilities.paginator import EnhancedPaginator from utilities.views import ( BulkComponentCreateView, BulkDeleteView, BulkEditView, BulkImportView, ComponentCreateView, GetReturnURLMixin, ObjectDeleteView, ObjectEditView, ObjectListView, ) from virtualization.models import VirtualMachine from . import filters, forms, tables from .models import ( Cable, ConsoleConnection, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceConnection, InterfaceTemplate, InventoryItem, Manufacturer, Platform, PowerConnection, PowerOutlet, PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RackReservation, RackRole, RearPort, RearPortTemplate, Region, Site, VirtualChassis, ) class BulkRenameView(GetReturnURLMixin, View): """ An extendable view for renaming device components in bulk. """ queryset = None form = None template_name = 'dcim/bulk_rename.html' def post(self, request): model = self.queryset.model if '_preview' in request.POST or '_apply' in request.POST: form = self.form(request.POST, initial={'pk': request.POST.getlist('pk')}) selected_objects = self.queryset.filter(pk__in=form.initial['pk']) if form.is_valid(): for obj in selected_objects: obj.new_name = obj.name.replace(form.cleaned_data['find'], form.cleaned_data['replace']) if '_apply' in request.POST: for obj in selected_objects: obj.name = obj.new_name obj.save() messages.success(request, "Renamed {} {}".format( len(selected_objects), model._meta.verbose_name_plural )) return redirect(self.get_return_url(request)) else: form = self.form(initial={'pk': request.POST.getlist('pk')}) selected_objects = self.queryset.filter(pk__in=form.initial['pk']) return render(request, self.template_name, { 'form': form, 'obj_type_plural': model._meta.verbose_name_plural, 'selected_objects': selected_objects, 'return_url': self.get_return_url(request), }) class BulkDisconnectView(GetReturnURLMixin, View): """ An extendable view for disconnection console/power/interface components in bulk. """ model = None form = None template_name = 'dcim/bulk_disconnect.html' def post(self, request): selected_objects = [] return_url = self.get_return_url(request) if '_confirm' in request.POST: form = self.form(request.POST) if form.is_valid(): with transaction.atomic(): count = 0 for obj in self.model.objects.filter(pk__in=form.cleaned_data['pk']): if obj.cable is None: continue obj.cable.delete() count += 1 messages.success(request, "Disconnected {} {}".format( count, self.model._meta.verbose_name_plural )) return redirect(return_url) else: form = self.form(initial={'pk': request.POST.getlist('pk')}) selected_objects = self.model.objects.filter(pk__in=form.initial['pk']) return render(request, self.template_name, { 'form': form, 'obj_type_plural': self.model._meta.verbose_name_plural, 'selected_objects': selected_objects, 'return_url': return_url, }) # # Regions # class RegionListView(ObjectListView): queryset = Region.objects.annotate(site_count=Count('sites')) filter = filters.RegionFilter filter_form = forms.RegionFilterForm table = tables.RegionTable template_name = 'dcim/region_list.html' class RegionCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_region' model = Region model_form = forms.RegionForm default_return_url = 'dcim:region_list' class RegionEditView(RegionCreateView): permission_required = 'dcim.change_region' class RegionBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_region' model_form = forms.RegionCSVForm table = tables.RegionTable default_return_url = 'dcim:region_list' class RegionBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_region' queryset = Region.objects.annotate(site_count=Count('sites')) filter = filters.RegionFilter table = tables.RegionTable default_return_url = 'dcim:region_list' # # Sites # class SiteListView(ObjectListView): queryset = Site.objects.select_related('region', 'tenant') filter = filters.SiteFilter filter_form = forms.SiteFilterForm table = tables.SiteTable template_name = 'dcim/site_list.html' class SiteView(View): def get(self, request, slug): site = get_object_or_404(Site.objects.select_related('region', 'tenant__group'), slug=slug) stats = { 'rack_count': Rack.objects.filter(site=site).count(), 'device_count': Device.objects.filter(site=site).count(), 'prefix_count': Prefix.objects.filter(site=site).count(), 'vlan_count': VLAN.objects.filter(site=site).count(), 'circuit_count': Circuit.objects.filter(terminations__site=site).count(), 'vm_count': VirtualMachine.objects.filter(cluster__site=site).count(), } rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks')) topology_maps = TopologyMap.objects.filter(site=site) show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists() return render(request, 'dcim/site.html', { 'site': site, 'stats': stats, 'rack_groups': rack_groups, 'topology_maps': topology_maps, 'show_graphs': show_graphs, }) class SiteCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_site' model = Site model_form = forms.SiteForm template_name = 'dcim/site_edit.html' default_return_url = 'dcim:site_list' class SiteEditView(SiteCreateView): permission_required = 'dcim.change_site' class SiteDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_site' model = Site default_return_url = 'dcim:site_list' class SiteBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_site' model_form = forms.SiteCSVForm table = tables.SiteTable default_return_url = 'dcim:site_list' class SiteBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_site' queryset = Site.objects.select_related('region', 'tenant') filter = filters.SiteFilter table = tables.SiteTable form = forms.SiteBulkEditForm default_return_url = 'dcim:site_list' # # Rack groups # class RackGroupListView(ObjectListView): queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')) filter = filters.RackGroupFilter filter_form = forms.RackGroupFilterForm table = tables.RackGroupTable template_name = 'dcim/rackgroup_list.html' class RackGroupCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_rackgroup' model = RackGroup model_form = forms.RackGroupForm default_return_url = 'dcim:rackgroup_list' class RackGroupEditView(RackGroupCreateView): permission_required = 'dcim.change_rackgroup' class RackGroupBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_rackgroup' model_form = forms.RackGroupCSVForm table = tables.RackGroupTable default_return_url = 'dcim:rackgroup_list' class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackgroup' queryset = RackGroup.objects.select_related('site').annotate(rack_count=Count('racks')) filter = filters.RackGroupFilter table = tables.RackGroupTable default_return_url = 'dcim:rackgroup_list' # # Rack roles # class RackRoleListView(ObjectListView): queryset = RackRole.objects.annotate(rack_count=Count('racks')) table = tables.RackRoleTable template_name = 'dcim/rackrole_list.html' class RackRoleCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_rackrole' model = RackRole model_form = forms.RackRoleForm default_return_url = 'dcim:rackrole_list' class RackRoleEditView(RackRoleCreateView): permission_required = 'dcim.change_rackrole' class RackRoleBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_rackrole' model_form = forms.RackRoleCSVForm table = tables.RackRoleTable default_return_url = 'dcim:rackrole_list' class RackRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackrole' queryset = RackRole.objects.annotate(rack_count=Count('racks')) table = tables.RackRoleTable default_return_url = 'dcim:rackrole_list' # # Racks # class RackListView(ObjectListView): queryset = Rack.objects.select_related( 'site', 'group', 'tenant', 'role' ).prefetch_related( 'devices__device_type' ).annotate( device_count=Count('devices') ) filter = filters.RackFilter filter_form = forms.RackFilterForm table = tables.RackDetailTable template_name = 'dcim/rack_list.html' class RackElevationListView(View): """ Display a set of rack elevations side-by-side. """ def get(self, request): racks = Rack.objects.select_related( 'site', 'group', 'tenant', 'role' ).prefetch_related( 'devices__device_type' ) racks = filters.RackFilter(request.GET, racks).qs total_count = racks.count() # Pagination paginator = EnhancedPaginator(racks, 25) page_number = request.GET.get('page', 1) try: page = paginator.page(page_number) except PageNotAnInteger: page = paginator.page(1) except EmptyPage: page = paginator.page(paginator.num_pages) # Determine rack face if request.GET.get('face') == '1': face_id = 1 else: face_id = 0 return render(request, 'dcim/rack_elevation_list.html', { 'paginator': paginator, 'page': page, 'total_count': total_count, 'face_id': face_id, 'filter_form': forms.RackFilterForm(request.GET), }) class RackView(View): def get(self, request, pk): rack = get_object_or_404(Rack.objects.select_related('site__region', 'tenant__group', 'group', 'role'), pk=pk) nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True) \ .select_related('device_type__manufacturer') next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first() prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first() reservations = RackReservation.objects.filter(rack=rack) return render(request, 'dcim/rack.html', { 'rack': rack, 'reservations': reservations, 'nonracked_devices': nonracked_devices, 'next_rack': next_rack, 'prev_rack': prev_rack, 'front_elevation': rack.get_front_elevation(), 'rear_elevation': rack.get_rear_elevation(), }) class RackCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_rack' model = Rack model_form = forms.RackForm template_name = 'dcim/rack_edit.html' default_return_url = 'dcim:rack_list' class RackEditView(RackCreateView): permission_required = 'dcim.change_rack' class RackDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_rack' model = Rack default_return_url = 'dcim:rack_list' class RackBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_rack' model_form = forms.RackCSVForm table = tables.RackTable default_return_url = 'dcim:rack_list' class RackBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rack' queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role') filter = filters.RackFilter table = tables.RackTable form = forms.RackBulkEditForm default_return_url = 'dcim:rack_list' class RackBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rack' queryset = Rack.objects.select_related('site', 'group', 'tenant', 'role') filter = filters.RackFilter table = tables.RackTable default_return_url = 'dcim:rack_list' # # Rack reservations # class RackReservationListView(ObjectListView): queryset = RackReservation.objects.all() filter = filters.RackReservationFilter filter_form = forms.RackReservationFilterForm table = tables.RackReservationTable template_name = 'dcim/rackreservation_list.html' class RackReservationCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_rackreservation' model = RackReservation model_form = forms.RackReservationForm def alter_obj(self, obj, request, args, kwargs): if not obj.pk: obj.rack = get_object_or_404(Rack, pk=kwargs['rack']) obj.user = request.user return obj def get_return_url(self, request, obj): return obj.rack.get_absolute_url() class RackReservationEditView(RackReservationCreateView): permission_required = 'dcim.change_rackreservation' class RackReservationDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_rackreservation' model = RackReservation def get_return_url(self, request, obj): return obj.rack.get_absolute_url() class RackReservationBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_rackreservation' queryset = RackReservation.objects.select_related('rack', 'user') filter = filters.RackReservationFilter table = tables.RackReservationTable form = forms.RackReservationBulkEditForm default_return_url = 'dcim:rackreservation_list' class RackReservationBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rackreservation' queryset = RackReservation.objects.select_related('rack', 'user') filter = filters.RackReservationFilter table = tables.RackReservationTable default_return_url = 'dcim:rackreservation_list' # # Manufacturers # class ManufacturerListView(ObjectListView): queryset = Manufacturer.objects.annotate( devicetype_count=Count('device_types', distinct=True), platform_count=Count('platforms', distinct=True), ) table = tables.ManufacturerTable template_name = 'dcim/manufacturer_list.html' class ManufacturerCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_manufacturer' model = Manufacturer model_form = forms.ManufacturerForm default_return_url = 'dcim:manufacturer_list' class ManufacturerEditView(ManufacturerCreateView): permission_required = 'dcim.change_manufacturer' class ManufacturerBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_manufacturer' model_form = forms.ManufacturerCSVForm table = tables.ManufacturerTable default_return_url = 'dcim:manufacturer_list' class ManufacturerBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_manufacturer' queryset = Manufacturer.objects.annotate(devicetype_count=Count('device_types')) table = tables.ManufacturerTable default_return_url = 'dcim:manufacturer_list' # # Device types # class DeviceTypeListView(ObjectListView): queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter filter_form = forms.DeviceTypeFilterForm table = tables.DeviceTypeTable template_name = 'dcim/devicetype_list.html' class DeviceTypeView(View): def get(self, request, pk): devicetype = get_object_or_404(DeviceType, pk=pk) # Component tables consoleport_table = tables.ConsolePortTemplateTable( ConsolePortTemplate.objects.filter(device_type=devicetype), orderable=False ) consoleserverport_table = tables.ConsoleServerPortTemplateTable( ConsoleServerPortTemplate.objects.filter(device_type=devicetype), orderable=False ) powerport_table = tables.PowerPortTemplateTable( PowerPortTemplate.objects.filter(device_type=devicetype), orderable=False ) poweroutlet_table = tables.PowerOutletTemplateTable( PowerOutletTemplate.objects.filter(device_type=devicetype), orderable=False ) interface_table = tables.InterfaceTemplateTable( list(InterfaceTemplate.objects.filter(device_type=devicetype)), orderable=False ) front_port_table = tables.FrontPortTemplateTable( FrontPortTemplate.objects.filter(device_type=devicetype), orderable=False ) rear_port_table = tables.RearPortTemplateTable( RearPortTemplate.objects.filter(device_type=devicetype), orderable=False ) devicebay_table = tables.DeviceBayTemplateTable( DeviceBayTemplate.objects.filter(device_type=devicetype), orderable=False ) if request.user.has_perm('dcim.change_devicetype'): consoleport_table.columns.show('pk') consoleserverport_table.columns.show('pk') powerport_table.columns.show('pk') poweroutlet_table.columns.show('pk') interface_table.columns.show('pk') front_port_table.columns.show('pk') rear_port_table.columns.show('pk') devicebay_table.columns.show('pk') return render(request, 'dcim/devicetype.html', { 'devicetype': devicetype, 'consoleport_table': consoleport_table, 'consoleserverport_table': consoleserverport_table, 'powerport_table': powerport_table, 'poweroutlet_table': poweroutlet_table, 'interface_table': interface_table, 'front_port_table': front_port_table, 'rear_port_table': rear_port_table, 'devicebay_table': devicebay_table, }) class DeviceTypeCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_devicetype' model = DeviceType model_form = forms.DeviceTypeForm template_name = 'dcim/devicetype_edit.html' default_return_url = 'dcim:devicetype_list' class DeviceTypeEditView(DeviceTypeCreateView): permission_required = 'dcim.change_devicetype' class DeviceTypeDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_devicetype' model = DeviceType default_return_url = 'dcim:devicetype_list' class DeviceTypeBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_devicetype' model_form = forms.DeviceTypeCSVForm table = tables.DeviceTypeTable default_return_url = 'dcim:devicetype_list' class DeviceTypeBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_devicetype' queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter table = tables.DeviceTypeTable form = forms.DeviceTypeBulkEditForm default_return_url = 'dcim:devicetype_list' class DeviceTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicetype' queryset = DeviceType.objects.select_related('manufacturer').annotate(instance_count=Count('instances')) filter = filters.DeviceTypeFilter table = tables.DeviceTypeTable default_return_url = 'dcim:devicetype_list' # # Device type components # class ConsolePortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_consoleporttemplate' parent_model = DeviceType parent_field = 'device_type' model = ConsolePortTemplate form = forms.ConsolePortTemplateCreateForm model_form = forms.ConsolePortTemplateForm template_name = 'dcim/device_component_add.html' class ConsolePortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleporttemplate' queryset = ConsolePortTemplate.objects.all() parent_model = DeviceType table = tables.ConsolePortTemplateTable class ConsoleServerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_consoleserverporttemplate' parent_model = DeviceType parent_field = 'device_type' model = ConsoleServerPortTemplate form = forms.ConsoleServerPortTemplateCreateForm model_form = forms.ConsoleServerPortTemplateForm template_name = 'dcim/device_component_add.html' class ConsoleServerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleserverporttemplate' queryset = ConsoleServerPortTemplate.objects.all() parent_model = DeviceType table = tables.ConsoleServerPortTemplateTable class PowerPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_powerporttemplate' parent_model = DeviceType parent_field = 'device_type' model = PowerPortTemplate form = forms.PowerPortTemplateCreateForm model_form = forms.PowerPortTemplateForm template_name = 'dcim/device_component_add.html' class PowerPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_powerporttemplate' queryset = PowerPortTemplate.objects.all() parent_model = DeviceType table = tables.PowerPortTemplateTable class PowerOutletTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_poweroutlettemplate' parent_model = DeviceType parent_field = 'device_type' model = PowerOutletTemplate form = forms.PowerOutletTemplateCreateForm model_form = forms.PowerOutletTemplateForm template_name = 'dcim/device_component_add.html' class PowerOutletTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_poweroutlettemplate' queryset = PowerOutletTemplate.objects.all() parent_model = DeviceType table = tables.PowerOutletTemplateTable class InterfaceTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_interfacetemplate' parent_model = DeviceType parent_field = 'device_type' model = InterfaceTemplate form = forms.InterfaceTemplateCreateForm model_form = forms.InterfaceTemplateForm template_name = 'dcim/device_component_add.html' class InterfaceTemplateBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_interfacetemplate' queryset = InterfaceTemplate.objects.all() parent_model = DeviceType table = tables.InterfaceTemplateTable form = forms.InterfaceTemplateBulkEditForm class InterfaceTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_interfacetemplate' queryset = InterfaceTemplate.objects.all() parent_model = DeviceType table = tables.InterfaceTemplateTable class FrontPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_frontporttemplate' parent_model = DeviceType parent_field = 'device_type' model = FrontPortTemplate form = forms.FrontPortTemplateCreateForm model_form = forms.FrontPortTemplateForm template_name = 'dcim/device_component_add.html' class FrontPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_frontporttemplate' queryset = FrontPortTemplate.objects.all() parent_model = DeviceType table = tables.FrontPortTemplateTable class RearPortTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_rearporttemplate' parent_model = DeviceType parent_field = 'device_type' model = RearPortTemplate form = forms.RearPortTemplateCreateForm model_form = forms.RearPortTemplateForm template_name = 'dcim/device_component_add.html' class RearPortTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rearporttemplate' queryset = RearPortTemplate.objects.all() parent_model = DeviceType table = tables.RearPortTemplateTable class DeviceBayTemplateCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_devicebaytemplate' parent_model = DeviceType parent_field = 'device_type' model = DeviceBayTemplate form = forms.DeviceBayTemplateCreateForm model_form = forms.DeviceBayTemplateForm template_name = 'dcim/device_component_add.html' class DeviceBayTemplateBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicebaytemplate' queryset = DeviceBayTemplate.objects.all() parent_model = DeviceType table = tables.DeviceBayTemplateTable # # Device roles # class DeviceRoleListView(ObjectListView): queryset = DeviceRole.objects.all() table = tables.DeviceRoleTable template_name = 'dcim/devicerole_list.html' class DeviceRoleCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_devicerole' model = DeviceRole model_form = forms.DeviceRoleForm default_return_url = 'dcim:devicerole_list' class DeviceRoleEditView(DeviceRoleCreateView): permission_required = 'dcim.change_devicerole' class DeviceRoleBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_devicerole' model_form = forms.DeviceRoleCSVForm table = tables.DeviceRoleTable default_return_url = 'dcim:devicerole_list' class DeviceRoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicerole' queryset = DeviceRole.objects.all() table = tables.DeviceRoleTable default_return_url = 'dcim:devicerole_list' # # Platforms # class PlatformListView(ObjectListView): queryset = Platform.objects.all() table = tables.PlatformTable template_name = 'dcim/platform_list.html' class PlatformCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_platform' model = Platform model_form = forms.PlatformForm default_return_url = 'dcim:platform_list' class PlatformEditView(PlatformCreateView): permission_required = 'dcim.change_platform' class PlatformBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_platform' model_form = forms.PlatformCSVForm table = tables.PlatformTable default_return_url = 'dcim:platform_list' class PlatformBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_platform' queryset = Platform.objects.all() table = tables.PlatformTable default_return_url = 'dcim:platform_list' # # Devices # class DeviceListView(ObjectListView): queryset = Device.objects.select_related( 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6' ) filter = filters.DeviceFilter filter_form = forms.DeviceFilterForm table = tables.DeviceDetailTable template_name = 'dcim/device_list.html' class DeviceView(View): def get(self, request, pk): device = get_object_or_404(Device.objects.select_related( 'site__region', 'rack__group', 'tenant__group', 'device_role', 'platform' ), pk=pk) # VirtualChassis members if device.virtual_chassis is not None: vc_members = Device.objects.filter( virtual_chassis=device.virtual_chassis ).order_by('vc_position') else: vc_members = [] # Console ports console_ports = device.consoleports.select_related('connected_endpoint__device', 'cable') # Console server ports consoleserverports = device.consoleserverports.select_related('connected_endpoint__device', 'cable') # Power ports power_ports = device.powerports.select_related('connected_endpoint__device', 'cable') # Power outlets poweroutlets = device.poweroutlets.select_related('connected_endpoint__device', 'cable') # Interfaces interfaces = device.vc_interfaces.select_related( 'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable' ).prefetch_related( 'cable__termination_a', 'cable__termination_b', 'ip_addresses' ) # Front ports front_ports = device.frontports.select_related('rear_port', 'cable') # Rear ports rear_ports = device.rearports.select_related('cable') # Device bays device_bays = device.device_bays.select_related('installed_device__device_type__manufacturer') # Services services = device.services.all() # Secrets secrets = device.secrets.all() # Find up to ten devices in the same site with the same functional role for quick reference. related_devices = Device.objects.filter( site=device.site, device_role=device.device_role ).exclude( pk=device.pk ).select_related( 'rack', 'device_type__manufacturer' )[:10] # Show graph button on interfaces only if at least one graph has been created. show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists() return render(request, 'dcim/device.html', { 'device': device, 'console_ports': console_ports, 'consoleserverports': consoleserverports, 'power_ports': power_ports, 'poweroutlets': poweroutlets, 'interfaces': interfaces, 'device_bays': device_bays, 'front_ports': front_ports, 'rear_ports': rear_ports, 'services': services, 'secrets': secrets, 'vc_members': vc_members, 'related_devices': related_devices, 'show_graphs': show_graphs, }) class DeviceInventoryView(View): def get(self, request, pk): device = get_object_or_404(Device, pk=pk) inventory_items = InventoryItem.objects.filter( device=device, parent=None ).select_related( 'manufacturer' ).prefetch_related( 'child_items' ) return render(request, 'dcim/device_inventory.html', { 'device': device, 'inventory_items': inventory_items, 'active_tab': 'inventory', }) class DeviceStatusView(PermissionRequiredMixin, View): permission_required = 'dcim.napalm_read' def get(self, request, pk): device = get_object_or_404(Device, pk=pk) return render(request, 'dcim/device_status.html', { 'device': device, 'active_tab': 'status', }) class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): permission_required = 'dcim.napalm_read' def get(self, request, pk): device = get_object_or_404(Device, pk=pk) interfaces = device.vc_interfaces.connectable().select_related( '_connected_interface__device' ) return render(request, 'dcim/device_lldp_neighbors.html', { 'device': device, 'interfaces': interfaces, 'active_tab': 'lldp-neighbors', }) class DeviceConfigView(PermissionRequiredMixin, View): permission_required = 'dcim.napalm_read' def get(self, request, pk): device = get_object_or_404(Device, pk=pk) return render(request, 'dcim/device_config.html', { 'device': device, 'active_tab': 'config', }) class DeviceConfigContextView(ObjectConfigContextView): object_class = Device base_template = 'dcim/device.html' class DeviceCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_device' model = Device model_form = forms.DeviceForm template_name = 'dcim/device_edit.html' default_return_url = 'dcim:device_list' class DeviceEditView(DeviceCreateView): permission_required = 'dcim.change_device' class DeviceDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_device' model = Device default_return_url = 'dcim:device_list' class DeviceBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_device' model_form = forms.DeviceCSVForm table = tables.DeviceImportTable template_name = 'dcim/device_import.html' default_return_url = 'dcim:device_list' class ChildDeviceBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_device' model_form = forms.ChildDeviceCSVForm table = tables.DeviceImportTable template_name = 'dcim/device_import_child.html' default_return_url = 'dcim:device_list' def _save_obj(self, obj_form): obj = obj_form.save() # Save the reverse relation to the parent device bay device_bay = obj.parent_bay device_bay.installed_device = obj device_bay.save() return obj class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_device' queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') filter = filters.DeviceFilter table = tables.DeviceTable form = forms.DeviceBulkEditForm default_return_url = 'dcim:device_list' class DeviceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_device' queryset = Device.objects.select_related('tenant', 'site', 'rack', 'device_role', 'device_type__manufacturer') filter = filters.DeviceFilter table = tables.DeviceTable default_return_url = 'dcim:device_list' # # Console ports # class ConsolePortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_consoleport' parent_model = Device parent_field = 'device' model = ConsolePort form = forms.ConsolePortCreateForm model_form = forms.ConsolePortForm template_name = 'dcim/device_component_add.html' class ConsolePortEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_consoleport' model = ConsolePort model_form = forms.ConsolePortForm class ConsolePortDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_consoleport' model = ConsolePort class ConsolePortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleport' queryset = ConsolePort.objects.all() parent_model = Device table = tables.ConsolePortTable # # Console server ports # class ConsoleServerPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_consoleserverport' parent_model = Device parent_field = 'device' model = ConsoleServerPort form = forms.ConsoleServerPortCreateForm model_form = forms.ConsoleServerPortForm template_name = 'dcim/device_component_add.html' class ConsoleServerPortEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_consoleserverport' model = ConsoleServerPort model_form = forms.ConsoleServerPortForm class ConsoleServerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_consoleserverport' model = ConsoleServerPort class ConsoleServerPortBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_consoleserverport' queryset = ConsoleServerPort.objects.all() form = forms.ConsoleServerPortBulkRenameForm class ConsoleServerPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): permission_required = 'dcim.change_consoleserverport' model = ConsoleServerPort form = forms.ConsoleServerPortBulkDisconnectForm class ConsoleServerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_consoleserverport' queryset = ConsoleServerPort.objects.all() parent_model = Device table = tables.ConsoleServerPortTable # # Power ports # class PowerPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_powerport' parent_model = Device parent_field = 'device' model = PowerPort form = forms.PowerPortCreateForm model_form = forms.PowerPortForm template_name = 'dcim/device_component_add.html' class PowerPortEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_powerport' model = PowerPort model_form = forms.PowerPortForm class PowerPortDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_powerport' model = PowerPort class PowerPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_powerport' queryset = PowerPort.objects.all() parent_model = Device table = tables.PowerPortTable # # Power outlets # class PowerOutletCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_poweroutlet' parent_model = Device parent_field = 'device' model = PowerOutlet form = forms.PowerOutletCreateForm model_form = forms.PowerOutletForm template_name = 'dcim/device_component_add.html' class PowerOutletEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_poweroutlet' model = PowerOutlet model_form = forms.PowerOutletForm class PowerOutletDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_poweroutlet' model = PowerOutlet class PowerOutletBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_poweroutlet' queryset = PowerOutlet.objects.all() form = forms.PowerOutletBulkRenameForm class PowerOutletBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): permission_required = 'dcim.change_poweroutlet' model = PowerOutlet form = forms.PowerOutletBulkDisconnectForm class PowerOutletBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_poweroutlet' queryset = PowerOutlet.objects.all() parent_model = Device table = tables.PowerOutletTable # # Interfaces # class InterfaceView(View): def get(self, request, pk): interface = get_object_or_404(Interface, pk=pk) # Get assigned IP addresses ipaddress_table = InterfaceIPAddressTable( data=interface.ip_addresses.select_related('vrf', 'tenant'), orderable=False ) # Get assigned VLANs and annotate whether each is tagged or untagged vlans = [] if interface.untagged_vlan is not None: vlans.append(interface.untagged_vlan) vlans[0].tagged = False for vlan in interface.tagged_vlans.select_related('site', 'group', 'tenant', 'role'): vlan.tagged = True vlans.append(vlan) vlan_table = InterfaceVLANTable( interface=interface, data=vlans, orderable=False ) return render(request, 'dcim/interface.html', { 'interface': interface, 'connected_interface': interface._connected_interface, 'connected_circuittermination': interface._connected_circuittermination, 'ipaddress_table': ipaddress_table, 'vlan_table': vlan_table, }) class InterfaceCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_interface' parent_model = Device parent_field = 'device' model = Interface form = forms.InterfaceCreateForm model_form = forms.InterfaceForm template_name = 'dcim/device_component_add.html' class InterfaceEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_interface' model = Interface model_form = forms.InterfaceForm template_name = 'dcim/interface_edit.html' class InterfaceAssignVLANsView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_interface' model = Interface model_form = forms.InterfaceAssignVLANsForm class InterfaceDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_interface' model = Interface class InterfaceBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_interface' queryset = Interface.objects.all() parent_model = Device table = tables.InterfaceTable form = forms.InterfaceBulkEditForm class InterfaceBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_interface' queryset = Interface.objects.all() form = forms.InterfaceBulkRenameForm class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): permission_required = 'dcim.change_interface' model = Interface form = forms.InterfaceBulkDisconnectForm class InterfaceBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_interface' queryset = Interface.objects.all() parent_model = Device table = tables.InterfaceTable # # Front ports # class FrontPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_frontport' parent_model = Device parent_field = 'device' model = FrontPort form = forms.FrontPortCreateForm model_form = forms.FrontPortForm template_name = 'dcim/device_component_add.html' class FrontPortEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_frontport' model = FrontPort model_form = forms.FrontPortForm class FrontPortDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_frontport' model = FrontPort class FrontPortBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_frontport' queryset = FrontPort.objects.all() form = forms.FrontPortBulkRenameForm class FrontPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): permission_required = 'dcim.change_frontport' model = FrontPort form = forms.FrontPortBulkDisconnectForm class FrontPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_frontport' queryset = FrontPort.objects.all() parent_model = Device table = tables.FrontPortTable # # Rear ports # class RearPortCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_rearport' parent_model = Device parent_field = 'device' model = RearPort form = forms.RearPortCreateForm model_form = forms.RearPortForm template_name = 'dcim/device_component_add.html' class RearPortEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_rearport' model = RearPort model_form = forms.RearPortForm class RearPortDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_rearport' model = RearPort class RearPortBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_rearport' queryset = RearPort.objects.all() form = forms.RearPortBulkRenameForm class RearPortBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView): permission_required = 'dcim.change_rearport' model = RearPort form = forms.RearPortBulkDisconnectForm class RearPortBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_rearport' queryset = RearPort.objects.all() parent_model = Device table = tables.RearPortTable # # Device bays # class DeviceBayCreateView(PermissionRequiredMixin, ComponentCreateView): permission_required = 'dcim.add_devicebay' parent_model = Device parent_field = 'device' model = DeviceBay form = forms.DeviceBayCreateForm model_form = forms.DeviceBayForm template_name = 'dcim/device_component_add.html' class DeviceBayEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_devicebay' model = DeviceBay model_form = forms.DeviceBayForm class DeviceBayDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_devicebay' model = DeviceBay class DeviceBayPopulateView(PermissionRequiredMixin, View): permission_required = 'dcim.change_devicebay' def get(self, request, pk): device_bay = get_object_or_404(DeviceBay, pk=pk) form = forms.PopulateDeviceBayForm(device_bay) return render(request, 'dcim/devicebay_populate.html', { 'device_bay': device_bay, 'form': form, 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}), }) def post(self, request, pk): device_bay = get_object_or_404(DeviceBay, pk=pk) form = forms.PopulateDeviceBayForm(device_bay, request.POST) if form.is_valid(): device_bay.installed_device = form.cleaned_data['installed_device'] device_bay.save() messages.success(request, "Added {} to {}.".format(device_bay.installed_device, device_bay)) return redirect('dcim:device', pk=device_bay.device.pk) return render(request, 'dcim/devicebay_populate.html', { 'device_bay': device_bay, 'form': form, 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}), }) class DeviceBayDepopulateView(PermissionRequiredMixin, View): permission_required = 'dcim.change_devicebay' def get(self, request, pk): device_bay = get_object_or_404(DeviceBay, pk=pk) form = ConfirmationForm() return render(request, 'dcim/devicebay_depopulate.html', { 'device_bay': device_bay, 'form': form, 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}), }) def post(self, request, pk): device_bay = get_object_or_404(DeviceBay, pk=pk) form = ConfirmationForm(request.POST) if form.is_valid(): removed_device = device_bay.installed_device device_bay.installed_device = None device_bay.save() messages.success(request, "{} has been removed from {}.".format(removed_device, device_bay)) return redirect('dcim:device', pk=device_bay.device.pk) return render(request, 'dcim/devicebay_depopulate.html', { 'device_bay': device_bay, 'form': form, 'return_url': reverse('dcim:device', kwargs={'pk': device_bay.device.pk}), }) class DeviceBayBulkRenameView(PermissionRequiredMixin, BulkRenameView): permission_required = 'dcim.change_devicebay' queryset = DeviceBay.objects.all() form = forms.DeviceBayBulkRenameForm class DeviceBayBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_devicebay' queryset = DeviceBay.objects.all() parent_model = Device table = tables.DeviceBayTable # # Bulk Device component creation # class DeviceBulkAddConsolePortView(PermissionRequiredMixin, BulkComponentCreateView): permission_required = 'dcim.add_consoleport' parent_model = Device parent_field = 'device' form = forms.DeviceBulkAddComponentForm model = ConsolePort model_form = forms.ConsolePortForm table = tables.DeviceTable default_return_url = 'dcim:device_list' class DeviceBulkAddConsoleServerPortView(PermissionRequiredMixin, BulkComponentCreateView): permission_required = 'dcim.add_consoleserverport' parent_model = Device parent_field = 'device' form = forms.DeviceBulkAddComponentForm model = ConsoleServerPort model_form = forms.ConsoleServerPortForm table = tables.DeviceTable default_return_url = 'dcim:device_list' class DeviceBulkAddPowerPortView(PermissionRequiredMixin, BulkComponentCreateView): permission_required = 'dcim.add_powerport' parent_model = Device parent_field = 'device' form = forms.DeviceBulkAddComponentForm model = PowerPort model_form = forms.PowerPortForm table = tables.DeviceTable default_return_url = 'dcim:device_list' class DeviceBulkAddPowerOutletView(PermissionRequiredMixin, BulkComponentCreateView): permission_required = 'dcim.add_poweroutlet' parent_model = Device parent_field = 'device' form = forms.DeviceBulkAddComponentForm model = PowerOutlet model_form = forms.PowerOutletForm table = tables.DeviceTable default_return_url = 'dcim:device_list' class DeviceBulkAddInterfaceView(PermissionRequiredMixin, BulkComponentCreateView): permission_required = 'dcim.add_interface' parent_model = Device parent_field = 'device' form = forms.DeviceBulkAddInterfaceForm model = Interface model_form = forms.InterfaceForm table = tables.DeviceTable default_return_url = 'dcim:device_list' class DeviceBulkAddDeviceBayView(PermissionRequiredMixin, BulkComponentCreateView): permission_required = 'dcim.add_devicebay' parent_model = Device parent_field = 'device' form = forms.DeviceBulkAddComponentForm model = DeviceBay model_form = forms.DeviceBayForm table = tables.DeviceTable default_return_url = 'dcim:device_list' # # Cables # class CableListView(ObjectListView): queryset = Cable.objects.prefetch_related( 'termination_a', 'termination_b' ) filter = filters.CableFilter filter_form = forms.CableFilterForm table = tables.CableTable template_name = 'dcim/cable_list.html' class CableView(View): def get(self, request, pk): cable = get_object_or_404(Cable, pk=pk) return render(request, 'dcim/cable.html', { 'cable': cable, }) class CableTraceView(View): """ Trace a cable path beginning from the given termination. """ def get(self, request, model, pk): obj = get_object_or_404(model, pk=pk) return render(request, 'dcim/cable_trace.html', { 'obj': obj, 'trace': obj.trace(follow_circuits=True), }) class CableCreateView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.add_cable' model = Cable model_form = forms.CableCreateForm template_name = 'dcim/cable_connect.html' def alter_obj(self, obj, request, url_args, url_kwargs): # Retrieve endpoint A based on the given type and PK termination_a_type = url_kwargs.get('termination_a_type') termination_a_id = url_kwargs.get('termination_a_id') obj.termination_a = termination_a_type.objects.get(pk=termination_a_id) return obj class CableEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_cable' model = Cable model_form = forms.CableForm template_name = 'dcim/cable_edit.html' default_return_url = 'dcim:cable_list' class CableDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_cable' model = Cable default_return_url = 'dcim:cable_list' class CableBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_cable' model_form = forms.CableCSVForm table = tables.CableTable default_return_url = 'dcim:cable_list' class CableBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_cable' queryset = Cable.objects.prefetch_related('termination_a', 'termination_b') filter = filters.CableFilter table = tables.CableTable form = forms.CableBulkEditForm default_return_url = 'dcim:cable_list' class CableBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_cable' queryset = Cable.objects.prefetch_related('termination_a', 'termination_b') filter = filters.CableFilter table = tables.CableTable default_return_url = 'dcim:cable_list' # # Connections # class ConsoleConnectionsListView(ObjectListView): queryset = ConsoleConnection.objects.select_related( 'device', 'connected_endpoint__device' ).filter( connected_endpoint__isnull=False ).order_by( 'cable', 'connected_endpoint__device__name', 'connected_endpoint__name' ) filter = filters.ConsoleConnectionFilter filter_form = forms.ConsoleConnectionFilterForm table = tables.ConsoleConnectionTable template_name = 'dcim/console_connections_list.html' class PowerConnectionsListView(ObjectListView): queryset = PowerConnection.objects.select_related( 'device', 'connected_endpoint__device' ).filter( connected_endpoint__isnull=False ).order_by( 'cable', 'connected_endpoint__device__name', 'connected_endpoint__name' ) filter = filters.PowerConnectionFilter filter_form = forms.PowerConnectionFilterForm table = tables.PowerConnectionTable template_name = 'dcim/power_connections_list.html' class InterfaceConnectionsListView(ObjectListView): queryset = InterfaceConnection.objects.select_related( 'device', 'cable', '_connected_interface__device' ).filter( # Avoid duplicate connections by only selecting the lower PK in a connected pair _connected_interface__isnull=False, pk__lt=F('_connected_interface') ).order_by( 'device' ) filter = filters.InterfaceConnectionFilter filter_form = forms.InterfaceConnectionFilterForm table = tables.InterfaceConnectionTable template_name = 'dcim/interface_connections_list.html' # # Inventory items # class InventoryItemListView(ObjectListView): queryset = InventoryItem.objects.select_related('device', 'manufacturer') filter = filters.InventoryItemFilter filter_form = forms.InventoryItemFilterForm table = tables.InventoryItemTable template_name = 'dcim/inventoryitem_list.html' class InventoryItemEditView(PermissionRequiredMixin, ObjectEditView): permission_required = 'dcim.change_inventoryitem' model = InventoryItem model_form = forms.InventoryItemForm def alter_obj(self, obj, request, url_args, url_kwargs): if 'device' in url_kwargs: obj.device = get_object_or_404(Device, pk=url_kwargs['device']) return obj def get_return_url(self, request, obj): return reverse('dcim:device_inventory', kwargs={'pk': obj.device.pk}) class InventoryItemDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_inventoryitem' model = InventoryItem class InventoryItemBulkImportView(PermissionRequiredMixin, BulkImportView): permission_required = 'dcim.add_inventoryitem' model_form = forms.InventoryItemCSVForm table = tables.InventoryItemTable default_return_url = 'dcim:inventoryitem_list' class InventoryItemBulkEditView(PermissionRequiredMixin, BulkEditView): permission_required = 'dcim.change_inventoryitem' queryset = InventoryItem.objects.select_related('device', 'manufacturer') filter = filters.InventoryItemFilter table = tables.InventoryItemTable form = forms.InventoryItemBulkEditForm default_return_url = 'dcim:inventoryitem_list' class InventoryItemBulkDeleteView(PermissionRequiredMixin, BulkDeleteView): permission_required = 'dcim.delete_inventoryitem' queryset = InventoryItem.objects.select_related('device', 'manufacturer') table = tables.InventoryItemTable template_name = 'dcim/inventoryitem_bulk_delete.html' default_return_url = 'dcim:inventoryitem_list' # # Virtual chassis # class VirtualChassisListView(ObjectListView): queryset = VirtualChassis.objects.select_related('master').annotate(member_count=Count('members')) table = tables.VirtualChassisTable filter = filters.VirtualChassisFilter filter_form = forms.VirtualChassisFilterForm template_name = 'dcim/virtualchassis_list.html' class VirtualChassisCreateView(PermissionRequiredMixin, View): permission_required = 'dcim.add_virtualchassis' def post(self, request): # Get the list of devices being added to a VirtualChassis pk_form = forms.DeviceSelectionForm(request.POST) pk_form.full_clean() if not pk_form.cleaned_data.get('pk'): messages.warning(request, "No devices were selected.") return redirect('dcim:device_list') device_queryset = Device.objects.filter( pk__in=pk_form.cleaned_data.get('pk') ).select_related('rack').order_by('vc_position') VCMemberFormSet = modelformset_factory( model=Device, formset=forms.BaseVCMemberFormSet, form=forms.DeviceVCMembershipForm, extra=0 ) if '_create' in request.POST: vc_form = forms.VirtualChassisForm(request.POST) vc_form.fields['master'].queryset = device_queryset formset = VCMemberFormSet(request.POST, queryset=device_queryset) if vc_form.is_valid() and formset.is_valid(): with transaction.atomic(): # Assign each device to the VirtualChassis before saving virtual_chassis = vc_form.save() devices = formset.save(commit=False) for device in devices: device.virtual_chassis = virtual_chassis device.save() return redirect(vc_form.cleaned_data['master'].get_absolute_url()) else: vc_form = forms.VirtualChassisForm() vc_form.fields['master'].queryset = device_queryset formset = VCMemberFormSet(queryset=device_queryset) return render(request, 'dcim/virtualchassis_edit.html', { 'pk_form': pk_form, 'vc_form': vc_form, 'formset': formset, 'return_url': reverse('dcim:device_list'), }) class VirtualChassisEditView(PermissionRequiredMixin, GetReturnURLMixin, View): permission_required = 'dcim.change_virtualchassis' def get(self, request, pk): virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) VCMemberFormSet = modelformset_factory( model=Device, form=forms.DeviceVCMembershipForm, formset=forms.BaseVCMemberFormSet, extra=0 ) members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position') vc_form = forms.VirtualChassisForm(instance=virtual_chassis) vc_form.fields['master'].queryset = members_queryset formset = VCMemberFormSet(queryset=members_queryset) return render(request, 'dcim/virtualchassis_edit.html', { 'vc_form': vc_form, 'formset': formset, 'return_url': self.get_return_url(request, virtual_chassis), }) def post(self, request, pk): virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) VCMemberFormSet = modelformset_factory( model=Device, form=forms.DeviceVCMembershipForm, formset=forms.BaseVCMemberFormSet, extra=0 ) members_queryset = virtual_chassis.members.select_related('rack').order_by('vc_position') vc_form = forms.VirtualChassisForm(request.POST, instance=virtual_chassis) vc_form.fields['master'].queryset = members_queryset formset = VCMemberFormSet(request.POST, queryset=members_queryset) if vc_form.is_valid() and formset.is_valid(): with transaction.atomic(): # Save the VirtualChassis vc_form.save() # Nullify the vc_position of each member first to allow reordering without raising an IntegrityError on # duplicate positions. Then save each member instance. members = formset.save(commit=False) Device.objects.filter(pk__in=[m.pk for m in members]).update(vc_position=None) for member in members: member.save() return redirect(vc_form.cleaned_data['master'].get_absolute_url()) return render(request, 'dcim/virtualchassis_edit.html', { 'vc_form': vc_form, 'formset': formset, 'return_url': self.get_return_url(request, virtual_chassis), }) class VirtualChassisDeleteView(PermissionRequiredMixin, ObjectDeleteView): permission_required = 'dcim.delete_virtualchassis' model = VirtualChassis default_return_url = 'dcim:device_list' class VirtualChassisAddMemberView(PermissionRequiredMixin, GetReturnURLMixin, View): permission_required = 'dcim.change_virtualchassis' def get(self, request, pk): virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) initial_data = {k: request.GET[k] for k in request.GET} member_select_form = forms.VCMemberSelectForm(initial=initial_data) membership_form = forms.DeviceVCMembershipForm(initial=initial_data) return render(request, 'dcim/virtualchassis_add_member.html', { 'virtual_chassis': virtual_chassis, 'member_select_form': member_select_form, 'membership_form': membership_form, 'return_url': self.get_return_url(request, virtual_chassis), }) def post(self, request, pk): virtual_chassis = get_object_or_404(VirtualChassis, pk=pk) member_select_form = forms.VCMemberSelectForm(request.POST) if member_select_form.is_valid(): device = member_select_form.cleaned_data['device'] device.virtual_chassis = virtual_chassis data = {k: request.POST[k] for k in ['vc_position', 'vc_priority']} membership_form = forms.DeviceVCMembershipForm(data=data, validate_vc_position=True, instance=device) if membership_form.is_valid(): membership_form.save() msg = 'Added member {}'.format(device.get_absolute_url(), escape(device)) messages.success(request, mark_safe(msg)) if '_addanother' in request.POST: return redirect(request.get_full_path()) return redirect(self.get_return_url(request, device)) else: membership_form = forms.DeviceVCMembershipForm(data=request.POST) return render(request, 'dcim/virtualchassis_add_member.html', { 'virtual_chassis': virtual_chassis, 'member_select_form': member_select_form, 'membership_form': membership_form, 'return_url': self.get_return_url(request, virtual_chassis), }) class VirtualChassisRemoveMemberView(PermissionRequiredMixin, GetReturnURLMixin, View): permission_required = 'dcim.change_virtualchassis' def get(self, request, pk): device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False) form = ConfirmationForm(initial=request.GET) return render(request, 'dcim/virtualchassis_remove_member.html', { 'device': device, 'form': form, 'return_url': self.get_return_url(request, device), }) def post(self, request, pk): device = get_object_or_404(Device, pk=pk, virtual_chassis__isnull=False) form = ConfirmationForm(request.POST) # Protect master device from being removed virtual_chassis = VirtualChassis.objects.filter(master=device).first() if virtual_chassis is not None: msg = 'Unable to remove master device {} from the virtual chassis.'.format(escape(device)) messages.error(request, mark_safe(msg)) return redirect(device.get_absolute_url()) if form.is_valid(): Device.objects.filter(pk=device.pk).update( virtual_chassis=None, vc_position=None, vc_priority=None ) msg = 'Removed {} from virtual chassis {}'.format(device, device.virtual_chassis) messages.success(request, msg) return redirect(self.get_return_url(request, device)) return render(request, 'dcim/virtualchassis_remove_member.html', { 'device': device, 'form': form, 'return_url': self.get_return_url(request, device), })