소스 검색

Fixes #2571: Enforce deletion of attached cable when deleting a termination point

Jeremy Stretch 7 년 전
부모
커밋
3e92aa9fe7
4개의 변경된 파일70개의 추가작업 그리고 4개의 파일을 삭제
  1. 1 0
      CHANGELOG.md
  2. 12 0
      netbox/dcim/models.py
  3. 6 4
      netbox/dcim/signals.py
  4. 51 0
      netbox/dcim/tests/test_models.py

+ 1 - 0
CHANGELOG.md

@@ -43,6 +43,7 @@ NetBox now supports modeling physical cables for console, power, and interface c
 * [#2566](https://github.com/digitalocean/netbox/issues/2566) - Prevent both ends of a cable from connecting to the same termination point
 * [#2567](https://github.com/digitalocean/netbox/issues/2567) - Introduced proxy models to represent console/power/interface connections
 * [#2569](https://github.com/digitalocean/netbox/issues/2569) - Added LSH fiber type; removed SC duplex/simplex designations
+* [#2571](https://github.com/digitalocean/netbox/issues/2571) - Enforce deletion of attached cable when deleting a termination point
 
 ## API Changes
 

+ 12 - 0
netbox/dcim/models.py

@@ -73,6 +73,18 @@ class CableTermination(models.Model):
         null=True
     )
 
+    # Generic relations to Cable. These ensure that an attached Cable is deleted if the terminated object is deleted.
+    _cabled_as_a = GenericRelation(
+        to='dcim.Cable',
+        content_type_field='termination_a_type',
+        object_id_field='termination_a_id'
+    )
+    _cabled_as_b = GenericRelation(
+        to='dcim.Cable',
+        content_type_field='termination_b_type',
+        object_id_field='termination_b_id'
+    )
+
     class Meta:
         abstract = True
 

+ 6 - 4
netbox/dcim/signals.py

@@ -45,10 +45,12 @@ def update_connected_endpoints(instance, **kwargs):
 def nullify_connected_endpoints(instance, **kwargs):
 
     # Disassociate the Cable from its termination points
-    instance.termination_a.cable = None
-    instance.termination_a.save()
-    instance.termination_b.cable = None
-    instance.termination_b.save()
+    if instance.termination_a is not None:
+        instance.termination_a.cable = None
+        instance.termination_a.save()
+    if instance.termination_b is not None:
+        instance.termination_b.cable = None
+        instance.termination_b.save()
 
     # If this Cable was part of a complete path, tear it down
     endpoint_a, endpoint_b = instance.get_path_endpoints()

+ 51 - 0
netbox/dcim/tests/test_models.py

@@ -149,3 +149,54 @@ class RackTestCase(TestCase):
             face=None,
         )
         self.assertTrue(pdu)
+
+
+class CableTestCase(TestCase):
+
+    def setUp(self):
+
+        site = Site.objects.create(name='Test Site 1', slug='test-site-1')
+        manufacturer = Manufacturer.objects.create(name='Test Manufacturer 1', slug='test-manufacturer-1')
+        devicetype = DeviceType.objects.create(
+            manufacturer=manufacturer, model='Test Device Type 1', slug='test-device-type-1'
+        )
+        devicerole = DeviceRole.objects.create(
+            name='Test Device Role 1', slug='test-device-role-1', color='ff0000'
+        )
+        self.device1 = Device.objects.create(
+            device_type=devicetype, device_role=devicerole, name='TestDevice1', site=site
+        )
+        self.device2 = Device.objects.create(
+            device_type=devicetype, device_role=devicerole, name='TestDevice2', site=site
+        )
+        self.interface1 = Interface.objects.create(device=self.device1, name='eth0')
+        self.interface2 = Interface.objects.create(device=self.device2, name='eth0')
+        self.cable = Cable(termination_a=self.interface1, termination_b=self.interface2)
+        self.cable.save()
+
+    def test_cable_creation(self):
+        """
+        When a new Cable is created, it must be cached on either termination point.
+        """
+        interface1 = Interface.objects.get(pk=self.interface1.pk)
+        self.assertEqual(self.cable.termination_a, interface1)
+        interface2 = Interface.objects.get(pk=self.interface2.pk)
+        self.assertEqual(self.cable.termination_b, interface2)
+
+    def test_cable_deletion(self):
+        """
+        When a Cable is deleted, the `cable` field on its termination points must be nullified.
+        """
+        self.cable.delete()
+        interface1 = Interface.objects.get(pk=self.interface1.pk)
+        self.assertIsNone(interface1.cable)
+        interface2 = Interface.objects.get(pk=self.interface2.pk)
+        self.assertIsNone(interface2.cable)
+
+    def test_cabletermination_deletion(self):
+        """
+        When a CableTermination object is deleted, its attached Cable (if any) must also be deleted.
+        """
+        self.interface1.delete()
+        cable = Cable.objects.filter(pk=self.cable.pk).first()
+        self.assertIsNone(cable)