""" Functions for triggering and responding to server side events """ # -*- coding: utf-8 -*- from __future__ import unicode_literals import logging from django.utils.translation import gettext_lazy as _ from django.conf import settings from django.db import transaction from django.db.models.signals import post_save, post_delete from django.dispatch.dispatcher import receiver import common.notifications from InvenTree.ready import canAppAccessDatabase, isImportingData from InvenTree.tasks import offload_task from plugin.registry import registry logger = logging.getLogger('inventree') def trigger_event(event, *args, **kwargs): """ Trigger an event with optional arguments. This event will be stored in the database, and the worker will respond to it later on. """ if not settings.PLUGINS_ENABLED: # Do nothing if plugins are not enabled return if not canAppAccessDatabase(): logger.debug(f"Ignoring triggered event '{event}' - database not ready") return logger.debug(f"Event triggered: '{event}'") offload_task( 'plugin.events.register_event', event, *args, **kwargs ) def register_event(event, *args, **kwargs): """ Register the event with any interested plugins. Note: This function is processed by the background worker, as it performs multiple database access operations. """ from common.models import InvenTreeSetting logger.debug(f"Registering triggered event: '{event}'") # Determine if there are any plugins which are interested in responding if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS'): with transaction.atomic(): for slug, plugin in registry.plugins.items(): if plugin.mixin_enabled('events'): config = plugin.plugin_config() if config and config.active: logger.debug(f"Registering callback for plugin '{slug}'") # Offload a separate task for each plugin offload_task( 'plugin.events.process_event', slug, event, *args, **kwargs ) def process_event(plugin_slug, event, *args, **kwargs): """ Respond to a triggered event. This function is run by the background worker process. This function may queue multiple functions to be handled by the background worker. """ logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'") plugin = registry.plugins.get(plugin_slug, None) if plugin is None: logger.error(f"Could not find matching plugin for '{plugin_slug}'") return plugin.process_event(event, *args, **kwargs) def allow_table_event(table_name): """ Determine if an automatic event should be fired for a given table. We *do not* want events to be fired for some tables! """ if isImportingData(): # Prevent table events during the data import process return False table_name = table_name.lower().strip() # Ignore any tables which start with these prefixes ignore_prefixes = [ 'account_', 'auth_', 'authtoken_', 'django_', 'error_', 'exchange_', 'otp_', 'plugin_', 'socialaccount_', 'user_', 'users_', ] if any([table_name.startswith(prefix) for prefix in ignore_prefixes]): return False ignore_tables = [ 'common_notificationentry', 'common_webhookendpoint', 'common_webhookmessage', ] if table_name in ignore_tables: return False return True @receiver(post_save) def after_save(sender, instance, created, **kwargs): """ Trigger an event whenever a database entry is saved """ table = sender.objects.model._meta.db_table instance_id = getattr(instance, 'id', None) if instance_id is None: return if not allow_table_event(table): return if created: trigger_event( f'{table}.created', id=instance.id, model=sender.__name__, ) else: trigger_event( f'{table}.saved', id=instance.id, model=sender.__name__, ) @receiver(post_delete) def after_delete(sender, instance, **kwargs): """ Trigger an event whenever a database entry is deleted """ table = sender.objects.model._meta.db_table if not allow_table_event(table): return trigger_event( f'{table}.deleted', model=sender.__name__, ) def print_label(plugin_slug, label_image, label_instance=None, user=None): """ Print label with the provided plugin. This task is nominally handled by the background worker. If the printing fails (throws an exception) then the user is notified. Arguments: plugin_slug: The unique slug (key) of the plugin label_image: A PIL.Image image object to be printed """ logger.info(f"Plugin '{plugin_slug}' is printing a label") plugin = registry.plugins.get(plugin_slug, None) if plugin is None: logger.error(f"Could not find matching plugin for '{plugin_slug}'") return try: plugin.print_label(label_image, width=label_instance.width, height=label_instance.height) except Exception as e: # Plugin threw an error - notify the user who attempted to print ctx = { 'name': _('Label printing failed'), 'message': str(e), } logger.error(f"Label printing failed: Sending notification to user '{user}'") # Throw an error against the plugin instance common.notifications.trigger_notifaction( plugin.plugin_config(), 'label.printing_failed', targets=[user], context=ctx, delivery_methods=[common.notifications.UIMessageNotification] )