fix(dcim): Refresh Device search cache on VC rename

Add post_save signal handler to update cached virtual_chassis field for
member Devices when a VirtualChassis is renamed. Skip updates for
creates, raw saves, or targeted saves excluding the name field.

Fixes stale search cache entries after VirtualChassis name changes.

Fixes #22489
This commit is contained in:
Martin Hauser 2026-06-22 16:34:21 +02:00 committed by Jeremy Stretch
parent d7c566aee6
commit a1db254104
2 changed files with 86 additions and 0 deletions

View File

@ -6,6 +6,7 @@ from django.dispatch import receiver
from dcim.choices import CableEndChoices, LinkStatusChoices
from ipam.models import Prefix
from netbox.search.backends import search_backend
from virtualization.models import Cluster, VMInterface
from wireless.models import WirelessLAN
@ -33,6 +34,7 @@ from .models import (
VirtualChassis,
)
from .models.cables import trace_paths
from .search import DeviceIndex
from .utils import create_cablepaths, rebuild_paths
COMPONENT_MODELS = (
@ -116,6 +118,24 @@ def assign_virtualchassis_master(instance, created, **kwargs):
master.save()
@receiver(post_save, sender=VirtualChassis)
def update_virtualchassis_member_search_cache(instance, created, raw=False, update_fields=None, **kwargs):
"""
Refresh the search cache for member Devices when a VirtualChassis is renamed. DeviceIndex caches
virtual_chassis as its string value, so a rename would otherwise leave stale CachedValue entries.
"""
if raw or created:
return
# The VC name is the only VC attribute cached on member Devices; skip saves that can't change it.
if update_fields is not None and 'name' not in update_fields:
return
search_backend.cache(
Device.objects.filter(virtual_chassis=instance).select_related('virtual_chassis'),
indexer=DeviceIndex,
remove_existing=True
)
#
# Cables
#

View File

@ -0,0 +1,66 @@
from django.test import TestCase
from core.models import ObjectType
from dcim.models import Device, VirtualChassis
from extras.models import CachedValue
from utilities.testing import create_test_device
class VirtualChassisSearchCacheTestCase(TestCase):
def setUp(self):
self.vc = VirtualChassis.objects.create(name='VC1')
self.device = create_test_device('Switch-1', virtual_chassis=self.vc, vc_position=1)
self.object_type = ObjectType.objects.get_for_model(Device)
def test_renaming_virtual_chassis_refreshes_member_search_cache(self):
"""
Renaming a VirtualChassis updates the cached virtual_chassis value for its member Devices.
"""
# The member device is initially cached under the original VC name
self.assertTrue(
CachedValue.objects.filter(
object_type=self.object_type,
object_id=self.device.pk,
field='virtual_chassis',
value='VC1',
).exists()
)
# Rename the VirtualChassis
self.vc.name = 'VC-test'
self.vc.save()
# The stale entry is gone and a fresh one reflects the new name
self.assertFalse(
CachedValue.objects.filter(
object_type=self.object_type,
object_id=self.device.pk,
field='virtual_chassis',
value='VC1',
).exists()
)
self.assertTrue(
CachedValue.objects.filter(
object_type=self.object_type,
object_id=self.device.pk,
field='virtual_chassis',
value='VC-test',
).exists()
)
def test_updating_virtual_chassis_without_name_change_keeps_member_cache(self):
"""
A targeted save excluding 'name' leaves the member Device search cache untouched.
"""
self.vc.domain = 'example'
self.vc.save(update_fields=['domain'])
self.assertTrue(
CachedValue.objects.filter(
object_type=self.object_type,
object_id=self.device.pk,
field='virtual_chassis',
value='VC1',
).exists()
)