1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Replace legacy trace() method

This commit is contained in:
Jeremy Stretch
2020-10-02 14:54:16 -04:00
parent aa0d4c4145
commit e9da84f91a
7 changed files with 38 additions and 204 deletions

View File

@ -1,6 +1,6 @@
from django.urls import path from django.urls import path
from dcim.views import CableCreateView, CableTraceView from dcim.views import CableCreateView, PathTraceView
from extras.views import ObjectChangeLogView from extras.views import ObjectChangeLogView
from . import views from . import views
from .models import Circuit, CircuitTermination, CircuitType, Provider from .models import Circuit, CircuitTermination, CircuitType, Provider
@ -45,6 +45,6 @@ urlpatterns = [
path('circuit-terminations/<int:pk>/edit/', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'), path('circuit-terminations/<int:pk>/edit/', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
path('circuit-terminations/<int:pk>/delete/', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'), path('circuit-terminations/<int:pk>/delete/', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
path('circuit-terminations/<int:termination_a_id>/connect/<str:termination_b_type>/', CableCreateView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}), path('circuit-terminations/<int:termination_a_id>/connect/<str:termination_b_type>/', CableCreateView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}),
path('circuit-terminations/<int:pk>/trace/', CableTraceView.as_view(), name='circuittermination_trace', kwargs={'model': CircuitTermination}), path('circuit-terminations/<int:pk>/trace/', PathTraceView.as_view(), name='circuittermination_trace', kwargs={'model': CircuitTermination}),
] ]

View File

@ -45,7 +45,7 @@ class DCIMRootView(APIRootView):
# Mixins # Mixins
class CableTraceMixin(object): class PathEndpointMixin(object):
@action(detail=True, url_path='trace') @action(detail=True, url_path='trace')
def trace(self, request, pk): def trace(self, request, pk):
@ -57,7 +57,7 @@ class CableTraceMixin(object):
# Initialize the path array # Initialize the path array
path = [] path = []
for near_end, cable, far_end in obj.trace()[0]: for near_end, cable, far_end in obj.trace():
# Serialize each object # Serialize each object
serializer_a = get_serializer_for_model(near_end, prefix='Nested') serializer_a = get_serializer_for_model(near_end, prefix='Nested')
@ -469,19 +469,19 @@ class DeviceViewSet(CustomFieldModelViewSet):
# Device components # Device components
# #
class ConsolePortViewSet(CableTraceMixin, ModelViewSet): class ConsolePortViewSet(PathEndpointMixin, ModelViewSet):
queryset = ConsolePort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags') queryset = ConsolePort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
serializer_class = serializers.ConsolePortSerializer serializer_class = serializers.ConsolePortSerializer
filterset_class = filters.ConsolePortFilterSet filterset_class = filters.ConsolePortFilterSet
class ConsoleServerPortViewSet(CableTraceMixin, ModelViewSet): class ConsoleServerPortViewSet(PathEndpointMixin, ModelViewSet):
queryset = ConsoleServerPort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags') queryset = ConsoleServerPort.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
serializer_class = serializers.ConsoleServerPortSerializer serializer_class = serializers.ConsoleServerPortSerializer
filterset_class = filters.ConsoleServerPortFilterSet filterset_class = filters.ConsoleServerPortFilterSet
class PowerPortViewSet(CableTraceMixin, ModelViewSet): class PowerPortViewSet(PathEndpointMixin, ModelViewSet):
queryset = PowerPort.objects.prefetch_related( queryset = PowerPort.objects.prefetch_related(
'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable', 'tags' 'device', '_connected_poweroutlet__device', '_connected_powerfeed', 'cable', 'tags'
) )
@ -489,13 +489,13 @@ class PowerPortViewSet(CableTraceMixin, ModelViewSet):
filterset_class = filters.PowerPortFilterSet filterset_class = filters.PowerPortFilterSet
class PowerOutletViewSet(CableTraceMixin, ModelViewSet): class PowerOutletViewSet(PathEndpointMixin, ModelViewSet):
queryset = PowerOutlet.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags') queryset = PowerOutlet.objects.prefetch_related('device', 'connected_endpoint__device', 'cable', 'tags')
serializer_class = serializers.PowerOutletSerializer serializer_class = serializers.PowerOutletSerializer
filterset_class = filters.PowerOutletFilterSet filterset_class = filters.PowerOutletFilterSet
class InterfaceViewSet(CableTraceMixin, ModelViewSet): class InterfaceViewSet(PathEndpointMixin, ModelViewSet):
queryset = Interface.objects.prefetch_related( queryset = Interface.objects.prefetch_related(
'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags' 'device', '_connected_interface', '_connected_circuittermination', 'cable', 'ip_addresses', 'tags'
).filter( ).filter(
@ -505,13 +505,13 @@ class InterfaceViewSet(CableTraceMixin, ModelViewSet):
filterset_class = filters.InterfaceFilterSet filterset_class = filters.InterfaceFilterSet
class FrontPortViewSet(CableTraceMixin, ModelViewSet): class FrontPortViewSet(ModelViewSet):
queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags') queryset = FrontPort.objects.prefetch_related('device__device_type__manufacturer', 'rear_port', 'cable', 'tags')
serializer_class = serializers.FrontPortSerializer serializer_class = serializers.FrontPortSerializer
filterset_class = filters.FrontPortFilterSet filterset_class = filters.FrontPortFilterSet
class RearPortViewSet(CableTraceMixin, ModelViewSet): class RearPortViewSet(ModelViewSet):
queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags') queryset = RearPort.objects.prefetch_related('device__device_type__manufacturer', 'cable', 'tags')
serializer_class = serializers.RearPortSerializer serializer_class = serializers.RearPortSerializer
filterset_class = filters.RearPortFilterSet filterset_class = filters.RearPortFilterSet

View File

@ -1,5 +1,3 @@
import logging
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator from django.core.validators import MaxValueValidator, MinValueValidator
@ -11,8 +9,8 @@ from taggit.managers import TaggableManager
from dcim.choices import * from dcim.choices import *
from dcim.constants import * from dcim.constants import *
from dcim.exceptions import CableTraceSplit
from dcim.fields import MACAddressField from dcim.fields import MACAddressField
from dcim.utils import path_node_to_object
from extras.models import ObjectChange, TaggedItem from extras.models import ObjectChange, TaggedItem
from extras.utils import extras_features from extras.utils import extras_features
from utilities.fields import NaturalOrderingField from utilities.fields import NaturalOrderingField
@ -117,114 +115,6 @@ class CableTermination(models.Model):
class Meta: class Meta:
abstract = True abstract = True
def trace(self):
"""
Return three items: the traceable portion of a cable path, the termination points where it splits (if any), and
the remaining positions on the position stack (if any). Splits occur when the trace is initiated from a midpoint
along a path which traverses a RearPort. In cases where the originating endpoint is unknown, it is not possible
to know which corresponding FrontPort to follow. Remaining positions occur when tracing a path that traverses
a FrontPort without traversing a RearPort again.
The path is a list representing a complete cable path, with each individual segment represented as a
three-tuple:
[
(termination A, cable, termination B),
(termination C, cable, termination D),
(termination E, cable, termination F)
]
"""
endpoint = self
path = []
position_stack = []
def get_peer_port(termination):
from circuits.models import CircuitTermination
# Map a front port to its corresponding rear port
if isinstance(termination, FrontPort):
# Retrieve the corresponding RearPort from database to ensure we have an up-to-date instance
peer_port = RearPort.objects.get(pk=termination.rear_port.pk)
# Don't use the stack for RearPorts with a single position. Only remember the position at
# many-to-one points so we can select the correct FrontPort when we reach the corresponding
# one-to-many point.
if peer_port.positions > 1:
position_stack.append(termination)
return peer_port
# Map a rear port/position to its corresponding front port
elif isinstance(termination, RearPort):
if termination.positions > 1:
# Can't map to a FrontPort without a position if there are multiple options
if not position_stack:
raise CableTraceSplit(termination)
front_port = position_stack.pop()
position = front_port.rear_port_position
# Validate the position
if position not in range(1, termination.positions + 1):
raise Exception("Invalid position for {} ({} positions): {})".format(
termination, termination.positions, position
))
else:
# Don't use the stack for RearPorts with a single position. The only possible position is 1.
position = 1
try:
peer_port = FrontPort.objects.get(
rear_port=termination,
rear_port_position=position,
)
return peer_port
except ObjectDoesNotExist:
return None
# Follow a circuit to its other termination
elif isinstance(termination, CircuitTermination):
peer_termination = termination.get_peer_termination()
if peer_termination is None:
return None
return peer_termination
# Termination is not a pass-through port
else:
return None
logger = logging.getLogger('netbox.dcim.cable.trace')
logger.debug("Tracing cable from {} {}".format(self.parent, self))
while endpoint is not None:
# No cable connected; nothing to trace
if not endpoint.cable:
path.append((endpoint, None, None))
logger.debug("No cable connected")
return path, None, position_stack
# Check for loops
if endpoint.cable in [segment[1] for segment in path]:
logger.debug("Loop detected!")
return path, None, position_stack
# Record the current segment in the path
far_end = endpoint.get_cable_peer()
path.append((endpoint, endpoint.cable, far_end))
logger.debug("{}[{}] --- Cable {} ---> {}[{}]".format(
endpoint.parent, endpoint, endpoint.cable.pk, far_end.parent, far_end
))
# Get the peer port of the far end termination
try:
endpoint = get_peer_port(far_end)
except CableTraceSplit as e:
return path, e.termination.frontports.all(), position_stack
if endpoint is None:
return path, None, position_stack
def get_cable_peer(self): def get_cable_peer(self):
if self.cable is None: if self.cable is None:
return None return None
@ -233,23 +123,6 @@ class CableTermination(models.Model):
if self._cabled_as_b.exists(): if self._cabled_as_b.exists():
return self.cable.termination_a return self.cable.termination_a
def get_path_endpoints(self):
"""
Return all endpoints of paths which traverse this object.
"""
endpoints = []
# Get the far end of the last path segment
path, split_ends, position_stack = self.trace()
endpoint = path[-1][2]
if split_ends is not None:
for termination in split_ends:
endpoints.extend(termination.get_path_endpoints())
elif endpoint is not None:
endpoints.append(endpoint)
return endpoints
class PathEndpoint(models.Model): class PathEndpoint(models.Model):
""" """
@ -265,6 +138,17 @@ class PathEndpoint(models.Model):
class Meta: class Meta:
abstract = True abstract = True
def trace(self):
if self.path is None:
return []
# Construct the complete path
path = [self, *[path_node_to_object(obj) for obj in self.path.path], self.path.destination]
assert not len(path) % 3, f"Invalid path length for CablePath #{self.pk}: {len(self.path)} elements in path"
# Return the path as a list of three-tuples (A termination, cable, B termination)
return list(zip(*[iter(path)] * 3))
@property @property
def path(self): def path(self):
""" """

View File

@ -207,7 +207,7 @@ urlpatterns = [
path('console-ports/<int:pk>/edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'), path('console-ports/<int:pk>/edit/', views.ConsolePortEditView.as_view(), name='consoleport_edit'),
path('console-ports/<int:pk>/delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'), path('console-ports/<int:pk>/delete/', views.ConsolePortDeleteView.as_view(), name='consoleport_delete'),
path('console-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleport_changelog', kwargs={'model': ConsolePort}), path('console-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleport_changelog', kwargs={'model': ConsolePort}),
path('console-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}), path('console-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='consoleport_trace', kwargs={'model': ConsolePort}),
path('console-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}), path('console-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleport_connect', kwargs={'termination_a_type': ConsolePort}),
path('devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'), path('devices/console-ports/add/', views.DeviceBulkAddConsolePortView.as_view(), name='device_bulk_add_consoleport'),
@ -223,7 +223,7 @@ urlpatterns = [
path('console-server-ports/<int:pk>/edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'), path('console-server-ports/<int:pk>/edit/', views.ConsoleServerPortEditView.as_view(), name='consoleserverport_edit'),
path('console-server-ports/<int:pk>/delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'), path('console-server-ports/<int:pk>/delete/', views.ConsoleServerPortDeleteView.as_view(), name='consoleserverport_delete'),
path('console-server-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleserverport_changelog', kwargs={'model': ConsoleServerPort}), path('console-server-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='consoleserverport_changelog', kwargs={'model': ConsoleServerPort}),
path('console-server-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}), path('console-server-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='consoleserverport_trace', kwargs={'model': ConsoleServerPort}),
path('console-server-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}), path('console-server-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='consoleserverport_connect', kwargs={'termination_a_type': ConsoleServerPort}),
path('devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'), path('devices/console-server-ports/add/', views.DeviceBulkAddConsoleServerPortView.as_view(), name='device_bulk_add_consoleserverport'),
@ -239,7 +239,7 @@ urlpatterns = [
path('power-ports/<int:pk>/edit/', views.PowerPortEditView.as_view(), name='powerport_edit'), path('power-ports/<int:pk>/edit/', views.PowerPortEditView.as_view(), name='powerport_edit'),
path('power-ports/<int:pk>/delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'), path('power-ports/<int:pk>/delete/', views.PowerPortDeleteView.as_view(), name='powerport_delete'),
path('power-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerport_changelog', kwargs={'model': PowerPort}), path('power-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='powerport_changelog', kwargs={'model': PowerPort}),
path('power-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}), path('power-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='powerport_trace', kwargs={'model': PowerPort}),
path('power-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}), path('power-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='powerport_connect', kwargs={'termination_a_type': PowerPort}),
path('devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'), path('devices/power-ports/add/', views.DeviceBulkAddPowerPortView.as_view(), name='device_bulk_add_powerport'),
@ -255,7 +255,7 @@ urlpatterns = [
path('power-outlets/<int:pk>/edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'), path('power-outlets/<int:pk>/edit/', views.PowerOutletEditView.as_view(), name='poweroutlet_edit'),
path('power-outlets/<int:pk>/delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'), path('power-outlets/<int:pk>/delete/', views.PowerOutletDeleteView.as_view(), name='poweroutlet_delete'),
path('power-outlets/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='poweroutlet_changelog', kwargs={'model': PowerOutlet}), path('power-outlets/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='poweroutlet_changelog', kwargs={'model': PowerOutlet}),
path('power-outlets/<int:pk>/trace/', views.CableTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}), path('power-outlets/<int:pk>/trace/', views.PathTraceView.as_view(), name='poweroutlet_trace', kwargs={'model': PowerOutlet}),
path('power-outlets/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}), path('power-outlets/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='poweroutlet_connect', kwargs={'termination_a_type': PowerOutlet}),
path('devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'), path('devices/power-outlets/add/', views.DeviceBulkAddPowerOutletView.as_view(), name='device_bulk_add_poweroutlet'),
@ -271,7 +271,7 @@ urlpatterns = [
path('interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='interface_edit'), path('interfaces/<int:pk>/edit/', views.InterfaceEditView.as_view(), name='interface_edit'),
path('interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'), path('interfaces/<int:pk>/delete/', views.InterfaceDeleteView.as_view(), name='interface_delete'),
path('interfaces/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}), path('interfaces/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='interface_changelog', kwargs={'model': Interface}),
path('interfaces/<int:pk>/trace/', views.CableTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}), path('interfaces/<int:pk>/trace/', views.PathTraceView.as_view(), name='interface_trace', kwargs={'model': Interface}),
path('interfaces/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}), path('interfaces/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='interface_connect', kwargs={'termination_a_type': Interface}),
path('devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'), path('devices/interfaces/add/', views.DeviceBulkAddInterfaceView.as_view(), name='device_bulk_add_interface'),
@ -287,7 +287,7 @@ urlpatterns = [
path('front-ports/<int:pk>/edit/', views.FrontPortEditView.as_view(), name='frontport_edit'), path('front-ports/<int:pk>/edit/', views.FrontPortEditView.as_view(), name='frontport_edit'),
path('front-ports/<int:pk>/delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'), path('front-ports/<int:pk>/delete/', views.FrontPortDeleteView.as_view(), name='frontport_delete'),
path('front-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='frontport_changelog', kwargs={'model': FrontPort}), path('front-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='frontport_changelog', kwargs={'model': FrontPort}),
path('front-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}), path('front-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='frontport_trace', kwargs={'model': FrontPort}),
path('front-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}), path('front-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='frontport_connect', kwargs={'termination_a_type': FrontPort}),
# path('devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'), # path('devices/front-ports/add/', views.DeviceBulkAddFrontPortView.as_view(), name='device_bulk_add_frontport'),
@ -303,7 +303,7 @@ urlpatterns = [
path('rear-ports/<int:pk>/edit/', views.RearPortEditView.as_view(), name='rearport_edit'), path('rear-ports/<int:pk>/edit/', views.RearPortEditView.as_view(), name='rearport_edit'),
path('rear-ports/<int:pk>/delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'), path('rear-ports/<int:pk>/delete/', views.RearPortDeleteView.as_view(), name='rearport_delete'),
path('rear-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rearport_changelog', kwargs={'model': RearPort}), path('rear-ports/<int:pk>/changelog/', ObjectChangeLogView.as_view(), name='rearport_changelog', kwargs={'model': RearPort}),
path('rear-ports/<int:pk>/trace/', views.CableTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}), path('rear-ports/<int:pk>/trace/', views.PathTraceView.as_view(), name='rearport_trace', kwargs={'model': RearPort}),
path('rear-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}), path('rear-ports/<int:termination_a_id>/connect/<str:termination_b_type>/', views.CableCreateView.as_view(), name='rearport_connect', kwargs={'termination_a_type': RearPort}),
path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'), path('devices/rear-ports/add/', views.DeviceBulkAddRearPortView.as_view(), name='device_bulk_add_rearport'),

View File

@ -2,7 +2,6 @@ from django.contrib.contenttypes.models import ContentType
from .choices import CableStatusChoices from .choices import CableStatusChoices
from .exceptions import CableTraceSplit from .exceptions import CableTraceSplit
from .models import FrontPort, RearPort
def object_to_path_node(obj): def object_to_path_node(obj):
@ -20,6 +19,8 @@ def path_node_to_object(repr):
def trace_path(node): def trace_path(node):
from .models import FrontPort, RearPort
destination = None destination = None
path = [] path = []
position_stack = [] position_stack = []

View File

@ -1961,9 +1961,9 @@ class CableView(ObjectView):
}) })
class CableTraceView(ObjectView): class PathTraceView(ObjectView):
""" """
Trace a cable path beginning from the given termination. Trace a cable path beginning from the given path endpoint (origin).
""" """
additional_permissions = ['dcim.view_cable'] additional_permissions = ['dcim.view_cable']
@ -1976,7 +1976,7 @@ class CableTraceView(ObjectView):
def get(self, request, pk): def get(self, request, pk):
obj = get_object_or_404(self.queryset, pk=pk) obj = get_object_or_404(self.queryset, pk=pk)
path, split_ends, position_stack = obj.trace() path = obj.trace()
total_length = sum( total_length = sum(
[entry[1]._abs_length for entry in path if entry[1] and entry[1]._abs_length] [entry[1]._abs_length for entry in path if entry[1] and entry[1]._abs_length]
) )
@ -1984,8 +1984,6 @@ class CableTraceView(ObjectView):
return render(request, 'dcim/cable_trace.html', { return render(request, 'dcim/cable_trace.html', {
'obj': obj, 'obj': obj,
'trace': path, 'trace': path,
'split_ends': split_ends,
'position_stack': position_stack,
'total_length': total_length, 'total_length': total_length,
}) })

View File

@ -51,57 +51,8 @@
<hr /> <hr />
{% endfor %} {% endfor %}
<div class="row"> <div class="row">
{% if split_ends %} <div class="col-md-11 col-md-offset-1">
<div class="col-md-7 col-md-offset-3"> <h3 class="text-success text-center">Trace completed!</h3>
<div class="panel panel-warning"> </div>
<div class="panel-heading">
<strong><i class="fa fa-warning"></i> Trace Split</strong>
</div>
<div class="panel-body">
There are multiple possible paths from this point. Select a port to continue.
</div>
</div>
<div class="panel panel-default">
<table class="panel-body table">
<thead>
<tr class="table-headings">
<th>Port</th>
<th>Connected</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
{% for termination in split_ends %}
<tr>
<td><a href="{% url 'dcim:frontport_trace' pk=termination.pk %}">{{ termination }}</a></td>
<td>
{% if termination.cable %}
<i class="fa fa-check text-success" title="Yes"></i>
{% else %}
<i class="fa fa-times text-danger" title="No"></i>
{% endif %}
</td>
<td>{{ termination.get_type_display }}</td>
<td>{{ termination.description|placeholder }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% elif position_stack %}
<div class="col-md-11 col-md-offset-1">
<h3 class="text-warning text-center">
{% with last_position=position_stack|last %}
Trace completed, but there is no Front Port corresponding to
<a href="{{ last_position.device.get_absolute_url }}">{{ last_position.device }}</a> {{ last_position }}.<br>
Therefore no end-to-end connection can be established.
{% endwith %}
</h3>
</div>
{% else %}
<div class="col-md-11 col-md-offset-1">
<h3 class="text-success text-center">Trace completed!</h3>
</div>
{% endif %}
</div> </div>
{% endblock %} {% endblock %}