2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

Add "ActionPlugin" interface

- Plugin for running a custom action
This commit is contained in:
Oliver Walters 2020-04-15 00:16:42 +10:00
parent 4d7407ee51
commit a58e2e84f8
6 changed files with 161 additions and 7 deletions

View File

@ -8,6 +8,7 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.http import JsonResponse from django.http import JsonResponse
from rest_framework import permissions
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
@ -20,6 +21,7 @@ from plugins import plugins as inventree_plugins
print("INFO: Loading plugins") print("INFO: Loading plugins")
barcode_plugins = inventree_plugins.load_barcode_plugins() barcode_plugins = inventree_plugins.load_barcode_plugins()
action_plugins = inventree_plugins.load_action_plugins()
class InfoView(AjaxView): class InfoView(AjaxView):
@ -38,7 +40,43 @@ class InfoView(AjaxView):
return JsonResponse(data) return JsonResponse(data)
class BarcodeScanView(APIView): class ActionPluginView(APIView):
"""
Endpoint for running custom action plugins.
"""
permission_classes = [
permissions.IsAuthenticated,
]
def post(self, request, *args, **kwargs):
action = request.data.get('action', None)
data = request.data.get('data', None)
if action is None:
return Response({
'error': _("No action specified")
})
for plugin_class in action_plugins:
if plugin_class.action_name() == action:
plugin = plugin_class(request.user, data=data)
plugin.perform_action()
return Response(plugin.get_response())
# If we got to here, no matching action was found
return Response({
'error': _("No matching action found for"),
"action": action,
})
class BarcodePluginView(APIView):
""" """
Endpoint for handling barcode scan requests. Endpoint for handling barcode scan requests.
@ -50,6 +88,10 @@ class BarcodeScanView(APIView):
""" """
permission_classes = [
permissions.IsAuthenticated,
]
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
response = {} response = {}

View File

@ -36,7 +36,7 @@ from rest_framework.documentation import include_docs_urls
from .views import IndexView, SearchView, DatabaseStatsView from .views import IndexView, SearchView, DatabaseStatsView
from .views import SettingsView, EditUserView, SetPasswordView from .views import SettingsView, EditUserView, SetPasswordView
from .api import InfoView, BarcodeScanView from .api import InfoView, BarcodePluginView, ActionPluginView
from users.urls import user_urls from users.urls import user_urls
@ -54,8 +54,9 @@ apipatterns = [
# User URLs # User URLs
url(r'^user/', include(user_urls)), url(r'^user/', include(user_urls)),
# Barcode scanning endpoint # Plugin endpoints
url(r'^barcode/', BarcodeScanView.as_view(), name='api-barcode-scan'), url(r'^barcode/', BarcodePluginView.as_view(), name='api-barcode-plugin'),
url(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'),
# InvenTree information endpoint # InvenTree information endpoint
url(r'^$', InfoView.as_view(), name='api-inventree-info'), url(r'^$', InfoView.as_view(), name='api-inventree-info'),

View File

View File

@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
import plugins.plugin as plugin
class ActionPlugin(plugin.InvenTreePlugin):
"""
The ActionPlugin class is used to perform custom actions
"""
ACTION_NAME = ""
@classmethod
def action_name(cls):
"""
Return the action name for this plugin.
If the ACTION_NAME parameter is empty,
look at the PLUGIN_NAME instead.
"""
action = cls.ACTION_NAME
if not action:
action = cls.PLUGIN_NAME
return action
def __init__(self, user, data=None):
"""
An action plugin takes a user reference, and an optional dataset (dict)
"""
plugin.InvenTreePlugin.__init__(self)
self.user = user
self.data = data
def perform_action(self):
"""
Override this method to perform the action!
"""
pass
def get_result(self):
"""
Result of the action?
"""
# Re-implement this for cutsom actions
return False
def get_info(self):
"""
Extra info? Can be a string / dict / etc
"""
return None
def get_response(self):
"""
Return a response. Default implementation is a simple response
which can be overridden.
"""
return {
"action": self.action_name(),
"result": self.get_result(),
"info": self.get_info(),
}
class SimpleActionPlugin(ActionPlugin):
"""
An EXTREMELY simple action plugin which demonstrates
the capability of the ActionPlugin class
"""
PLUGIN_NAME = "SimpleActionPlugin"
ACTION_NAME = "simple"
def perform_action(self):
print("Action plugin in action!")
def get_info(self):
return {
"user": self.user.username,
"hello": "world",
}
def get_result(self):
return True

View File

@ -9,7 +9,7 @@ class InvenTreePlugin():
# Override the plugin name for each concrete plugin instance # Override the plugin name for each concrete plugin instance
PLUGIN_NAME = '' PLUGIN_NAME = ''
def get_name(self): def plugin_name(self):
return self.PLUGIN_NAME return self.PLUGIN_NAME
def __init__(self): def __init__(self):

View File

@ -8,6 +8,10 @@ import pkgutil
import plugins.barcode as barcode import plugins.barcode as barcode
from plugins.barcode.barcode import BarcodePlugin from plugins.barcode.barcode import BarcodePlugin
# Action plugins
import plugins.action as action
from plugins.action.action import ActionPlugin
def iter_namespace(pkg): def iter_namespace(pkg):
@ -16,7 +20,7 @@ def iter_namespace(pkg):
def get_modules(pkg): def get_modules(pkg):
# Return all modules in a given package # Return all modules in a given package
return [importlib.import_module(name) for finder, name, ispkg in iter_namespace(barcode)] return [importlib.import_module(name) for finder, name, ispkg in iter_namespace(pkg)]
def get_classes(module): def get_classes(module):
@ -41,7 +45,7 @@ def get_plugins(pkg, baseclass):
# Iterate through each class in the module # Iterate through each class in the module
for item in get_classes(mod): for item in get_classes(mod):
plugin = item[1] plugin = item[1]
if plugin.__class__ is type(baseclass) and plugin.PLUGIN_NAME: if issubclass(plugin, baseclass) and plugin.PLUGIN_NAME:
plugins.append(plugin) plugins.append(plugin)
return plugins return plugins
@ -52,6 +56,8 @@ def load_barcode_plugins():
Return a list of all registered barcode plugins Return a list of all registered barcode plugins
""" """
print("Loading barcode plugins")
plugins = get_plugins(barcode, BarcodePlugin) plugins = get_plugins(barcode, BarcodePlugin)
if len(plugins) > 0: if len(plugins) > 0:
@ -61,3 +67,21 @@ def load_barcode_plugins():
print(" - {bp}".format(bp=bp.PLUGIN_NAME)) print(" - {bp}".format(bp=bp.PLUGIN_NAME))
return plugins return plugins
def load_action_plugins():
"""
Return a list of all registered action plugins
"""
print("Loading action plugins")
plugins = get_plugins(action, ActionPlugin)
if len(plugins) > 0:
print("Discovered {n} action plugins:".format(n=len(plugins)))
for ap in plugins:
print(" - {ap}".format(ap=ap.PLUGIN_NAME))
return plugins