Adds sample plugin which responds to triggered events

- Adds some example trigger events for the "Part" model
This commit is contained in:
Oliver 2022-01-08 09:07:27 +11:00
parent a604d85f0f
commit 04d25a60b0
8 changed files with 116 additions and 9 deletions

View File

@ -60,6 +60,8 @@ import common.models
import part.settings as part_settings
from plugin.events import trigger_event
logger = logging.getLogger("inventree")
@ -1980,10 +1982,10 @@ class Part(MPTTModel):
@property
def attachment_count(self):
""" Count the number of attachments for this part.
"""
Count the number of attachments for this part.
If the part is a variant of a template part,
include the number of attachments for the template part.
"""
return self.part_attachments.count()
@ -2181,7 +2183,11 @@ def after_save_part(sender, instance: Part, created, **kwargs):
Function to be executed after a Part is saved
"""
if not created:
trigger_event('part.saved', part_id=instance.pk)
if created:
trigger_event('part.created', part_id=instance.pk)
else:
# Check part stock only if we are *updating* the part (not creating it)
# Run this check in the background

View File

@ -189,10 +189,10 @@ class EventMixin:
which provide pairs of 'event':'function'
Notes:
Events are called by name, and based on the django signal nomenclature,
e.g. 'part.pre_save'
Receiving functions must be prototyped to match the 'event' they receive.
Example:

View File

@ -8,11 +8,14 @@ from __future__ import unicode_literals
import logging
from django.conf import settings
from django.db import transaction
from common.models import InvenTreeSetting
from InvenTree.tasks import offload_task
from plugin.registry import plugin_registry
logger = logging.getLogger('inventree')
@ -38,12 +41,30 @@ def trigger_event(event, *args, **kwargs):
def process_event(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"Processing event '{event}'")
# Determine if there are any plugins which are interested in responding
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS'):
pass
# Run atomically, to ensure that either *all* tasks are registered, or *none*
with transaction.atomic():
for slug, callbacks in plugin_registry.mixins_events.items():
# slug = plugin slug
# callbacks = list of (event, function) tuples
for _event, _func in callbacks:
if _event == event:
logger.info(f"Running task '{_func}' for plugin '{slug}'")
offload_task(
_func,
*args,
**kwargs
)

View File

@ -63,3 +63,15 @@ class InvenTreePlugin():
raise error
return cfg
def is_active(self):
"""
Return True if this plugin is currently active
"""
cfg = self.plugin_config()
if cfg:
return cfg.active
else:
return False

View File

@ -56,8 +56,10 @@ class PluginsRegistry:
# integration specific
self.installed_apps = [] # Holds all added plugin_paths
# mixins
self.mixins_settings = {}
self.mixins_events = {}
# region public plugin functions
def load_plugins(self):
@ -265,6 +267,7 @@ class PluginsRegistry:
logger.info(f'Found {len(plugins)} active plugins')
self.activate_integration_settings(plugins)
self.activate_integration_events(plugins)
self.activate_integration_schedule(plugins)
self.activate_integration_app(plugins, force_reload=force_reload)
@ -275,6 +278,7 @@ class PluginsRegistry:
self.deactivate_integration_app()
self.deactivate_integration_schedule()
self.deactivate_integration_events()
self.deactivate_integration_settings()
def activate_integration_settings(self, plugins):
@ -299,6 +303,41 @@ class PluginsRegistry:
# clear cache
self.mixins_settings = {}
def activate_integration_events(self, plugins):
"""
Activate triggered events mixin for applicable plugins
"""
logger.info('Activating plugin events')
from common.models import InvenTreeSetting
self.mixins_events = {}
event_count = 0
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS'):
for slug, plugin in plugins:
if plugin.mixin_enabled('events'):
config = plugin.plugin_config()
# Only activate events for plugins which are enabled
if config and config.active:
self.mixins_events[slug] = plugin.events
event_count += len(plugin.events)
if event_count > 0:
logger.info(f"Registered callbacks for {event_count} events")
def deactivate_integration_events(self):
"""
Deactivate events for all plugins
"""
self.mixins_events = {}
def activate_integration_schedule(self, plugins):
logger.info('Activating plugin tasks')

View File

@ -0,0 +1,29 @@
"""
Sample plugin which responds to events
"""
from plugin import IntegrationPluginBase
from plugin.mixins import EventMixin
def on_part_saved(*args, **kwargs):
"""
Simple function which responds to a triggered event
"""
part_id = kwargs['part_id']
print(f"func on_part_saved() - part: {part_id}")
class EventPlugin(EventMixin, IntegrationPluginBase):
"""
A sample plugin which provides supports for triggered events
"""
PLUGIN_NAME = "EventPlugin"
PLUGIN_SLUG = "event"
PLUGIN_TITLE = "Triggered Events"
EVENTS = [
('part.saved', 'plugin.samples.integration.event_sample.on_part_saved'),
]

View File

@ -32,7 +32,7 @@ class ScheduledTaskPlugin(ScheduleMixin, IntegrationPluginBase):
'hello': {
'func': 'plugin.samples.integration.scheduled_task.print_hello',
'schedule': 'I',
'minutes': 5,
'minutes': 45,
},
'world': {
'func': 'plugin.samples.integration.scheduled_task.print_hello',

View File

@ -90,7 +90,7 @@
</td>
</tr>
<tr>
<td></td>
<td><span class='fas fa-sitemap'></span></td>
<td>{% trans "Installation path" %}</td>
<td>{{ plugin.package_path }}</td>
</tr>