1
0
mirror of https://github.com/netbox-community/netbox.git synced 2024-05-10 07:54:54 +00:00

Webhook signal refactor - fixes #2282 (#2260)

Refactor of webhook signaling system to use the same middleware mechanics of Changelogging
This commit is contained in:
John Anderson
2018-07-30 14:23:49 -04:00
committed by Jeremy Stretch
parent 9876a2efcd
commit 722d0d5554
17 changed files with 65 additions and 192 deletions

View File

@ -1,119 +1,54 @@
import time
import datetime
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q
from django.db.models.signals import post_save, post_delete
from django.dispatch import Signal
from extras.models import Webhook
from utilities.utils import dynamic_import
from extras.constants import OBJECTCHANGE_ACTION_CREATE, OBJECTCHANGE_ACTION_DELETE, OBJECTCHANGE_ACTION_UPDATE
from utilities.api import get_serializer_for_model
def enqueue_webhooks(webhooks, model_class, data, event, signal_received_timestamp):
def enqueue_webhooks(instance, action):
"""
Serialize data and enqueue webhooks
Find Webhook(s) assigned to this instance + action and enqueue them
to be processed
"""
serializer_context = {
'request': None,
}
type_create = action == OBJECTCHANGE_ACTION_CREATE
type_update = action == OBJECTCHANGE_ACTION_UPDATE
type_delete = action == OBJECTCHANGE_ACTION_DELETE
if isinstance(data, list):
serializer_property = data[0].serializer
serializer_cls = dynamic_import(serializer_property)
serialized_data = serializer_cls(data, context=serializer_context, many=True)
else:
serializer_property = data.serializer
serializer_cls = dynamic_import(serializer_property)
serialized_data = serializer_cls(data, context=serializer_context)
# Find assigned webhooks
obj_type = ContentType.objects.get_for_model(instance.__class__)
webhooks = Webhook.objects.filter(
Q(enabled=True) &
(
Q(type_create=type_create) |
Q(type_update=type_update) |
Q(type_delete=type_delete)
) &
Q(obj_type=obj_type)
)
from django_rq import get_queue
webhook_queue = get_queue('default')
if webhooks:
# Get the Model's API serializer class and serialize the object
serializer_class = get_serializer_for_model(instance.__class__)
serializer_context = {
'request': None,
}
serializer = serializer_class(instance, context=serializer_context)
for webhook in webhooks:
webhook_queue.enqueue("extras.webhooks_worker.process_webhook",
webhook,
serialized_data.data,
model_class,
event,
signal_received_timestamp)
# We must only import django_rq if the Webhooks feature is enabled.
# Only if we have gotten to ths point, is the feature enabled
from django_rq import get_queue
webhook_queue = get_queue('default')
def post_save_receiver(sender, instance, created, **kwargs):
"""
Receives post_save signals from registered models. If the webhook
backend is enabled, queue any webhooks that apply to the event.
"""
if settings.WEBHOOKS_ENABLED:
signal_received_timestamp = time.time()
# look for any webhooks that match this event
updated = not created
obj_type = ContentType.objects.get_for_model(sender)
webhooks = Webhook.objects.filter(
Q(enabled=True) &
(
Q(type_create=created) |
Q(type_update=updated)
) &
Q(obj_type=obj_type)
)
event = 'created' if created else 'updated'
if webhooks:
enqueue_webhooks(webhooks, sender, instance, event, signal_received_timestamp)
def post_delete_receiver(sender, instance, **kwargs):
"""
Receives post_delete signals from registered models. If the webhook
backend is enabled, queue any webhooks that apply to the event.
"""
if settings.WEBHOOKS_ENABLED:
signal_received_timestamp = time.time()
obj_type = ContentType.objects.get_for_model(sender)
# look for any webhooks that match this event
webhooks = Webhook.objects.filter(enabled=True, type_delete=True, obj_type=obj_type)
if webhooks:
enqueue_webhooks(webhooks, sender, instance, 'deleted', signal_received_timestamp)
def bulk_operation_receiver(sender, **kwargs):
"""
Receives bulk_operation_signal signals from registered models. If the webhook
backend is enabled, queue any webhooks that apply to the event.
"""
if settings.WEBHOOKS_ENABLED:
signal_received_timestamp = time.time()
event = kwargs['event']
obj_type = ContentType.objects.get_for_model(sender)
# look for any webhooks that match this event
if event == 'created':
webhooks = Webhook.objects.filter(enabled=True, type_create=True, obj_type=obj_type)
elif event == 'updated':
webhooks = Webhook.objects.filter(enabled=True, type_update=True, obj_type=obj_type)
elif event == 'deleted':
webhooks = Webhook.objects.filter(enabled=True, type_delete=True, obj_type=obj_type)
else:
webhooks = None
if webhooks:
enqueue_webhooks(webhooks, sender, list(kwargs['instances']), event, signal_received_timestamp)
# the bulk operation signal is used to overcome signals not being sent for bulk model changes
bulk_operation_signal = Signal(providing_args=["instances", "event"])
bulk_operation_signal.connect(bulk_operation_receiver)
def register_signals(senders):
"""
Take a list of senders (Models) and register them to the post_save
and post_delete signal receivers.
"""
if settings.WEBHOOKS_ENABLED:
# only register signals if the backend is enabled
# this reduces load by not firing signals if the
# webhook backend feature is disabled
for sender in senders:
post_save.connect(post_save_receiver, sender=sender)
post_delete.connect(post_delete_receiver, sender=sender)
# enqueue the webhooks:
for webhook in webhooks:
webhook_queue.enqueue(
"extras.webhooks_worker.process_webhook",
webhook,
serializer.data,
instance.__class__,
action,
str(datetime.datetime.now())
)