Extend custom collector to force expected ordering of cascading deletions

This commit is contained in:
Jeremy Stretch 2025-10-28 16:37:21 -04:00
parent 729b0365e0
commit bbf1f6181d

View File

@ -2,14 +2,14 @@ import logging
from django.contrib.contenttypes.fields import GenericRelation from django.contrib.contenttypes.fields import GenericRelation
from django.db import router from django.db import router
from django.db.models.deletion import Collector from django.db.models.deletion import CASCADE, Collector
logger = logging.getLogger("netbox.models.deletion") logger = logging.getLogger("netbox.models.deletion")
class CustomCollector(Collector): class CustomCollector(Collector):
""" """
Custom collector that handles GenericRelations correctly. Override Django's stock Collector to handle GenericRelations and ensure proper ordering of cascading deletions.
""" """
def collect( def collect(
@ -23,11 +23,15 @@ class CustomCollector(Collector):
keep_parents=False, keep_parents=False,
fail_on_restricted=True, fail_on_restricted=True,
): ):
""" # By default, Django will force the deletion of dependent objects before the parent only if the ForeignKey field
Override collect to first collect standard dependencies, # is not nullable. We want to ensure proper ordering regardless, so if the ForeignKey has `on_delete=CASCADE`
then add GenericRelations to the dependency graph. # applied, we set `nullable` to False when calling `collect()`.
""" if objs and source and source_attr:
# Call parent collect first to get all standard dependencies model = objs[0].__class__
field = model._meta.get_field(source_attr)
if field.remote_field.on_delete == CASCADE:
nullable = False
super().collect( super().collect(
objs, objs,
source=source, source=source,
@ -39,10 +43,8 @@ class CustomCollector(Collector):
fail_on_restricted=fail_on_restricted, fail_on_restricted=fail_on_restricted,
) )
# Track which GenericRelations we've already processed to prevent infinite recursion # Add GenericRelations to the dependency graph
processed_relations = set() processed_relations = set()
# Now add GenericRelations to the dependency graph
for _, instances in list(self.data.items()): for _, instances in list(self.data.items()):
for instance in instances: for instance in instances:
# Get all GenericRelations for this model # Get all GenericRelations for this model