mirror of
https://github.com/netbox-community/netbox.git
synced 2026-04-06 00:32:08 +02:00
Fixes #21618: Preserve cable terminations when bulk-editing cable profile
When `update_terminations(force=True)` is called (e.g. after a profile change), cache the termination objects from the database before deleting CableTermination records. Without this, the `a_terminations`/`b_terminations` properties fall back to querying the (now-empty) DB and return empty lists, resulting in all terminations being lost. Also removes a leftover debug print statement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
committed by
Jeremy Stretch
parent
cac3c1221c
commit
dad96c525f
@@ -293,7 +293,6 @@ class Cable(PrimaryModel):
|
||||
self._pk = self.pk
|
||||
|
||||
if self._orig_profile != self.profile:
|
||||
print(f'profile changed from {self._orig_profile} to {self.profile}')
|
||||
self.update_terminations(force=True)
|
||||
elif self._terminations_modified:
|
||||
self.update_terminations()
|
||||
@@ -403,6 +402,15 @@ class Cable(PrimaryModel):
|
||||
"""
|
||||
a_terminations, b_terminations = self.get_terminations()
|
||||
|
||||
# When force-recreating terminations (e.g. after a profile change), cache the termination objects
|
||||
# from the database before deleting, so they are available for recreation. Without this, the
|
||||
# a_terminations/b_terminations properties would query the DB after deletion and return empty lists.
|
||||
if force:
|
||||
if not hasattr(self, '_a_terminations'):
|
||||
self._a_terminations = list(a_terminations.keys())
|
||||
if not hasattr(self, '_b_terminations'):
|
||||
self._b_terminations = list(b_terminations.keys())
|
||||
|
||||
# Delete any stale CableTerminations
|
||||
for termination, ct in a_terminations.items():
|
||||
if force or (termination.pk and termination not in self.a_terminations):
|
||||
|
||||
@@ -1201,6 +1201,35 @@ class CableTestCase(TestCase):
|
||||
with self.assertRaises(ValidationError):
|
||||
cable.clean()
|
||||
|
||||
def test_cable_profile_change_preserves_terminations(self):
|
||||
"""
|
||||
When a Cable's profile is changed via save() without explicitly setting terminations (as happens during
|
||||
bulk edit), the existing termination points must be preserved.
|
||||
"""
|
||||
cable = Cable.objects.first()
|
||||
interface1 = Interface.objects.get(device__name='TestDevice1', name='eth0')
|
||||
interface2 = Interface.objects.get(device__name='TestDevice2', name='eth0')
|
||||
|
||||
# Verify initial state: cable has terminations and no profile
|
||||
self.assertEqual(cable.profile, '')
|
||||
self.assertEqual(CableTermination.objects.filter(cable=cable).count(), 2)
|
||||
|
||||
# Simulate what bulk edit does: load the cable from DB, set profile via setattr, and save.
|
||||
# Crucially, do NOT set a_terminations or b_terminations on the instance.
|
||||
cable_from_db = Cable.objects.get(pk=cable.pk)
|
||||
cable_from_db.profile = CableProfileChoices.SINGLE_1C1P
|
||||
cable_from_db.save()
|
||||
|
||||
# Verify terminations are preserved
|
||||
self.assertEqual(CableTermination.objects.filter(cable=cable).count(), 2)
|
||||
|
||||
# Verify the correct interfaces are still terminated
|
||||
cable_from_db.refresh_from_db()
|
||||
a_terms = [ct.termination for ct in CableTermination.objects.filter(cable=cable, cable_end='A')]
|
||||
b_terms = [ct.termination for ct in CableTermination.objects.filter(cable=cable, cable_end='B')]
|
||||
self.assertEqual(a_terms, [interface1])
|
||||
self.assertEqual(b_terms, [interface2])
|
||||
|
||||
|
||||
class VirtualDeviceContextTestCase(TestCase):
|
||||
|
||||
|
||||
Reference in New Issue
Block a user