瀏覽代碼

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
Martin Hauser 1 周之前
父節點
當前提交
a1db254104
共有 2 個文件被更改,包括 86 次插入0 次删除
  1. 20 0
      netbox/dcim/signals.py
  2. 66 0
      netbox/dcim/tests/test_search.py

+ 20 - 0
netbox/dcim/signals.py

@@ -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
 #

+ 66 - 0
netbox/dcim/tests/test_search.py

@@ -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()
+        )