|
@@ -0,0 +1,142 @@
|
|
|
|
|
+import time
|
|
|
|
|
+from importlib import import_module
|
|
|
|
|
+
|
|
|
|
|
+from django.db.models.signals import post_save, post_delete
|
|
|
|
|
+from django.conf import settings
|
|
|
|
|
+from django.core.cache import caches
|
|
|
|
|
+from django.dispatch import Signal
|
|
|
|
|
+from django.contrib.contenttypes.models import ContentType
|
|
|
|
|
+
|
|
|
|
|
+from utilities.utils import dynamic_import
|
|
|
|
|
+from .models import Webhook
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+#
|
|
|
|
|
+# Webhooks signals regiters and receivers
|
|
|
|
|
+#
|
|
|
|
|
+
|
|
|
|
|
+def get_or_set_webhook_cache():
|
|
|
|
|
+ """
|
|
|
|
|
+ Retrieve the webhook cache. If it is None set it to the current
|
|
|
|
|
+ Webhook queryset
|
|
|
|
|
+ """
|
|
|
|
|
+ cache = caches['default']
|
|
|
|
|
+ webhook_cache = cache.get('webhook_cache', None)
|
|
|
|
|
+
|
|
|
|
|
+ if webhook_cache is None:
|
|
|
|
|
+ webhook_cache = Webhook.objects.all()
|
|
|
|
|
+ cache.set('webhook_cache', webhook_cache)
|
|
|
|
|
+
|
|
|
|
|
+ return webhook_cache
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def enqueue_webhooks(webhooks, model_class, data, event, signal_received_timestamp):
|
|
|
|
|
+ """
|
|
|
|
|
+ Serialize data and enqueue webhooks
|
|
|
|
|
+ """
|
|
|
|
|
+ serializer_context = {
|
|
|
|
|
+ 'request': None,
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ 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)
|
|
|
|
|
+
|
|
|
|
|
+ from django_rq import get_queue
|
|
|
|
|
+ webhook_queue = get_queue('default')
|
|
|
|
|
+
|
|
|
|
|
+ for webhook in webhooks:
|
|
|
|
|
+ webhook_queue.enqueue("extras.webhooks_worker.process_webhook",
|
|
|
|
|
+ webhook,
|
|
|
|
|
+ serialized_data.data,
|
|
|
|
|
+ model_class,
|
|
|
|
|
+ event,
|
|
|
|
|
+ signal_received_timestamp)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+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.WEBHOOK_BACKEND_ENABLED:
|
|
|
|
|
+ signal_received_timestamp = time.time()
|
|
|
|
|
+ webhook_cache = get_or_set_webhook_cache()
|
|
|
|
|
+ # look for any webhooks that match this event
|
|
|
|
|
+ updated = not created
|
|
|
|
|
+ obj_type = ContentType.objects.get_for_model(sender)
|
|
|
|
|
+ webhooks = [
|
|
|
|
|
+ x
|
|
|
|
|
+ for x in webhook_cache
|
|
|
|
|
+ if (
|
|
|
|
|
+ x.enabled and x.type_create == created or x.type_update == updated and
|
|
|
|
|
+ obj_type in x.obj_type.all()
|
|
|
|
|
+ )
|
|
|
|
|
+ ]
|
|
|
|
|
+ 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.WEBHOOK_BACKEND_ENABLED:
|
|
|
|
|
+ signal_received_timestamp = time.time()
|
|
|
|
|
+ webhook_cache = get_or_set_webhook_cache()
|
|
|
|
|
+ obj_type = ContentType.objects.get_for_model(sender)
|
|
|
|
|
+ # look for any webhooks that match this event
|
|
|
|
|
+ webhooks = [x for x in webhook_cache if x.enabled and x.type_delete and obj_type in x.obj_type.all()]
|
|
|
|
|
+ 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.WEBHOOK_BACKEND_ENABLED:
|
|
|
|
|
+ signal_received_timestamp = time.time()
|
|
|
|
|
+ event = kwargs['event']
|
|
|
|
|
+ webhook_cache = get_or_set_webhook_cache()
|
|
|
|
|
+ obj_type = ContentType.objects.get_for_model(sender)
|
|
|
|
|
+ # look for any webhooks that match this event
|
|
|
|
|
+ if event == 'created':
|
|
|
|
|
+ webhooks = [x for x in webhook_cache if x.enabled and x.type_create and obj_type in x.obj_type.all()]
|
|
|
|
|
+ elif event == 'updated':
|
|
|
|
|
+ webhooks = [x for x in webhook_cache if x.enabled and x.type_update and obj_type in x.obj_type.all()]
|
|
|
|
|
+ elif event == 'deleted':
|
|
|
|
|
+ webhooks = [x for x in webhook_cache if x.enabled and x.type_delete and obj_type in x.obj_type.all()]
|
|
|
|
|
+ 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.WEBHOOK_BACKEND_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)
|