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:
@ -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}),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
@ -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'),
|
||||||
|
|
||||||
|
@ -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 = []
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -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 %}
|
||||||
|
Reference in New Issue
Block a user