Przeglądaj źródła

Remove legacy connected endpoint fields

Jeremy Stretch 5 lat temu
rodzic
commit
079c42291c

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

@@ -46,9 +46,7 @@ class CircuitTypeViewSet(ModelViewSet):
 
 class CircuitViewSet(CustomFieldModelViewSet):
     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',
     ).prefetch_related('tags')
     serializer_class = serializers.CircuitSerializer
@@ -61,7 +59,7 @@ class CircuitViewSet(CustomFieldModelViewSet):
 
 class CircuitTerminationViewSet(ModelViewSet):
     queryset = CircuitTermination.objects.prefetch_related(
-        'circuit', 'site', 'connected_endpoint__device', 'cable'
+        'circuit', 'site', 'cable'
     )
     serializer_class = serializers.CircuitTerminationSerializer
     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,
         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(
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,

+ 2 - 2
netbox/circuits/views.py

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

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

@@ -34,8 +34,7 @@ class ConnectedEndpointSerializer(ValidatedModelSerializer):
 
     def get_connected_endpoint_type(self, obj):
         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
 
     @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.
         """
         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']}
             return serializer(obj.path.destination, context=context).data
         return None

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

@@ -470,37 +470,31 @@ class DeviceViewSet(CustomFieldModelViewSet):
 #
 
 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
     filterset_class = filters.ConsolePortFilterSet
 
 
 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
     filterset_class = filters.ConsoleServerPortFilterSet
 
 
 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
     filterset_class = filters.PowerPortFilterSet
 
 
 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
     filterset_class = filters.PowerOutletFilterSet
 
 
 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
     filterset_class = filters.InterfaceFilterSet
 
@@ -534,32 +528,26 @@ class InventoryItemViewSet(ModelViewSet):
 #
 
 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
     filterset_class = filters.ConsoleConnectionFilterSet
 
 
 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
     filterset_class = filters.PowerConnectionFilterSet
 
 
 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
-        _connected_interface__isnull=False,
-        pk__lt=F('_connected_interface')
+        _path__destination_id__isnull=False,
+        pk__lt=F('_path__destination_id')
     )
     serializer_class = serializers.InterfaceConnectionSerializer
     filterset_class = filters.InterfaceConnectionFilterSet
@@ -664,7 +652,7 @@ class ConnectedDeviceViewSet(ViewSet):
             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:
             return Response()

+ 42 - 39
netbox/dcim/filters.py

@@ -1171,18 +1171,19 @@ class ConsoleConnectionFilterSet(BaseFilterSet):
         model = ConsolePort
         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):
@@ -1202,18 +1203,19 @@ class PowerConnectionFilterSet(BaseFilterSet):
         model = PowerPort
         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):
@@ -1233,21 +1235,22 @@ class InterfaceConnectionFilterSet(BaseFilterSet):
         model = Interface
         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):

+ 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 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
@@ -166,13 +175,6 @@ class ConsolePort(CableTermination, PathEndpoint, ComponentModel):
         blank=True,
         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(
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
@@ -267,20 +269,6 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
         validators=[MinValueValidator(1)],
         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(
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
@@ -308,43 +296,6 @@ class PowerPort(CableTermination, PathEndpoint, ComponentModel):
             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):
         """
         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,
         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(
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,
@@ -631,42 +568,6 @@ class Interface(CableTermination, PathEndpoint, ComponentModel, BaseInterface):
 
         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
     def parent(self):
         return self.device

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

@@ -88,13 +88,6 @@ class PowerFeed(ChangeLoggedModel, PathEndpoint, CableTermination, CustomFieldMo
         blank=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(
         choices=CONNECTION_STATUS_CHOICES,
         blank=True,

+ 2 - 6
netbox/dcim/views.py

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

+ 7 - 7
netbox/netbox/views.py

@@ -190,15 +190,15 @@ class HomeView(View):
 
     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

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

@@ -77,61 +77,63 @@
                     </div>
                     {% if instance.cable %}
                         <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>
                                         <td>Provider</td>
                                         <td><a href="{{ ct.circuit.provider.get_absolute_url }}">{{ ct.circuit.provider }}</a></td>