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

This commit is contained in:
Jeremy Stretch 2018-11-08 12:15:56 -05:00
parent bb5432de7d
commit 3e92aa9fe7
4 changed files with 70 additions and 4 deletions

View File

@ -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 * [#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 * [#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 * [#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 ## API Changes

View File

@ -73,6 +73,18 @@ class CableTermination(models.Model):
null=True 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: class Meta:
abstract = True abstract = True

View File

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

View File

@ -149,3 +149,54 @@ class RackTestCase(TestCase):
face=None, face=None,
) )
self.assertTrue(pdu) 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)