Răsfoiți Sursa

delete handling

Arthur 2 zile în urmă
părinte
comite
325959fe36

+ 16 - 0
netbox/circuits/apps.py

@@ -1,4 +1,20 @@
 from django.apps import AppConfig
+from django.db.models.signals import pre_delete
+
+
+def _clear_circuit_termination_pointer(sender, **kwargs):
+    from .models import CircuitTermination
+    from .signals import clear_circuit_termination_pointer
+
+    if sender is CircuitTermination:
+        clear_circuit_termination_pointer(**kwargs)
+
+
+# This module is imported in populate() phase 1, ahead of the models phase which connects
+# core.signals.handle_deleted_object. Connecting here records the Circuit pointer clear before the
+# termination's own DELETE; branch revert replays newest-first and needs the termination restored
+# before the pointer referencing it. (#23134)
+pre_delete.connect(_clear_circuit_termination_pointer)
 
 
 class CircuitsConfig(AppConfig):

+ 3 - 2
netbox/circuits/signals.py

@@ -1,4 +1,4 @@
-from django.db.models.signals import post_delete, post_save, pre_delete
+from django.db.models.signals import post_delete, post_save
 from django.dispatch import receiver
 
 from dcim.signals import rebuild_paths
@@ -17,12 +17,13 @@ def rebuild_cablepaths(instance, raw=False, **kwargs):
             rebuild_paths([peer_termination])
 
 
-@receiver(pre_delete, sender=CircuitTermination)
 def clear_circuit_termination_pointer(instance, using=None, origin=None, **kwargs):
     """
     Clear the parent Circuit's cached `termination_a`/`termination_z` pointer with a change-logged
     save. on_delete=SET_NULL clears it via a bulk UPDATE, and related_name='+' hides the relation
     from Circuit._meta.related_objects, so neither path records an ObjectChange. (#23134)
+
+    Connected in CircuitsConfig, not here, so that it precedes handle_deleted_object.
     """
     if not instance.term_side:
         return

+ 3 - 5
netbox/circuits/tests/test_models.py

@@ -482,14 +482,12 @@ class CircuitTerminationChangeLoggingTestCase(TestCase):
         self.assertEqual(changes[0].prechange_data['termination_a'], termination_pk)
         self.assertIsNone(changes[0].postchange_data['termination_a'])
 
-        # core.signals.handle_deleted_object is connected before this app's receiver, so the
-        # DELETE precedes the pointer clear. Replaying in this order relies on the consumer
-        # applying the DELETE through the ORM, where on_delete=SET_NULL clears the pointer, or
-        # on the FK being DEFERRABLE INITIALLY DEFERRED within one transaction.
+        # The pointer clear must precede the DELETE, so that a consumer replaying in reverse
+        # restores the termination before the record which references it
         termination_delete = self._termination_change(
             termination_pk, ObjectChangeActionChoices.ACTION_DELETE
         )
-        self.assertLess(termination_delete.pk, changes[0].pk)
+        self.assertLess(changes[0].pk, termination_delete.pk)
 
     @tag('regression')  # Ref: #23134
     def test_bulk_deletion_records_circuit_update(self):