jeremystretch 3 лет назад
Родитель
Сommit
6280398bc1
2 измененных файлов с 96 добавлено и 53 удалено
  1. 44 19
      netbox/dcim/models/cables.py
  2. 52 34
      netbox/dcim/tests/test_models.py

+ 44 - 19
netbox/dcim/models/cables.py

@@ -143,6 +143,50 @@ class Cable(NetBoxModel):
         elif self.length is None:
             self.length_unit = ''
 
+        a_terminations = [
+            CableTermination(cable=self, cable_end='A', termination=t) for t in getattr(self, 'a_terminations', [])
+        ]
+        b_terminations = [
+            CableTermination(cable=self, cable_end='B', termination=t) for t in getattr(self, 'b_terminations', [])
+        ]
+
+        # Check that all termination objects for either end are of the same type
+        for terms in (a_terminations, b_terminations):
+            if terms and len(terms) > 1:
+                if not all(t.termination.parent_object == terms[0].termination.parent_object for t in terms[1:]):
+                    raise ValidationError(
+                        "All terminations on one end of a cable must belong to the same parent object."
+                    )
+                if not all(t.termination_type == terms[0].termination_type for t in terms[1:]):
+                    raise ValidationError(
+                        "Cannot connect different termination types to same end of cable."
+                    )
+
+        # Check that termination types are compatible
+        if a_terminations and b_terminations:
+            a_type = a_terminations[0].termination_type.model
+            b_type = b_terminations[0].termination_type.model
+            if b_type not in COMPATIBLE_TERMINATION_TYPES.get(a_type):
+                raise ValidationError(
+                    f"Incompatible termination types: {a_type} and {b_type}"
+                )
+
+        # Run clean() on any new CableTerminations
+        for cabletermination in [*a_terminations, *b_terminations]:
+            cabletermination.clean()
+
+        # TODO
+        # # A front port cannot be connected to its corresponding rear port
+        # if (
+        #     type_a in ['frontport', 'rearport'] and
+        #     type_b in ['frontport', 'rearport'] and
+        #     (
+        #         getattr(self.termination_a, 'rear_port', None) == self.termination_b or
+        #         getattr(self.termination_b, 'rear_port', None) == self.termination_a
+        #     )
+        # ):
+        #     raise ValidationError("A front port cannot be connected to it corresponding rear port")
+
     def save(self, *args, **kwargs):
         _created = self.pk is None
 
@@ -258,25 +302,6 @@ class CableTermination(models.Model):
                 'termination': "Circuit terminations attached to a provider network may not be cabled."
             })
 
-        # TODO
-        # # A front port cannot be connected to its corresponding rear port
-        # if (
-        #     type_a in ['frontport', 'rearport'] and
-        #     type_b in ['frontport', 'rearport'] and
-        #     (
-        #         getattr(self.termination_a, 'rear_port', None) == self.termination_b or
-        #         getattr(self.termination_b, 'rear_port', None) == self.termination_a
-        #     )
-        # ):
-        #     raise ValidationError("A front port cannot be connected to it corresponding rear port")
-
-        # TODO
-        # # Check that termination types are compatible
-        # if type_b not in COMPATIBLE_TERMINATION_TYPES.get(type_a):
-        #     raise ValidationError(
-        #         f"Incompatible termination types: {self.termination_a_type} and {self.termination_b_type}"
-        #     )
-
     def save(self, *args, **kwargs):
         super().save(*args, **kwargs)
 

+ 52 - 34
netbox/dcim/tests/test_models.py

@@ -521,59 +521,77 @@ class CableTestCase(TestCase):
         self.assertIsNone(interface2.cable)
         self.assertIsNone(interface2._link_peer)
 
-    def test_cable_validates_compatible_types(self):
+    def test_cable_validates_same_parent_object(self):
         """
-        The clean method should have a check to ensure only compatible port types can be connected by a cable
+        The clean method should ensure that all terminations at either end of a Cable belong to the same parent object.
         """
-        # An interface cannot be connected to a power port
         cable = Cable(a_terminations=[self.interface1], b_terminations=[self.power_port1])
         with self.assertRaises(ValidationError):
             cable.clean()
 
-    def test_cable_front_port_cannot_connect_to_corresponding_rear_port(self):
+    def test_cable_validates_same_type(self):
         """
-        A cable cannot connect a front port to its corresponding rear port
+        The clean method should ensure that all terminations at either end of a Cable are of the same type.
         """
-        cable = Cable(a_terminations=[self.front_port1], b_terminations=[self.rear_port1])
+        cable = Cable(a_terminations=[self.front_port1, self.rear_port1], b_terminations=[self.interface1])
         with self.assertRaises(ValidationError):
             cable.clean()
 
-    def test_cable_cannot_terminate_to_a_provider_network_circuittermination(self):
+    def test_cable_validates_compatible_types(self):
         """
-        Neither side of a cable can be terminated to a CircuitTermination which is attached to a ProviderNetwork
+        The clean method should have a check to ensure only compatible port types can be connected by a cable
         """
-        cable = Cable(a_terminations=[self.interface3], b_terminations=[self.circuittermination3])
+        # An interface cannot be connected to a power port
+        cable = Cable(a_terminations=[self.interface1, self.interface2], b_terminations=[self.interface3])
         with self.assertRaises(ValidationError):
             cable.clean()
 
-    def test_rearport_connections(self):
+    # TODO: Remove this?
+    # def test_cable_front_port_cannot_connect_to_corresponding_rear_port(self):
+    #     """
+    #     A cable cannot connect a front port to its corresponding rear port
+    #     """
+    #     cable = Cable(a_terminations=[self.front_port1], b_terminations=[self.rear_port1])
+    #     with self.assertRaises(ValidationError):
+    #         cable.clean()
+
+    def test_cable_cannot_terminate_to_a_provider_network_circuittermination(self):
         """
-        Test various combinations of RearPort connections.
+        Neither side of a cable can be terminated to a CircuitTermination which is attached to a ProviderNetwork
         """
-        # Connecting a single-position RearPort to a multi-position RearPort is ok
-        Cable(a_terminations=[self.rear_port1], b_terminations=[self.rear_port2]).full_clean()
-
-        # Connecting a single-position RearPort to an Interface is ok
-        Cable(a_terminations=[self.rear_port1], b_terminations=[self.interface3]).full_clean()
-
-        # Connecting a single-position RearPort to a CircuitTermination is ok
-        Cable(a_terminations=[self.rear_port1], b_terminations=[self.circuittermination1]).full_clean()
-
-        # Connecting a multi-position RearPort to another RearPort with the same number of positions is ok
-        Cable(a_terminations=[self.rear_port3], b_terminations=[self.rear_port4]).full_clean()
-
-        # Connecting a multi-position RearPort to an Interface is ok
-        Cable(a_terminations=[self.rear_port2], b_terminations=[self.interface3]).full_clean()
-
-        # Connecting a multi-position RearPort to a CircuitTermination is ok
-        Cable(a_terminations=[self.rear_port2], b_terminations=[self.circuittermination1]).full_clean()
+        cable = Cable(a_terminations=[self.interface3], b_terminations=[self.circuittermination3])
+        with self.assertRaises(ValidationError):
+            cable.clean()
 
-        # Connecting a two-position RearPort to a three-position RearPort is NOT ok
-        with self.assertRaises(
-            ValidationError,
-                msg='Connecting a 2-position RearPort to a 3-position RearPort should fail'
-        ):
-            Cable(a_terminations=[self.rear_port2], b_terminations=[self.rear_port3]).full_clean()
+    # TODO: Remove this?
+    # def test_rearport_connections(self):
+    #     """
+    #     Test various combinations of RearPort connections.
+    #     """
+    #     # Connecting a single-position RearPort to a multi-position RearPort is ok
+    #     Cable(a_terminations=[self.rear_port1], b_terminations=[self.rear_port2]).full_clean()
+    #
+    #     # Connecting a single-position RearPort to an Interface is ok
+    #     Cable(a_terminations=[self.rear_port1], b_terminations=[self.interface3]).full_clean()
+    #
+    #     # Connecting a single-position RearPort to a CircuitTermination is ok
+    #     Cable(a_terminations=[self.rear_port1], b_terminations=[self.circuittermination1]).full_clean()
+    #
+    #     # Connecting a multi-position RearPort to another RearPort with the same number of positions is ok
+    #     Cable(a_terminations=[self.rear_port3], b_terminations=[self.rear_port4]).full_clean()
+    #
+    #     # Connecting a multi-position RearPort to an Interface is ok
+    #     Cable(a_terminations=[self.rear_port2], b_terminations=[self.interface3]).full_clean()
+    #
+    #     # Connecting a multi-position RearPort to a CircuitTermination is ok
+    #     Cable(a_terminations=[self.rear_port2], b_terminations=[self.circuittermination1]).full_clean()
+    #
+    #     # Connecting a two-position RearPort to a three-position RearPort is NOT ok
+    #     with self.assertRaises(
+    #         ValidationError,
+    #         msg='Connecting a 2-position RearPort to a 3-position RearPort should fail'
+    #     ):
+    #         Cable(a_terminations=[self.rear_port2], b_terminations=[self.rear_port3]).full_clean()
 
     def test_cable_cannot_terminate_to_a_virtual_interface(self):
         """