Remove legacy connected endpoint fields

This commit is contained in:
Jeremy Stretch 2020-10-05 09:56:46 -04:00
parent f8800b8303
commit 079c42291c
13 changed files with 190 additions and 263 deletions

View File

@ -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( Prefetch('terminations', queryset=CircuitTermination.objects.prefetch_related('site')),
'site', 'connected_endpoint__device'
)),
'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

View File

@ -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',
),
]

View File

@ -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,

View File

@ -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()

View File

@ -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'{obj.connected_endpoint._meta.app_label}.{obj.connected_endpoint._meta.model_name}'
return f'{destination._meta.app_label}.{destination._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

View File

@ -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( queryset = PowerPort.objects.prefetch_related('device', '_path', 'cable', 'tags')
'device', '_connected_poweroutlet__device', '_connected_powerfeed', '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( queryset = Interface.objects.prefetch_related('device', '_path', 'cable', 'ip_addresses', 'tags')
'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags'
).filter(
device__isnull=False
)
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( queryset = ConsolePort.objects.prefetch_related('device', '_path').filter(
'device', 'connected_endpoint__device' _path__destination_id__isnull=False
).filter(
connected_endpoint__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( queryset = PowerPort.objects.prefetch_related('device', '_path').filter(
'device', 'connected_endpoint__device' _path__destination_id__isnull=False
).filter(
_connected_poweroutlet__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( queryset = Interface.objects.prefetch_related('device', '_path').filter(
'device', '_connected_interface__device'
).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, _path__destination_id__isnull=False,
pk__lt=F('_connected_interface') 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()

View File

@ -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): # TODO: Fix filters
if not value.strip(): # def filter_site(self, queryset, name, value):
return queryset # if not value.strip():
return queryset.filter(connected_endpoint__device__site__slug=value) # return queryset
# return queryset.filter(connected_endpoint__device__site__slug=value)
def filter_device(self, queryset, name, value): #
if not value: # def filter_device(self, queryset, name, value):
return queryset # if not value:
return queryset.filter( # return queryset
Q(**{'{}__in'.format(name): value}) | # return queryset.filter(
Q(**{'connected_endpoint__{}__in'.format(name): value}) # 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): # TODO: Fix filters
if not value.strip(): # def filter_site(self, queryset, name, value):
return queryset # if not value.strip():
return queryset.filter(_connected_poweroutlet__device__site__slug=value) # return queryset
# return queryset.filter(_connected_poweroutlet__device__site__slug=value)
def filter_device(self, queryset, name, value): #
if not value: # def filter_device(self, queryset, name, value):
return queryset # if not value:
return queryset.filter( # return queryset
Q(**{'{}__in'.format(name): value}) | # return queryset.filter(
Q(**{'_connected_poweroutlet__{}__in'.format(name): value}) # 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): # TODO: Fix filters
if not value.strip(): # def filter_site(self, queryset, name, value):
return queryset # if not value.strip():
return queryset.filter( # return queryset
Q(device__site__slug=value) | # return queryset.filter(
Q(_connected_interface__device__site__slug=value) # Q(device__site__slug=value) |
) # Q(_connected_interface__device__site__slug=value)
# )
def filter_device(self, queryset, name, value): #
if not value: # def filter_device(self, queryset, name, value):
return queryset # if not value:
return queryset.filter( # return queryset
Q(**{'{}__in'.format(name): value}) | # return queryset.filter(
Q(**{'_connected_interface__{}__in'.format(name): value}) # Q(**{'{}__in'.format(name): value}) |
) # Q(**{'_connected_interface__{}__in'.format(name): value})
# )
class PowerPanelFilterSet(BaseFilterSet): class PowerPanelFilterSet(BaseFilterSet):

View File

@ -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',
),
]

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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_consoleports = ConsolePort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
connected_endpoint__isnull=False _path__destination_id__isnull=False
) )
connected_powerports = PowerPort.objects.restrict(request.user, 'view').filter( connected_powerports = PowerPort.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
_connected_poweroutlet__isnull=False _path__destination_id__isnull=False
) )
connected_interfaces = Interface.objects.restrict(request.user, 'view').filter( connected_interfaces = Interface.objects.restrict(request.user, 'view').prefetch_related('_path').filter(
_connected_interface__isnull=False, _path__destination_id__isnull=False,
pk__lt=F('_connected_interface') pk__lt=F('_path__destination_id')
) )
# Report Results # Report Results

View File

@ -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 %} {% if instance.connected_endpoint.device %}
<tr> {% with iface=instance.connected_endpoint %}
<td>Device</td> <tr>
<td> <td>Device</td>
<a href="{{ connected_interface.device.get_absolute_url }}">{{ connected_interface.device }}</a> <td>
</td> <a href="{{ iface.device.get_absolute_url }}">{{ iface.device }}</a>
</tr> </td>
<tr> </tr>
<td>Name</td> <tr>
<td> <td>Name</td>
<a href="{{ connected_interface.get_absolute_url }}">{{ connected_interface.name }}</a> <td>
</td> <a href="{{ iface.get_absolute_url }}">{{ iface.name }}</a>
</tr> </td>
<tr> </tr>
<td>Type</td> <tr>
<td>{{ connected_interface.get_type_display }}</td> <td>Type</td>
</tr> <td>{{ iface.get_type_display }}</td>
<tr> </tr>
<td>Enabled</td> <tr>
<td> <td>Enabled</td>
{% if connected_interface.enabled %} <td>
<span class="text-success"><i class="fa fa-check"></i></span> {% if iface.enabled %}
{% else %} <span class="text-success"><i class="fa fa-check"></i></span>
<span class="text-danger"><i class="fa fa-close"></i></span> {% else %}
{% endif %} <span class="text-danger"><i class="fa fa-close"></i></span>
</td> {% endif %}
</tr> </td>
<tr> </tr>
<td>LAG</td> <tr>
<td> <td>LAG</td>
{% if connected_interface.lag%} <td>
<a href="{{ connected_interface.lag.get_absolute_url }}">{{ connected_interface.lag }}</a> {% if iface.lag%}
{% else %} <a href="{{ iface.lag.get_absolute_url }}">{{ iface.lag }}</a>
<span class="text-muted">None</span> {% else %}
{% endif %} <span class="text-muted">None</span>
</td> {% endif %}
</tr> </td>
<tr> </tr>
<td>Description</td> <tr>
<td>{{ connected_interface.description|placeholder }}</td> <td>Description</td>
</tr> <td>{{ iface.description|placeholder }}</td>
<tr> </tr>
<td>MTU</td> <tr>
<td>{{ connected_interface.mtu|placeholder }}</td> <td>MTU</td>
</tr> <td>{{ iface.mtu|placeholder }}</td>
<tr> </tr>
<td>MAC Address</td> <tr>
<td>{{ connected_interface.mac_address|placeholder }}</td> <td>MAC Address</td>
</tr> <td>{{ iface.mac_address|placeholder }}</td>
<tr> </tr>
<td>802.1Q Mode</td> <tr>
<td>{{ connected_interface.get_mode_display }}</td> <td>802.1Q Mode</td>
</tr> <td>{{ iface.get_mode_display }}</td>
{% elif connected_circuittermination %} </tr>
{% with ct=connected_circuittermination %} {% 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>