Adds an endpoint for calling the plugin code to "locate" something

This commit is contained in:
Oliver Walters 2022-05-09 23:42:28 +10:00
parent 9b7c26ec9c
commit 57f3efe758
5 changed files with 135 additions and 2 deletions

View File

@ -13,13 +13,18 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import filters from rest_framework import filters
from rest_framework import permissions from rest_framework import permissions
from rest_framework.exceptions import ParseError, NotFound
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from InvenTree.tasks import offload_task
from .views import AjaxView from .views import AjaxView
from .version import inventreeVersion, inventreeApiVersion, inventreeInstanceName from .version import inventreeVersion, inventreeApiVersion, inventreeInstanceName
from .status import is_worker_running from .status import is_worker_running
from stock.models import StockItem, StockLocation
from plugin import registry from plugin import registry
@ -114,7 +119,75 @@ class ActionPluginView(APIView):
return Response(plugin.get_response()) return Response(plugin.get_response())
# If we got to here, no matching action was found # If we got to here, no matching action was found
return Response({ raise NotFound({
'error': _("No matching action found"), 'error': _("No matching action found"),
"action": action, "action": action,
}) })
class LocatePluginView(APIView):
"""
Endpoint for using a custom plugin to identify or 'locate' a stock item or location
"""
permission_classes = [
permissions.IsAuthenticated,
]
def post(self, request, *args, **kwargs):
# Which plugin to we wish to use?
plugin = request.data.get('plugin', None)
if not plugin:
raise ParseError("'plugin' field must be supplied")
# Check that the plugin exists, and supports the 'locate' mixin
plugins = registry.with_mixin('locate')
if plugin not in [p.slug for p in plugins]:
raise ParseError(f"Plugin '{plugin}' is not installed, or does not support the location mixin")
# StockItem to identify
item_pk= request.data.get('item', None)
# StockLocation to identify
location_pk = request.data.get('location', None)
if not item_pk and not location_pk:
raise ParseError("Must supply either 'item' or 'location' parameter")
data = {
"success": "Identification plugin activated",
"plugin": plugin,
}
# StockItem takes priority
if item_pk:
try:
item = StockItem.objects.get(pk=item_pk)
offload_task('plugin.registry.call_function', plugin, 'locate_stock_item', item_pk)
data['item'] = item_pk
return Response(data)
except StockItem.DoesNotExist:
raise NotFound("StockItem matching PK '{item}' not found")
elif location_pk:
try:
location = StockItem.objects.get(pk=location_pk)
offload_task('plugin.registry.call_function', plugin, 'locate_stock_location', location_pk)
data['location'] = location_pk
return Response(data)
except StockLocation.DoesNotExist:
raise NotFound("StockLocation matching PK {'location'} not found")
else:
raise NotFound()

View File

@ -45,7 +45,7 @@ from .views import DynamicJsView
from .views import NotificationsView from .views import NotificationsView
from .api import InfoView, NotFoundView from .api import InfoView, NotFoundView
from .api import ActionPluginView from .api import ActionPluginView, LocatePluginView
from users.api import user_urls from users.api import user_urls
@ -75,6 +75,7 @@ apipatterns += [
# Plugin endpoints # Plugin endpoints
re_path(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'), re_path(r'^action/', ActionPluginView.as_view(), name='api-action-plugin'),
re_path(r'^locate/', LocatePluginView.as_view(), name='api-locate-plugin'),
# Webhook enpoint # Webhook enpoint
path('', include(common_api_urls)), path('', include(common_api_urls)),

View File

@ -473,6 +473,8 @@ class LocateMixin:
Note: A custom implemenation could always change this behaviour Note: A custom implemenation could always change this behaviour
""" """
logger.info(f"LocateMixin: Attempting to locate StockItem pk={item_pk}")
from stock.models import StockItem from stock.models import StockItem
try: try:
@ -482,6 +484,7 @@ class LocateMixin:
self.locate_stock_location(item.location.pk) self.locate_stock_location(item.location.pk)
except StockItem.DoesNotExist: except StockItem.DoesNotExist:
logger.warning("LocateMixin: StockItem pk={item_pk} not found")
pass pass
def locate_stock_location(self, location_pk): def locate_stock_location(self, location_pk):

View File

@ -520,6 +520,14 @@ $("#barcode-scan-into-location").click(function() {
}); });
}); });
{% if plugins_enabled %}
$('#locate-item-button').click(function() {
locateItemOrLocation({
item: {{ item.pk }},
});
});
{% endif %}
function itemAdjust(action) { function itemAdjust(action) {
inventreeGet( inventreeGet(

View File

@ -7,6 +7,7 @@
/* exported /* exported
installPlugin, installPlugin,
locateItemOrLocation
*/ */
function installPlugin() { function installPlugin() {
@ -24,3 +25,50 @@ function installPlugin() {
} }
}); });
} }
function locateItemOrLocation(options={}) {
if (!options.item && !options.location) {
console.error("locateItemOrLocation: Either 'item' or 'location' must be provided!");
return;
}
function performLocate(plugin) {
inventreePut(
'{% url "api-locate-plugin" %}',
{
plugin: plugin,
item: options.item,
location: options.location,
},
{
method: 'POST',
},
);
}
// Request the list of available 'locate' plugins
inventreeGet(
'{% url "api-plugin-list" %}',
{
mixin: 'locate',
},
{
success: function(plugins) {
// No 'locate' plugins are available!
if (plugins.length == 0) {
console.warn("No 'locate' plugins are available");
} else if (plugins.length == 1) {
// Only a single locate plugin is available
performLocate(plugins[0].key);
} else {
// More than 1 location plugin available
// Select from a list
}
}
},
);
}