Fix on delete cascade entity order

Since [#20708](https://github.com/netbox-community/netbox/pull/20708)
relation with a on delete RESTRICT are not deleted in the proper order.
Then the error `violate not-null constraint` occurs and breaks the
delete cascade feature.
This commit is contained in:
Vincent Simonin 2025-12-08 17:06:13 +01:00
parent 269112a565
commit 605c61ef5b
No known key found for this signature in database

View File

@ -3,7 +3,7 @@ from threading import local
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.db.models import CASCADE
from django.db.models import CASCADE, RESTRICT
from django.db.models.fields.reverse_related import ManyToManyRel, ManyToOneRel
from django.db.models.signals import m2m_changed, post_migrate, post_save, pre_delete
from django.dispatch import receiver, Signal
@ -47,6 +47,7 @@ clear_events = Signal()
# Object types
#
@receiver(post_migrate)
def update_object_types(sender, **kwargs):
"""
@ -133,7 +134,7 @@ def handle_changed_object(sender, instance, **kwargs):
prev_change := ObjectChange.objects.filter(
changed_object_type=ContentType.objects.get_for_model(instance),
changed_object_id=instance.pk,
request_id=request.id
request_id=request.id,
).first()
):
prev_change.postchange_data = objectchange.postchange_data
@ -172,9 +173,7 @@ def handle_deleted_object(sender, instance, **kwargs):
try:
run_validators(instance, validators)
except ValidationError as e:
raise AbortRequest(
_("Deletion is prevented by a protection rule: {message}").format(message=e)
)
raise AbortRequest(_("Deletion is prevented by a protection rule: {message}").format(message=e))
# Get the current request, or bail if not set
request = current_request.get()
@ -221,7 +220,12 @@ def handle_deleted_object(sender, instance, **kwargs):
obj.snapshot() # Ensure the change record includes the "before" state
if type(relation) is ManyToManyRel:
getattr(obj, related_field_name).remove(instance)
elif type(relation) is ManyToOneRel and relation.null and relation.on_delete is not CASCADE:
elif (
type(relation) is ManyToOneRel
and relation.null
and relation.on_delete is not CASCADE
and relation.on_delete is not RESTRICT
):
setattr(obj, related_field_name, None)
obj.save()
@ -256,6 +260,7 @@ def clear_events_queue(sender, **kwargs):
# DataSource handlers
#
@receiver(post_save, sender=DataSource)
def enqueue_sync_job(instance, created, **kwargs):
"""
@ -267,9 +272,10 @@ def enqueue_sync_job(instance, created, **kwargs):
SyncDataSourceJob.enqueue_once(instance, interval=instance.sync_interval)
elif not created:
# Delete any previously scheduled recurring jobs for this DataSource
for job in SyncDataSourceJob.get_jobs(instance).defer('data').filter(
interval__isnull=False,
status=JobStatusChoices.STATUS_SCHEDULED
for job in (
SyncDataSourceJob.get_jobs(instance)
.defer('data')
.filter(interval__isnull=False, status=JobStatusChoices.STATUS_SCHEDULED)
):
# Call delete() per instance to ensure the associated background task is deleted as well
job.delete()