From 4a95cfd1c4435e6eda01745fe06d902c25d2493e Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Thu, 3 Nov 2022 08:48:13 -0400 Subject: [PATCH] Permanently connect change logging & webhook receivers --- netbox/extras/context_managers.py | 16 --------------- netbox/extras/signals.py | 34 +++++++++++++++++++++++-------- netbox/netbox/context.py | 2 +- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/netbox/extras/context_managers.py b/netbox/extras/context_managers.py index d4aeb8364..32323999e 100644 --- a/netbox/extras/context_managers.py +++ b/netbox/extras/context_managers.py @@ -1,8 +1,5 @@ from contextlib import contextmanager -from django.db.models.signals import m2m_changed, pre_delete, post_save - -from extras.signals import clear_webhooks, clear_webhook_queue, handle_changed_object, handle_deleted_object from netbox.context import current_request, webhooks_queue from .webhooks import flush_webhooks @@ -18,21 +15,8 @@ def change_logging(request): current_request.set(request) webhooks_queue.set([]) - # Connect our receivers to the post_save and post_delete signals. - post_save.connect(handle_changed_object, dispatch_uid='handle_changed_object') - m2m_changed.connect(handle_changed_object, dispatch_uid='handle_changed_object') - pre_delete.connect(handle_deleted_object, dispatch_uid='handle_deleted_object') - clear_webhooks.connect(clear_webhook_queue, dispatch_uid='clear_webhook_queue') - yield - # Disconnect change logging signals. This is necessary to avoid recording any errant - # changes during test cleanup. - post_save.disconnect(handle_changed_object, dispatch_uid='handle_changed_object') - m2m_changed.disconnect(handle_changed_object, dispatch_uid='handle_changed_object') - pre_delete.disconnect(handle_deleted_object, dispatch_uid='handle_deleted_object') - clear_webhooks.disconnect(clear_webhook_queue, dispatch_uid='clear_webhook_queue') - # Flush queued webhooks to RQ flush_webhooks(webhooks_queue.get()) diff --git a/netbox/extras/signals.py b/netbox/extras/signals.py index 8854d6314..31e0c126c 100644 --- a/netbox/extras/signals.py +++ b/netbox/extras/signals.py @@ -14,6 +14,7 @@ from .choices import ObjectChangeActionChoices from .models import ConfigRevision, CustomField, ObjectChange from .webhooks import enqueue_object, get_snapshots, serialize_for_webhook + # # Change logging/webhooks # @@ -22,22 +23,32 @@ from .webhooks import enqueue_object, get_snapshots, serialize_for_webhook clear_webhooks = Signal() +def is_same_object(instance, webhook_data, request_id): + """ + Compare the given instance to the most recent queued webhook object, returning True + if they match. This check is used to avoid creating duplicate webhook entries. + """ + return ( + ContentType.objects.get_for_model(instance) == webhook_data['content_type'] and + instance.pk == webhook_data['object_id'] and + request_id == webhook_data['request_id'] + ) + + +@receiver((post_save, m2m_changed)) def handle_changed_object(sender, instance, **kwargs): """ Fires when an object is created or updated. """ + m2m_changed = False + if not hasattr(instance, 'to_objectchange'): return + # Get the current request, or bail if not set request = current_request.get() - m2m_changed = False - - def is_same_object(instance, webhook_data): - return ( - ContentType.objects.get_for_model(instance) == webhook_data['content_type'] and - instance.pk == webhook_data['object_id'] and - request.id == webhook_data['request_id'] - ) + if request is None: + return # Determine the type of change being made if kwargs.get('created'): @@ -69,7 +80,7 @@ def handle_changed_object(sender, instance, **kwargs): # If this is an M2M change, update the previously queued webhook (from post_save) queue = webhooks_queue.get() - if m2m_changed and queue and is_same_object(instance, queue[-1]): + if m2m_changed and queue and is_same_object(instance, queue[-1], request.id): instance.refresh_from_db() # Ensure that we're working with fresh M2M assignments queue[-1]['data'] = serialize_for_webhook(instance) queue[-1]['snapshots']['postchange'] = get_snapshots(instance, action)['postchange'] @@ -84,6 +95,7 @@ def handle_changed_object(sender, instance, **kwargs): model_updates.labels(instance._meta.model_name).inc() +@receiver(pre_delete) def handle_deleted_object(sender, instance, **kwargs): """ Fires when an object is deleted. @@ -91,7 +103,10 @@ def handle_deleted_object(sender, instance, **kwargs): if not hasattr(instance, 'to_objectchange'): return + # Get the current request, or bail if not set request = current_request.get() + if request is None: + return # Record an ObjectChange if applicable if hasattr(instance, 'to_objectchange'): @@ -109,6 +124,7 @@ def handle_deleted_object(sender, instance, **kwargs): model_deletes.labels(instance._meta.model_name).inc() +@receiver(clear_webhooks) def clear_webhook_queue(sender, **kwargs): """ Delete any queued webhooks (e.g. because of an aborted bulk transaction) diff --git a/netbox/netbox/context.py b/netbox/netbox/context.py index 02c6fccae..b5e4dc28e 100644 --- a/netbox/netbox/context.py +++ b/netbox/netbox/context.py @@ -6,5 +6,5 @@ __all__ = ( ) -current_request = ContextVar('current_request') +current_request = ContextVar('current_request', default=None) webhooks_queue = ContextVar('webhooks_queue')