fix(dcim): Nullify empty cable_end values instead of empty strings

Replace empty strings with null values for cable_end fields across
cable termination models. Adds data migrations to clean up inconsistent
values from earlier versions that wrote empty strings when cables were
deleted.

Fixes #22768
This commit is contained in:
Martin Hauser 2026-07-25 13:52:12 +02:00
parent ba5018a3b4
commit 91b51f62d6
No known key found for this signature in database
5 changed files with 65 additions and 7 deletions

View File

@ -0,0 +1,23 @@
from django.db import migrations
def nullify_empty_cable_end(apps, schema_editor):
"""
Replace empty strings with null values on cached cable end data. Earlier versions
wrote an empty string when a cable termination was deleted, leaving disconnected
terminations inconsistent with those which have never been cabled.
"""
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
db_alias = schema_editor.connection.alias
CircuitTermination.objects.using(db_alias).filter(cable_end='').update(cable_end=None)
class Migration(migrations.Migration):
dependencies = [
('circuits', '0058_clear_stale_cable_profile_data'),
]
operations = [
migrations.RunPython(nullify_empty_cable_end, migrations.RunPython.noop),
]

View File

@ -0,0 +1,35 @@
from django.db import migrations
CABLED_MODELS = (
'ConsolePort',
'ConsoleServerPort',
'FrontPort',
'Interface',
'PowerFeed',
'PowerOutlet',
'PowerPort',
'RearPort',
)
def nullify_empty_cable_end(apps, schema_editor):
"""
Replace empty strings with null values on cached cable end data. Earlier versions
wrote an empty string when a cable termination was deleted, leaving disconnected
endpoints inconsistent with those which have never been cabled.
"""
db_alias = schema_editor.connection.alias
for model_name in CABLED_MODELS:
model = apps.get_model('dcim', model_name)
model.objects.using(db_alias).filter(cable_end='').update(cable_end=None)
class Migration(migrations.Migration):
dependencies = [
('dcim', '0240_clear_stale_cable_profile_data'),
]
operations = [
migrations.RunPython(nullify_empty_cable_end, migrations.RunPython.noop),
]

View File

@ -205,7 +205,7 @@ def nullify_connected_endpoints(instance, **kwargs):
model = instance.termination_type.model_class()
model.objects.filter(pk=instance.termination_id).update(
cable=None,
cable_end='',
cable_end=None,
cable_connector=None,
cable_positions=None,
)

View File

@ -60,11 +60,11 @@ class LegacyCablePathTestCase(BaseCablePathTestCase):
interface2.refresh_from_db()
self.assertIsNone(interface1.cable_id)
self.assertEqual(interface1.cable_end, '')
self.assertIsNone(interface1.cable_end)
self.assertPathIsNotSet(interface1)
self.assertIsNone(interface2.cable_id)
self.assertEqual(interface2.cable_end, '')
self.assertIsNone(interface2.cable_end)
self.assertPathIsNotSet(interface2)
def test_102_consoleport_to_consoleserverport(self):

View File

@ -272,8 +272,8 @@ class CableSignalTestCase(TestCase):
self.assertIsNone(interface_b._path_id)
self.assertIsNone(interface_a.cable_id)
self.assertIsNone(interface_b.cable_id)
self.assertEqual(interface_a.cable_end, '')
self.assertEqual(interface_b.cable_end, '')
self.assertIsNone(interface_a.cable_end)
self.assertIsNone(interface_b.cable_end)
def test_deleting_profiled_cable_nullifies_endpoints(self):
"""
@ -301,7 +301,7 @@ class CableSignalTestCase(TestCase):
for interface in (interface_a, interface_b):
interface.refresh_from_db()
self.assertIsNone(interface.cable_id)
self.assertEqual(interface.cable_end, '')
self.assertIsNone(interface.cable_end)
self.assertIsNone(interface.cable_connector)
self.assertIsNone(interface.cable_positions)
@ -367,7 +367,7 @@ class CableSignalTestCase(TestCase):
termination.delete()
interface_a.refresh_from_db()
self.assertIsNone(interface_a.cable_id)
self.assertEqual(interface_a.cable_end, '')
self.assertIsNone(interface_a.cable_end)
class MACAddressInterfaceSignalTestCase(TestCase):