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):
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

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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