소스 검색

First stab at cable path tracing and automatic endpoint connections

Jeremy Stretch 7 년 전
부모
커밋
35f80f5085
4개의 변경된 파일56개의 추가작업 그리고 8개의 파일을 삭제
  1. 43 3
      netbox/dcim/models.py
  2. 5 4
      netbox/dcim/signals.py
  3. 1 1
      netbox/templates/dcim/inc/interface.html
  4. 7 0
      netbox/utilities/views.py

+ 43 - 3
netbox/dcim/models.py

@@ -2372,9 +2372,49 @@ class Cable(ChangeLoggedModel):
             ('termination_b_type', 'termination_b_id'),
             ('termination_b_type', 'termination_b_id'),
         )
         )
 
 
-    # TODO: This should follow all cables in a path
     def get_path_endpoints(self):
     def get_path_endpoints(self):
         """
         """
-        Return the endpoints connected by this cable path.
+        Traverse both ends of a cable path and return its connected endpoints. Note that one or both endpoints may be
+        None.
         """
         """
-        return (self.termination_a, self.termination_b)
+        def trace_cable(termination, position=None):
+
+            # Given a front port, follow the cable connected to the corresponding rear port/position
+            if isinstance(termination, FrontPanelPort):
+                rear_port = termination.rear_port
+                port_type = ContentType.objects.get_for_model(rear_port)
+                next_cable = Cable.objects.filter(
+                    Q(termination_a_type=port_type, termination_a_id=rear_port.pk) |
+                    Q(termination_b_type=port_type, termination_b_id=rear_port.pk)
+                ).first()
+                if next_cable is None:
+                    return None
+                if next_cable.termination_a == termination.rear_port:
+                    return trace_cable(next_cable.termination_b, termination.rear_port_position)
+                else:
+                    return trace_cable(next_cable.termination_a, termination.rear_port_position)
+
+            # Given a rear port/position, follow the cable connected to the corresponding front port
+            if isinstance(termination, RearPanelPort):
+                if position is None:
+                    raise Exception("Must specify a position when tracing a path from a rear panel port")
+                front_port = FrontPanelPort.objects.get(
+                    rear_port=termination,
+                    rear_port_position=position,
+                )
+                port_type = ContentType.objects.get_for_model(front_port)
+                next_cable = Cable.objects.filter(
+                    Q(termination_a_type=port_type, termination_a_id=front_port.pk) |
+                    Q(termination_b_type=port_type, termination_b_id=front_port.pk)
+                ).first()
+                if next_cable is None:
+                    return None
+                if next_cable.termination_a == front_port:
+                    return trace_cable(next_cable.termination_b)
+                else:
+                    return trace_cable(next_cable.termination_a)
+
+            # Termination is not a panel port, so we've reached the end of the path
+            return termination
+
+        return trace_cable(self.termination_a), trace_cable(self.termination_b)

+ 5 - 4
netbox/dcim/signals.py

@@ -27,10 +27,11 @@ def update_connected_endpoints(instance, **kwargs):
     When a Cable is saved, update its connected endpoints.
     When a Cable is saved, update its connected endpoints.
     """
     """
     termination_a, termination_b = instance.get_path_endpoints()
     termination_a, termination_b = instance.get_path_endpoints()
-    termination_a.connected_endpoint = termination_b
-    termination_a.save()
-    termination_b.connected_endpoint = termination_a
-    termination_b.save()
+    if termination_a is not None and termination_b is not None:
+        termination_a.connected_endpoint = termination_b
+        termination_a.save()
+        termination_b.connected_endpoint = termination_a
+        termination_b.save()
 
 
 
 
 @receiver(post_delete, sender=Cable)
 @receiver(post_delete, sender=Cable)

+ 1 - 1
netbox/templates/dcim/inc/interface.html

@@ -106,7 +106,7 @@
                         <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
                         <i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% else %}
                 {% else %}
-                    <a href="{% url 'dcim:interface_connect' termination_a_id=device.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
+                    <a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
                         <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
                         <i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
                     </a>
                     </a>
                 {% endif %}
                 {% endif %}

+ 7 - 0
netbox/utilities/views.py

@@ -212,6 +212,13 @@ class ObjectEditView(GetReturnURLMixin, View):
             obj_created = not form.instance.pk
             obj_created = not form.instance.pk
             obj = form.save()
             obj = form.save()
 
 
+            print("Connecting {} {} to {} {}".format(
+                obj.termination_a.device,
+                obj.termination_a,
+                obj.termination_b.device,
+                obj.termination_b
+            ))
+
             msg = '{} {}'.format(
             msg = '{} {}'.format(
                 'Created' if obj_created else 'Modified',
                 'Created' if obj_created else 'Modified',
                 self.model._meta.verbose_name
                 self.model._meta.verbose_name