Просмотр исходного кода

Remove legacy connected endpoint fields

Jeremy Stretch 5 лет назад
Родитель
Сommit
079c42291c

+ 2 - 4
netbox/circuits/api/views.py

@@ -46,9 +46,7 @@ class CircuitTypeViewSet(ModelViewSet):
 
 
 class CircuitViewSet(CustomFieldModelViewSet):
 class CircuitViewSet(CustomFieldModelViewSet):
     queryset = Circuit.objects.prefetch_related(
     queryset = Circuit.objects.prefetch_related(
-        Prefetch('terminations', queryset=CircuitTermination.objects.prefetch_related(
-            'site', 'connected_endpoint__device'
-        )),
+        Prefetch('terminations', queryset=CircuitTermination.objects.prefetch_related('site')),
         'type', 'tenant', 'provider',
         'type', 'tenant', 'provider',
     ).prefetch_related('tags')
     ).prefetch_related('tags')
     serializer_class = serializers.CircuitSerializer
     serializer_class = serializers.CircuitSerializer
@@ -61,7 +59,7 @@ class CircuitViewSet(CustomFieldModelViewSet):
 
 
 class CircuitTerminationViewSet(ModelViewSet):
 class CircuitTerminationViewSet(ModelViewSet):
     queryset = CircuitTermination.objects.prefetch_related(
     queryset = CircuitTermination.objects.prefetch_related(
-        'circuit', 'site', 'connected_endpoint__device', 'cable'
+        'circuit', 'site', 'cable'
     )
     )
     serializer_class = serializers.CircuitTerminationSerializer
     serializer_class = serializers.CircuitTerminationSerializer
     filterset_class = filters.CircuitTerminationFilterSet
     filterset_class = filters.CircuitTerminationFilterSet

+ 17 - 0
netbox/circuits/migrations/0022_drop_connected_endpoint.py

@@ -0,0 +1,17 @@
+# Generated by Django 3.1 on 2020-10-05 13:56
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('circuits', '0021_cablepath'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='circuittermination',
+            name='connected_endpoint',
+        ),
+    ]

+ 0 - 7
netbox/circuits/models.py

@@ -248,13 +248,6 @@ class CircuitTermination(PathEndpoint, CableTermination):
         on_delete=models.PROTECT,
         on_delete=models.PROTECT,
         related_name='circuit_terminations'
         related_name='circuit_terminations'
     )
     )
-    connected_endpoint = models.OneToOneField(
-        to='dcim.Interface',
-        on_delete=models.SET_NULL,
-        related_name='+',
-        blank=True,
-        null=True
-    )
     connection_status = models.BooleanField(
     connection_status = models.BooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
         blank=True,

+ 2 - 2
netbox/circuits/views.py

@@ -131,7 +131,7 @@ class CircuitView(ObjectView):
         circuit = get_object_or_404(self.queryset, pk=pk)
         circuit = get_object_or_404(self.queryset, pk=pk)
 
 
         termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
         termination_a = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
-            'site__region', 'connected_endpoint__device'
+            'site__region'
         ).filter(
         ).filter(
             circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
             circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_A
         ).first()
         ).first()
@@ -139,7 +139,7 @@ class CircuitView(ObjectView):
             termination_a.ip_addresses = termination_a.connected_endpoint.ip_addresses.restrict(request.user, 'view')
             termination_a.ip_addresses = termination_a.connected_endpoint.ip_addresses.restrict(request.user, 'view')
 
 
         termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
         termination_z = CircuitTermination.objects.restrict(request.user, 'view').prefetch_related(
-            'site__region', 'connected_endpoint__device'
+            'site__region'
         ).filter(
         ).filter(
             circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
             circuit=circuit, term_side=CircuitTerminationSideChoices.SIDE_Z
         ).first()
         ).first()

+ 2 - 3
netbox/dcim/api/serializers.py

@@ -34,8 +34,7 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer):
 
 
     def get_connected_endpoint_type(self, obj):
     def get_connected_endpoint_type(self, obj):
         if obj.path is not None:
         if obj.path is not None:
-            destination = obj.path.destination
-            return f'{destination._meta.app_label}.{destination._meta.model_name}'
+            return f'{obj.connected_endpoint._meta.app_label}.{obj.connected_endpoint._meta.model_name}'
         return None
         return None
 
 
     @swagger_serializer_method(serializer_or_field=serializers.DictField)
     @swagger_serializer_method(serializer_or_field=serializers.DictField)
@@ -44,7 +43,7 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer):
         Return the appropriate serializer for the type of connected object.
         Return the appropriate serializer for the type of connected object.
         """
         """
         if obj.path is not None:
         if obj.path is not None:
-            serializer = get_serializer_for_model(obj.path.destination, prefix='Nested')
+            serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested')
             context = {'request': self.context['request']}
             context = {'request': self.context['request']}
             return serializer(obj.path.destination, context=context).data
             return serializer(obj.path.destination, context=context).data
         return None
         return None

+ 13 - 25
netbox/dcim/api/views.py

@@ -470,37 +470,31 @@ class DeviceViewSet(CustomFieldModelViewSet):
 #
 #
 
 
 class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
 class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
-    queryset = ConsolePort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
+    queryset = ConsolePort.objects.prefetch_related('device', '_path', 'cable', 'tags')
     serializer_class = serializers.ConsolePortSerializer
     serializer_class = serializers.ConsolePortSerializer
     filterset_class = filters.ConsolePortFilterSet
     filterset_class = filters.ConsolePortFilterSet
 
 
 
 
 class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
 class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
-    queryset = ConsoleServerPort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
+    queryset = ConsoleServerPort.objects.prefetch_related('device', '_path', 'cable', 'tags')
     serializer_class = serializers.ConsoleServerPortSerializer
     serializer_class = serializers.ConsoleServerPortSerializer
     filterset_class = filters.ConsoleServerPortFilterSet
     filterset_class = filters.ConsoleServerPortFilterSet
 
 
 
 
 class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
 class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
-    queryset = PowerPort.objects.prefetch_related(
-        'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable', 'tags'
-    )
+    queryset = PowerPort.objects.prefetch_related('device', '_path', 'cable', 'tags')
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
     filterset_class = filters.PowerPortFilterSet
     filterset_class = filters.PowerPortFilterSet
 
 
 
 
 class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
 class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
-    queryset = PowerOutlet.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
+    queryset = PowerOutlet.objects.prefetch_related('device', '_path', 'cable', 'tags')
     serializer_class = serializers.PowerOutletSerializer
     serializer_class = serializers.PowerOutletSerializer
     filterset_class = filters.PowerOutletFilterSet
     filterset_class = filters.PowerOutletFilterSet
 
 
 
 
 class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
 class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
-    queryset = Interface.objects.prefetch_related(
-        'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags'
-    ).filter(
-        device__isnull=False
-    )
+    queryset = Interface.objects.prefetch_related('device', '_path', 'cable', 'ip_addresses', 'tags')
     serializer_class = serializers.InterfaceSerializer
     serializer_class = serializers.InterfaceSerializer
     filterset_class = filters.InterfaceFilterSet
     filterset_class = filters.InterfaceFilterSet
 
 
@@ -534,32 +528,26 @@ class InventoryItemViewSet(ModelViewSet):
 #
 #
 
 
 class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
 class ConsoleConnectionViewSet(ListModelMixin, GenericViewSet):
-    queryset = ConsolePort.objects.prefetch_related(
-        'device', 'connected_endpoint__device'
-    ).filter(
-        connected_endpoint__isnull=False
+    queryset = ConsolePort.objects.prefetch_related('device', '_path').filter(
+        _path__destination_id__isnull=False
     )
     )
     serializer_class = serializers.ConsolePortSerializer
     serializer_class = serializers.ConsolePortSerializer
     filterset_class = filters.ConsoleConnectionFilterSet
     filterset_class = filters.ConsoleConnectionFilterSet
 
 
 
 
 class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
 class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
-    queryset = PowerPort.objects.prefetch_related(
-        'device', 'connected_endpoint__device'
-    ).filter(
-        _connected_poweroutlet__isnull=False
+    queryset = PowerPort.objects.prefetch_related('device', '_path').filter(
+        _path__destination_id__isnull=False
     )
     )
     serializer_class = serializers.PowerPortSerializer
     serializer_class = serializers.PowerPortSerializer
     filterset_class = filters.PowerConnectionFilterSet
     filterset_class = filters.PowerConnectionFilterSet
 
 
 
 
 class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
 class InterfaceConnectionViewSet(ListModelMixin, GenericViewSet):
-    queryset = Interface.objects.prefetch_related(
-        'device', '_connected_interface__device'
-    ).filter(
+    queryset = Interface.objects.prefetch_related('device', '_path').filter(
         # Avoid duplicate connections by only selecting the lower PK in a connected pair
         # Avoid duplicate connections by only selecting the lower PK in a connected pair
-        _connected_interface__isnull=False,
-        pk__lt=F('_connected_interface')
+        _path__destination_id__isnull=False,
+        pk__lt=F('_path__destination_id')
     )
     )
     serializer_class = serializers.InterfaceConnectionSerializer
     serializer_class = serializers.InterfaceConnectionSerializer
     filterset_class = filters.InterfaceConnectionFilterSet
     filterset_class = filters.InterfaceConnectionFilterSet
@@ -664,7 +652,7 @@ class ConnectedDeviceViewSet(ViewSet):
             device__name=peer_device_name,
             device__name=peer_device_name,
             name=peer_interface_name
             name=peer_interface_name
         )
         )
-        local_interface = peer_interface._connected_interface
+        local_interface = peer_interface.connected_endpoint
 
 
         if local_interface is None:
         if local_interface is None:
             return Response()
             return Response()

+ 42 - 39
netbox/dcim/filters.py

@@ -1171,18 +1171,19 @@ class ConsoleConnectionFilterSet(BaseFilterSet):
         model = ConsolePort
         model = ConsolePort
         fields = ['name', 'connection_status']
         fields = ['name', 'connection_status']
 
 
-    def filter_site(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(connected_endpoint__device__site__slug=value)
-
-    def filter_device(self, queryset, name, value):
-        if not value:
-            return queryset
-        return queryset.filter(
-            Q(**{'{}__in'.format(name): value}) |
-            Q(**{'connected_endpoint__{}__in'.format(name): value})
-        )
+    # TODO: Fix filters
+    # def filter_site(self, queryset, name, value):
+    #     if not value.strip():
+    #         return queryset
+    #     return queryset.filter(connected_endpoint__device__site__slug=value)
+    #
+    # def filter_device(self, queryset, name, value):
+    #     if not value:
+    #         return queryset
+    #     return queryset.filter(
+    #         Q(**{'{}__in'.format(name): value}) |
+    #         Q(**{'connected_endpoint__{}__in'.format(name): value})
+    #     )
 
 
 
 
 class PowerConnectionFilterSet(BaseFilterSet):
 class PowerConnectionFilterSet(BaseFilterSet):
@@ -1202,18 +1203,19 @@ class PowerConnectionFilterSet(BaseFilterSet):
         model = PowerPort
         model = PowerPort
         fields = ['name', 'connection_status']
         fields = ['name', 'connection_status']
 
 
-    def filter_site(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(_connected_poweroutlet__device__site__slug=value)
-
-    def filter_device(self, queryset, name, value):
-        if not value:
-            return queryset
-        return queryset.filter(
-            Q(**{'{}__in'.format(name): value}) |
-            Q(**{'_connected_poweroutlet__{}__in'.format(name): value})
-        )
+    # TODO: Fix filters
+    # def filter_site(self, queryset, name, value):
+    #     if not value.strip():
+    #         return queryset
+    #     return queryset.filter(_connected_poweroutlet__device__site__slug=value)
+    #
+    # def filter_device(self, queryset, name, value):
+    #     if not value:
+    #         return queryset
+    #     return queryset.filter(
+    #         Q(**{'{}__in'.format(name): value}) |
+    #         Q(**{'_connected_poweroutlet__{}__in'.format(name): value})
+    #     )
 
 
 
 
 class InterfaceConnectionFilterSet(BaseFilterSet):
 class InterfaceConnectionFilterSet(BaseFilterSet):
@@ -1233,21 +1235,22 @@ class InterfaceConnectionFilterSet(BaseFilterSet):
         model = Interface
         model = Interface
         fields = ['connection_status']
         fields = ['connection_status']
 
 
-    def filter_site(self, queryset, name, value):
-        if not value.strip():
-            return queryset
-        return queryset.filter(
-            Q(device__site__slug=value) |
-            Q(_connected_interface__device__site__slug=value)
-        )
-
-    def filter_device(self, queryset, name, value):
-        if not value:
-            return queryset
-        return queryset.filter(
-            Q(**{'{}__in'.format(name): value}) |
-            Q(**{'_connected_interface__{}__in'.format(name): value})
-        )
+    # TODO: Fix filters
+    # def filter_site(self, queryset, name, value):
+    #     if not value.strip():
+    #         return queryset
+    #     return queryset.filter(
+    #         Q(device__site__slug=value) |
+    #         Q(_connected_interface__device__site__slug=value)
+    #     )
+    #
+    # def filter_device(self, queryset, name, value):
+    #     if not value:
+    #         return queryset
+    #     return queryset.filter(
+    #         Q(**{'{}__in'.format(name): value}) |
+    #         Q(**{'_connected_interface__{}__in'.format(name): value})
+    #     )
 
 
 
 
 class PowerPanelFilterSet(BaseFilterSet):
 class PowerPanelFilterSet(BaseFilterSet):

+ 37 - 0
netbox/dcim/migrations/0121_drop_connected_endpoint.py

@@ -0,0 +1,37 @@
+# Generated by Django 3.1 on 2020-10-05 13:56
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dcim', '0120_cablepath'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='consoleport',
+            name='connected_endpoint',
+        ),
+        migrations.RemoveField(
+            model_name='interface',
+            name='_connected_circuittermination',
+        ),
+        migrations.RemoveField(
+            model_name='interface',
+            name='_connected_interface',
+        ),
+        migrations.RemoveField(
+            model_name='powerfeed',
+            name='connected_endpoint',
+        ),
+        migrations.RemoveField(
+            model_name='powerport',
+            name='_connected_powerfeed',
+        ),
+        migrations.RemoveField(
+            model_name='powerport',
+            name='_connected_poweroutlet',
+        ),
+    ]

+ 9 - 108
netbox/dcim/models/device_components.py

@@ -150,6 +150,15 @@ class PathEndpoint(models.Model):
         # Return the path as a list of three-tuples (A termination, cable, B termination)
         # Return the path as a list of three-tuples (A termination, cable, B termination)
         return list(zip(*[iter(path)] * 3))
         return list(zip(*[iter(path)] * 3))
 
 
+    @property
+    def connected_endpoint(self):
+        """
+        Caching accessor for the attached CablePath's destination (if any)
+        """
+        if not hasattr(self, '_connected_endpoint'):
+            self._connected_endpoint = self._path.destination if self._path else None
+        return self._connected_endpoint
+
 
 
 #
 #
 # Console ports
 # Console ports
@@ -166,13 +175,6 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
         blank=True,
         blank=True,
         help_text='Physical port type'
         help_text='Physical port type'
     )
     )
-    connected_endpoint = models.OneToOneField(
-        to='dcim.ConsoleServerPort',
-        on_delete=models.SET_NULL,
-        related_name='connected_endpoint',
-        blank=True,
-        null=True
-    )
     connection_status = models.BooleanField(
     connection_status = models.BooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
         blank=True,
@@ -267,20 +269,6 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
         validators=[MinValueValidator(1)],
         validators=[MinValueValidator(1)],
         help_text="Allocated power draw (watts)"
         help_text="Allocated power draw (watts)"
     )
     )
-    _connected_poweroutlet = models.OneToOneField(
-        to='dcim.PowerOutlet',
-        on_delete=models.SET_NULL,
-        related_name='connected_endpoint',
-        blank=True,
-        null=True
-    )
-    _connected_powerfeed = models.OneToOneField(
-        to='dcim.PowerFeed',
-        on_delete=models.SET_NULL,
-        related_name='+',
-        blank=True,
-        null=True
-    )
     connection_status = models.BooleanField(
     connection_status = models.BooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
         blank=True,
@@ -308,43 +296,6 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
             self.description,
             self.description,
         )
         )
 
 
-    @property
-    def connected_endpoint(self):
-        """
-        Return the connected PowerOutlet, if it exists, or the connected PowerFeed, if it exists. We have to check for
-        ObjectDoesNotExist in case the referenced object has been deleted from the database.
-        """
-        try:
-            if self._connected_poweroutlet:
-                return self._connected_poweroutlet
-        except ObjectDoesNotExist:
-            pass
-        try:
-            if self._connected_powerfeed:
-                return self._connected_powerfeed
-        except ObjectDoesNotExist:
-            pass
-        return None
-
-    @connected_endpoint.setter
-    def connected_endpoint(self, value):
-        # TODO: Fix circular import
-        from . import PowerFeed
-
-        if value is None:
-            self._connected_poweroutlet = None
-            self._connected_powerfeed = None
-        elif isinstance(value, PowerOutlet):
-            self._connected_poweroutlet = value
-            self._connected_powerfeed = None
-        elif isinstance(value, PowerFeed):
-            self._connected_poweroutlet = None
-            self._connected_powerfeed = value
-        else:
-            raise ValueError(
-                "Connected endpoint must be a PowerOutlet or PowerFeed, not {}.".format(type(value))
-            )
-
     def get_power_draw(self):
     def get_power_draw(self):
         """
         """
         Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort.
         Return the allocated and maximum power draw (in VA) and child PowerOutlet count for this PowerPort.
@@ -497,20 +448,6 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
         max_length=100,
         max_length=100,
         blank=True
         blank=True
     )
     )
-    _connected_interface = models.OneToOneField(
-        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
-    )
     connection_status = models.BooleanField(
     connection_status = models.BooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
         blank=True,
@@ -631,42 +568,6 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
 
 
         return super().save(*args, **kwargs)
         return super().save(*args, **kwargs)
 
 
-    @property
-    def connected_endpoint(self):
-        """
-        Return the connected Interface, if it exists, or the connected CircuitTermination, if it exists. We have to
-        check for ObjectDoesNotExist in case the referenced object has been deleted from the database.
-        """
-        try:
-            if self._connected_interface:
-                return self._connected_interface
-        except ObjectDoesNotExist:
-            pass
-        try:
-            if self._connected_circuittermination:
-                return self._connected_circuittermination
-        except ObjectDoesNotExist:
-            pass
-        return None
-
-    @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
     @property
     def parent(self):
     def parent(self):
         return self.device
         return self.device

+ 0 - 7
netbox/dcim/models/power.py

@@ -88,13 +88,6 @@ class PowerFeed(ChangeLoggedModel, PathEndpoint, CableTermination, CustomFieldMo
         blank=True,
         blank=True,
         null=True
         null=True
     )
     )
-    connected_endpoint = models.OneToOneField(
-        to='dcim.PowerPort',
-        on_delete=models.SET_NULL,
-        related_name='+',
-        blank=True,
-        null=True
-    )
     connection_status = models.BooleanField(
     connection_status = models.BooleanField(
         choices=CONNECTION_STATUS_CHOICES,
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
         blank=True,

+ 2 - 6
netbox/dcim/views.py

@@ -1122,10 +1122,8 @@ class DeviceLLDPNeighborsView(ObjectView):
     def get(self, request, pk):
     def get(self, request, pk):
 
 
         device = get_object_or_404(self.queryset, pk=pk)
         device = get_object_or_404(self.queryset, pk=pk)
-        interfaces = device.vc_interfaces.restrict(request.user, 'view').exclude(
+        interfaces = device.vc_interfaces.restrict(request.user, 'view').prefetch_related('_path').exclude(
             type__in=NONCONNECTABLE_IFACE_TYPES
             type__in=NONCONNECTABLE_IFACE_TYPES
-        ).prefetch_related(
-            '_connected_interface__device'
         )
         )
 
 
         return render(request, 'dcim/device_lldp_neighbors.html', {
         return render(request, 'dcim/device_lldp_neighbors.html', {
@@ -1483,8 +1481,6 @@ class InterfaceView(ObjectView):
 
 
         return render(request, 'dcim/interface.html', {
         return render(request, 'dcim/interface.html', {
             'instance': interface,
             'instance': interface,
-            'connected_interface': interface._connected_interface,
-            'connected_circuittermination': interface._connected_circuittermination,
             'ipaddress_table': ipaddress_table,
             'ipaddress_table': ipaddress_table,
             'vlan_table': vlan_table,
             'vlan_table': vlan_table,
         })
         })
@@ -2137,7 +2133,7 @@ class InterfaceConnectionsListView(ObjectListView):
     ).filter(
     ).filter(
         # Avoid duplicate connections by only selecting the lower PK in a connected pair
         # Avoid duplicate connections by only selecting the lower PK in a connected pair
         _path__isnull=False,
         _path__isnull=False,
-        pk__lt=F('_connected_interface')
+        pk__lt=F('_path__destination_id')
     ).order_by('device')
     ).order_by('device')
     filterset = filters.InterfaceConnectionFilterSet
     filterset = filters.InterfaceConnectionFilterSet
     filterset_form = forms.InterfaceConnectionFilterForm
     filterset_form = forms.InterfaceConnectionFilterForm

+ 7 - 7
netbox/netbox/views.py

@@ -190,15 +190,15 @@ class HomeView(View):
 
 
     def get(self, request):
     def get(self, request):
 
 
-        connected_consoleports = ConsolePort.objects.restrict(request.user, 'view').filter(
-            connected_endpoint__isnull=False
+        connected_consoleports = ConsolePort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
+            _path__destination_id__isnull=False
         )
         )
-        connected_powerports = PowerPort.objects.restrict(request.user, 'view').filter(
-            _connected_poweroutlet__isnull=False
+        connected_powerports = PowerPort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
+            _path__destination_id__isnull=False
         )
         )
-        connected_interfaces = Interface.objects.restrict(request.user, 'view').filter(
-            _connected_interface__isnull=False,
-            pk__lt=F('_connected_interface')
+        connected_interfaces = Interface.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
+            _path__destination_id__isnull=False,
+            pk__lt=F('_path__destination_id')
         )
         )
 
 
         # Report Results
         # Report Results

+ 57 - 55
netbox/templates/dcim/interface.html

@@ -77,61 +77,63 @@
                     </div>
                     </div>
                     {% if instance.cable %}
                     {% if instance.cable %}
                         <table class="table table-hover panel-body attr-table">
                         <table class="table table-hover panel-body attr-table">
-                            {% if connected_interface %}
-                                <tr>
-                                    <td>Device</td>
-                                    <td>
-                                        <a href="{{ connected_interface.device.get_absolute_url }}">{{ connected_interface.device }}</a>
-                                    </td>
-                                </tr>
-                                <tr>
-                                    <td>Name</td>
-                                    <td>
-                                        <a href="{{ connected_interface.get_absolute_url }}">{{ connected_interface.name }}</a>
-                                    </td>
-                                </tr>
-                                <tr>
-                                    <td>Type</td>
-                                    <td>{{ connected_interface.get_type_display }}</td>
-                                </tr>
-                                <tr>
-                                    <td>Enabled</td>
-                                    <td>
-                                        {% if connected_interface.enabled %}
-                                            <span class="text-success"><i class="fa fa-check"></i></span>
-                                        {% else %}
-                                            <span class="text-danger"><i class="fa fa-close"></i></span>
-                                        {% endif %}
-                                    </td>
-                                </tr>
-                                <tr>
-                                    <td>LAG</td>
-                                    <td>
-                                        {% if connected_interface.lag%}
-                                            <a href="{{ connected_interface.lag.get_absolute_url }}">{{ connected_interface.lag }}</a>
-                                        {% else %}
-                                            <span class="text-muted">None</span>
-                                        {% endif %}
-                                    </td>
-                                </tr>
-                                <tr>
-                                    <td>Description</td>
-                                    <td>{{ connected_interface.description|placeholder }}</td>
-                                </tr>
-                                <tr>
-                                    <td>MTU</td>
-                                    <td>{{ connected_interface.mtu|placeholder }}</td>
-                                </tr>
-                                <tr>
-                                    <td>MAC Address</td>
-                                    <td>{{ connected_interface.mac_address|placeholder }}</td>
-                                </tr>
-                                <tr>
-                                    <td>802.1Q Mode</td>
-                                    <td>{{ connected_interface.get_mode_display }}</td>
-                                </tr>
-                            {% elif connected_circuittermination %}
-                                {% with ct=connected_circuittermination %}
+                            {% if instance.connected_endpoint.device %}
+                                {% with iface=instance.connected_endpoint %}
+                                    <tr>
+                                        <td>Device</td>
+                                        <td>
+                                            <a href="{{ iface.device.get_absolute_url }}">{{ iface.device }}</a>
+                                        </td>
+                                    </tr>
+                                    <tr>
+                                        <td>Name</td>
+                                        <td>
+                                            <a href="{{ iface.get_absolute_url }}">{{ iface.name }}</a>
+                                        </td>
+                                    </tr>
+                                    <tr>
+                                        <td>Type</td>
+                                        <td>{{ iface.get_type_display }}</td>
+                                    </tr>
+                                    <tr>
+                                        <td>Enabled</td>
+                                        <td>
+                                            {% if iface.enabled %}
+                                                <span class="text-success"><i class="fa fa-check"></i></span>
+                                            {% else %}
+                                                <span class="text-danger"><i class="fa fa-close"></i></span>
+                                            {% endif %}
+                                        </td>
+                                    </tr>
+                                    <tr>
+                                        <td>LAG</td>
+                                        <td>
+                                            {% if iface.lag%}
+                                                <a href="{{ iface.lag.get_absolute_url }}">{{ iface.lag }}</a>
+                                            {% else %}
+                                                <span class="text-muted">None</span>
+                                            {% endif %}
+                                        </td>
+                                    </tr>
+                                    <tr>
+                                        <td>Description</td>
+                                        <td>{{ iface.description|placeholder }}</td>
+                                    </tr>
+                                    <tr>
+                                        <td>MTU</td>
+                                        <td>{{ iface.mtu|placeholder }}</td>
+                                    </tr>
+                                    <tr>
+                                        <td>MAC Address</td>
+                                        <td>{{ iface.mac_address|placeholder }}</td>
+                                    </tr>
+                                    <tr>
+                                        <td>802.1Q Mode</td>
+                                        <td>{{ iface.get_mode_display }}</td>
+                                    </tr>
+                                {% endwith %}
+                            {% elif instance.connected_endpoint.circuit %}
+                                {% with ct=instance.connected_endpoint %}
                                     <tr>
                                     <tr>
                                         <td>Provider</td>
                                         <td>Provider</td>
                                         <td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>
                                         <td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>