mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-23 17:08:41 -06:00
Fixes #14079: Explicitly remove M2M assignments to objects being deleted to ensure change logging
This commit is contained in:
parent
de5c5aeb2a
commit
bd7d4a3f34
@ -3,6 +3,7 @@ import logging
|
|||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db.models.fields.reverse_related import ManyToManyRel
|
||||||
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
from django.db.models.signals import m2m_changed, post_save, pre_delete
|
||||||
from django.dispatch import receiver, Signal
|
from django.dispatch import receiver, Signal
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -15,6 +16,7 @@ from extras.models import EventRule
|
|||||||
from extras.validators import CustomValidator
|
from extras.validators import CustomValidator
|
||||||
from netbox.config import get_config
|
from netbox.config import get_config
|
||||||
from netbox.context import current_request, events_queue
|
from netbox.context import current_request, events_queue
|
||||||
|
from netbox.models.features import ChangeLoggingMixin
|
||||||
from netbox.signals import post_clean
|
from netbox.signals import post_clean
|
||||||
from utilities.exceptions import AbortRequest
|
from utilities.exceptions import AbortRequest
|
||||||
from .choices import ObjectChangeActionChoices
|
from .choices import ObjectChangeActionChoices
|
||||||
@ -68,7 +70,7 @@ def handle_changed_object(sender, instance, **kwargs):
|
|||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Create/update an ObejctChange record for this change
|
# Create/update an ObjectChange record for this change
|
||||||
objectchange = instance.to_objectchange(action)
|
objectchange = instance.to_objectchange(action)
|
||||||
# If this is a many-to-many field change, check for a previous ObjectChange instance recorded
|
# If this is a many-to-many field change, check for a previous ObjectChange instance recorded
|
||||||
# for this object by this request and update it
|
# for this object by this request and update it
|
||||||
@ -122,6 +124,25 @@ def handle_deleted_object(sender, instance, **kwargs):
|
|||||||
objectchange.request_id = request.id
|
objectchange.request_id = request.id
|
||||||
objectchange.save()
|
objectchange.save()
|
||||||
|
|
||||||
|
# Django does not automatically send an m2m_changed signal for the reverse direction of a
|
||||||
|
# many-to-many relationship (see https://code.djangoproject.com/ticket/17688), so we need to
|
||||||
|
# trigger one manually. We do this by checking for any reverse M2M relationships on the
|
||||||
|
# instance being deleted, and explicitly call .remove() on the remote M2M field to delete
|
||||||
|
# the association. This triggers an m2m_changed signal with the `post_remove` action type
|
||||||
|
# for the forward direction of the relationship, ensuring that the change is recorded.
|
||||||
|
for relation in instance._meta.related_objects:
|
||||||
|
if type(relation) is not ManyToManyRel:
|
||||||
|
continue
|
||||||
|
related_model = relation.related_model
|
||||||
|
related_field_name = relation.remote_field.name
|
||||||
|
if not issubclass(related_model, ChangeLoggingMixin):
|
||||||
|
# We only care about triggering the m2m_changed signal for models which support
|
||||||
|
# change logging
|
||||||
|
continue
|
||||||
|
for obj in related_model.objects.filter(**{related_field_name: instance.pk}):
|
||||||
|
obj.snapshot() # Ensure the change record includes the "before" state
|
||||||
|
getattr(obj, related_field_name).remove(instance)
|
||||||
|
|
||||||
# Enqueue webhooks
|
# Enqueue webhooks
|
||||||
queue = events_queue.get()
|
queue = events_queue.get()
|
||||||
enqueue_object(queue, instance, request.user, request.id, ObjectChangeActionChoices.ACTION_DELETE)
|
enqueue_object(queue, instance, request.user, request.id, ObjectChangeActionChoices.ACTION_DELETE)
|
||||||
|
Loading…
Reference in New Issue
Block a user