diff --git a/netbox/dcim/api/views.py b/netbox/dcim/api/views.py index 12774e4be..287bf634e 100644 --- a/netbox/dcim/api/views.py +++ b/netbox/dcim/api/views.py @@ -1,6 +1,7 @@ from collections import OrderedDict from django.conf import settings +from django.contrib.contenttypes.models import ContentType from django.db.models import Count, F from django.http import HttpResponseForbidden from django.shortcuts import get_object_or_404 @@ -462,7 +463,7 @@ class PowerOutletViewSet(CableTraceMixin, ModelViewSet): class InterfaceViewSet(CableTraceMixin, ModelViewSet): queryset = Interface.objects.prefetch_related( - 'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags' + 'device', 'connected_endpoint', 'cable', 'ip_addresses', 'tags' ).filter( device__isnull=False ) @@ -530,11 +531,13 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet): class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet): queryset = Interface.objects.prefetch_related( - 'device', '_connected_interface__device' + 'device', 'connected_endpoint__device' ).filter( # Avoid duplicate connections by only selecting the lower PK in a connected pair - _connected_interface__isnull=False, - pk__lt=F('_connected_interface') + connected_endpoint_type=ContentType.objects.get_for_model(Interface), + pk__lt=F('connected_endpoint_id') + ).order_by( + 'device' ) serializer_class = serializers.InterfaceConnectionSerializer filterset_class = filters.InterfaceConnectionFilter @@ -634,9 +637,9 @@ class ConnectedDeviceViewSet(ViewSet): # Determine local interface from peer interface's connection peer_interface = get_object_or_404(Interface, device__name=peer_device_name, name=peer_interface_name) - local_interface = peer_interface._connected_interface + local_interface = peer_interface.connected_endpoint - if local_interface is None: + if not isinstance(local_interface, Interface): return Response() return Response(serializers.DeviceSerializer(local_interface.device, context={'request': request}).data) diff --git a/netbox/dcim/filters.py b/netbox/dcim/filters.py index c1274e3d5..c448d999d 100644 --- a/netbox/dcim/filters.py +++ b/netbox/dcim/filters.py @@ -1046,7 +1046,7 @@ class InterfaceConnectionFilter(django_filters.FilterSet): return queryset return queryset.filter( Q(device__site__slug=value) | - Q(_connected_interface__device__site__slug=value) + Q(connected_interface__device__site__slug=value) ) def filter_device(self, queryset, name, value): @@ -1054,7 +1054,7 @@ class InterfaceConnectionFilter(django_filters.FilterSet): return queryset return queryset.filter( Q(device__name__icontains=value) | - Q(_connected_interface__device__name__icontains=value) + Q(connected_interface__device__name__icontains=value) ) diff --git a/netbox/dcim/migrations/0075_generic_connected_endpoint.py b/netbox/dcim/migrations/0075_generic_connected_endpoint.py new file mode 100644 index 000000000..c34fad6a6 --- /dev/null +++ b/netbox/dcim/migrations/0075_generic_connected_endpoint.py @@ -0,0 +1,62 @@ +# Generated by Django 2.2.5 on 2019-10-06 19:01 + +import django.db.models.deletion +from django.db import migrations, models + + +def connected_interface_to_endpoint(apps, schema_editor): + Interface = apps.get_model('dcim', 'Interface') + ContentType = apps.get_model('contenttypes', 'ContentType') + + model_type = ContentType.objects.get_for_model(Interface) + for interface in Interface.objects.exclude(_connected_interface=None): + interface.connected_endpoint_type = model_type + interface.connected_endpoint_id = interface._connected_interface.pk + interface.save() + + +def connected_circuittermination_to_endpoint(apps, schema_editor): + Interface = apps.get_model('dcim', 'Interface') + ContentType = apps.get_model('contenttypes', 'ContentType') + CircuitTermination = apps.get_model('circuits', 'CircuitTermination') + + model_type = ContentType.objects.get_for_model(CircuitTermination) + for interface in Interface.objects.exclude(_connected_circuittermination=None): + interface.connected_endpoint_type = model_type + interface.connected_endpoint_id = interface._connected_circuittermination.pk + interface.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('dcim', '0074_increase_field_length_platform_name_slug'), + ] + + operations = [ + migrations.AddField( + model_name='interface', + name='connected_endpoint_id', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='interface', + name='connected_endpoint_type', + field=models.ForeignKey(blank=True, limit_choices_to={ + 'model__in': ['consoleport', 'consoleserverport', 'interface', 'poweroutlet', 'powerport', 'frontport', + 'rearport', 'circuittermination'] + }, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='contenttypes.ContentType'), + ), + + migrations.RunPython(connected_interface_to_endpoint), + migrations.RunPython(connected_circuittermination_to_endpoint), + + migrations.RemoveField( + model_name='interface', + name='_connected_circuittermination', + ), + migrations.RemoveField( + model_name='interface', + name='_connected_interface', + ), + ] diff --git a/netbox/dcim/models.py b/netbox/dcim/models.py index 03f560a59..254b6b1b9 100644 --- a/netbox/dcim/models.py +++ b/netbox/dcim/models.py @@ -2169,20 +2169,30 @@ class Interface(CableTermination, ComponentModel): name = models.CharField( max_length=64 ) - _connected_interface = models.OneToOneField( + + connected_endpoint_type = models.ForeignKey( + to=ContentType, + limit_choices_to={'model__in': CABLE_TERMINATION_TYPES}, + on_delete=models.PROTECT, + related_name='+', + blank = True, + null = True + ) + connected_endpoint_id = models.PositiveIntegerField( + blank=True, + null=True + ) + connected_endpoint = GenericForeignKey( + ct_field='connected_endpoint_type', + fk_field='connected_endpoint_id' + ) + + connected_interface = GenericRelation( to='self', - on_delete=models.SET_NULL, - related_name='+', - blank=True, - null=True - ) - _connected_circuittermination = models.OneToOneField( - to='circuits.CircuitTermination', - on_delete=models.SET_NULL, - related_name='+', - blank=True, - null=True + object_id_field='connected_endpoint_id', + content_type_field='connected_endpoint_type', ) + connection_status = models.NullBooleanField( choices=CONNECTION_STATUS_CHOICES, blank=True @@ -2366,30 +2376,6 @@ class Interface(CableTermination, ComponentModel): """ self.type = value - @property - def connected_endpoint(self): - if self._connected_interface: - return self._connected_interface - return self._connected_circuittermination - - @connected_endpoint.setter - def connected_endpoint(self, value): - from circuits.models import CircuitTermination - - if value is None: - self._connected_interface = None - self._connected_circuittermination = None - elif isinstance(value, Interface): - self._connected_interface = value - self._connected_circuittermination = None - elif isinstance(value, CircuitTermination): - self._connected_interface = None - self._connected_circuittermination = value - else: - raise ValueError( - "Connected endpoint must be an Interface or CircuitTermination, not {}.".format(type(value)) - ) - @property def parent(self): return self.device or self.virtual_machine diff --git a/netbox/dcim/tables.py b/netbox/dcim/tables.py index 70a9aa5c8..3850c3531 100644 --- a/netbox/dcim/tables.py +++ b/netbox/dcim/tables.py @@ -822,18 +822,18 @@ class InterfaceConnectionTable(BaseTable): ) device_b = tables.LinkColumn( viewname='dcim:device', - accessor=Accessor('_connected_interface.device'), - args=[Accessor('_connected_interface.device.pk')], + accessor=Accessor('connected_endpoint.device'), + args=[Accessor('connected_endpoint.device.pk')], verbose_name='Device B' ) interface_b = tables.LinkColumn( viewname='dcim:interface', - accessor=Accessor('_connected_interface'), - args=[Accessor('_connected_interface.pk')], + accessor=Accessor('connected_endpoint'), + args=[Accessor('connected_endpoint.pk')], verbose_name='Interface B' ) description_b = tables.Column( - accessor=Accessor('_connected_interface.description'), + accessor=Accessor('connected_endpoint.description'), verbose_name='Description' ) diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index 58c759822..8bc28bdc4 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -993,7 +993,7 @@ class DeviceView(PermissionRequiredMixin, View): # Interfaces interfaces = device.vc_interfaces.prefetch_related( - 'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable', + 'lag', 'connected_endpoint', 'cable', 'cable__termination_a', 'cable__termination_b', 'ip_addresses', 'tags' ) @@ -1079,7 +1079,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View): device = get_object_or_404(Device, pk=pk) interfaces = device.vc_interfaces.connectable().prefetch_related( - '_connected_interface__device' + 'connected_endpoint' ) return render(request, 'dcim/device_lldp_neighbors.html', { @@ -1371,8 +1371,7 @@ class InterfaceView(PermissionRequiredMixin, View): return render(request, 'dcim/interface.html', { 'interface': interface, - 'connected_interface': interface._connected_interface, - 'connected_circuittermination': interface._connected_circuittermination, + 'connected_endpoint': interface.connected_endpoint, 'ipaddress_table': ipaddress_table, 'vlan_table': vlan_table, }) @@ -1940,11 +1939,11 @@ class PowerConnectionsListView(PermissionRequiredMixin, ObjectListView): class InterfaceConnectionsListView(PermissionRequiredMixin, ObjectListView): permission_required = 'dcim.view_interface' queryset = Interface.objects.prefetch_related( - 'device', 'cable', '_connected_interface__device' + 'device', 'cable', 'connected_endpoint__device' ).filter( # Avoid duplicate connections by only selecting the lower PK in a connected pair - _connected_interface__isnull=False, - pk__lt=F('_connected_interface') + connected_endpoint_type=ContentType.objects.get_for_model(Interface), + pk__lt=F('connected_endpoint_id') ).order_by( 'device' ) diff --git a/netbox/extras/models.py b/netbox/extras/models.py index ea71cf95e..9d10ff31e 100644 --- a/netbox/extras/models.py +++ b/netbox/extras/models.py @@ -638,23 +638,16 @@ class TopologyMap(models.Model): # Add all interface connections to the graph connected_interfaces = Interface.objects.prefetch_related( - '_connected_interface__device' + 'connected_endpoint__device' ).filter( - Q(device__in=devices) | Q(_connected_interface__device__in=devices), - _connected_interface__isnull=False, - pk__lt=F('_connected_interface') + Q(device__in=devices) | Q(connected_interface__device__in=devices), + connected_endpoint_type=ContentType.objects.get_for_model(Interface), + pk__lt=F('connected_endpoint_id') ) for interface in connected_interfaces: style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed' self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style) - # Add all circuits to the graph - for termination in CircuitTermination.objects.filter(term_side='A', connected_endpoint__device__in=devices): - peer_termination = termination.get_peer_termination() - if (peer_termination is not None and peer_termination.interface is not None and - peer_termination.interface.device in devices): - self.graph.edge(termination.interface.device.name, peer_termination.interface.device.name, color='blue') - def add_console_connections(self, devices): from dcim.models import ConsolePort diff --git a/netbox/netbox/views.py b/netbox/netbox/views.py index 05036a37a..e99c0f70c 100644 --- a/netbox/netbox/views.py +++ b/netbox/netbox/views.py @@ -1,5 +1,6 @@ from collections import OrderedDict +from django.contrib.contenttypes.models import ContentType from django.db.models import Count, F from django.shortcuts import render from django.views.generic import View @@ -200,8 +201,8 @@ class HomeView(View): _connected_poweroutlet__isnull=False ) connected_interfaces = Interface.objects.filter( - _connected_interface__isnull=False, - pk__lt=F('_connected_interface') + connected_endpoint_type=ContentType.objects.get_for_model(Interface), + pk__lt=F('connected_endpoint_id') ) cables = Cable.objects.all() diff --git a/netbox/templates/dcim/interface.html b/netbox/templates/dcim/interface.html index 4e7cc6306..912a283a3 100644 --- a/netbox/templates/dcim/interface.html +++ b/netbox/templates/dcim/interface.html @@ -110,27 +110,27 @@ {% if interface.cable %} - {% if connected_interface %} + {% if connected_endpoint.device %} - + - + - + - + - + - {% elif connected_circuittermination %} - {% with ct=connected_circuittermination %} + {% elif connected_endpoint.circuit %} + {% with ct=connected_endpoint %}
Device - {{ connected_interface.device }} + {{ connected_endpoint.device }}
Name - {{ connected_interface.name }} + {{ connected_endpoint.name }}
Type{{ connected_interface.get_type_display }}{{ connected_endpoint.get_type_display }}
Enabled - {% if connected_interface.enabled %} + {% if connected_endpoint.enabled %} {% else %} @@ -140,8 +140,8 @@
LAG - {% if connected_interface.lag%} - {{ connected_interface.lag }} + {% if connected_endpoint.lag%} + {{ connected_endpoint.lag }} {% else %} None {% endif %} @@ -149,22 +149,22 @@
Description{{ connected_interface.description|placeholder }}{{ connected_endpoint.description|placeholder }}
MTU{{ connected_interface.mtu|placeholder }}{{ connected_endpoint.mtu|placeholder }}
MAC Address{{ connected_interface.mac_address|placeholder }}{{ connected_endpoint.mac_address|placeholder }}
802.1Q Mode{{ connected_interface.get_mode_display }}{{ connected_endpoint.get_mode_display }}
Provider {{ ct.circuit.provider }}