Coverage for src/backend/InvenTree/plugin/base/locate/api.py: 47%
50 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""API for location plugins."""
3from rest_framework import serializers
4from rest_framework.exceptions import NotFound, ParseError, ValidationError
5from rest_framework.generics import GenericAPIView
6from rest_framework.response import Response
8import InvenTree.permissions
9from InvenTree.exceptions import log_error
10from InvenTree.tasks import offload_task
11from plugin import PluginMixinEnum, registry
12from plugin.registry import call_plugin_function
13from stock.models import StockItem, StockLocation
16class LocatePluginSerializer(serializers.Serializer):
17 """Serializer for the LocatePluginView API endpoint."""
19 plugin = serializers.CharField(
20 help_text='Plugin to use for location identification'
21 )
22 item = serializers.IntegerField(required=False, help_text='StockItem to identify')
23 location = serializers.IntegerField(
24 required=False, help_text='StockLocation to identify'
25 )
28class LocatePluginView(GenericAPIView):
29 """Endpoint for using a custom plugin to identify or 'locate' a stock item or location."""
31 permission_classes = [InvenTree.permissions.IsAuthenticatedOrReadScope]
32 serializer_class = LocatePluginSerializer
34 def post(self, request, *args, **kwargs):
35 """Identify or 'locate' a stock item or location with a plugin."""
36 # Check inputs and offload the task to the plugin
37 # Which plugin to we wish to use?
38 plugin = request.data.get('plugin', None)
40 if not plugin:
41 raise ParseError("'plugin' field must be supplied")
43 # Check that the plugin exists, and supports the 'locate' mixin
44 plugins = registry.with_mixin(PluginMixinEnum.LOCATE)
46 if plugin not in [p.slug for p in plugins]: 46 ↛ 52line 46 didn't jump to line 52 because the condition on line 46 was always true
47 raise ParseError(
48 f"Plugin '{plugin}' is not installed, or does not support the location mixin"
49 )
51 # StockItem to identify
52 item_pk = request.data.get('item', None)
54 # StockLocation to identify
55 location_pk = request.data.get('location', None)
57 data = {'success': 'Identification plugin activated', 'plugin': plugin}
59 # StockItem takes priority
60 if item_pk:
61 try:
62 StockItem.objects.get(pk=item_pk)
64 offload_task(
65 call_plugin_function,
66 plugin,
67 'locate_stock_item',
68 item_pk,
69 group='plugin',
70 )
72 data['item'] = item_pk
74 return Response(data)
76 except (ValueError, StockItem.DoesNotExist):
77 raise NotFound(f"StockItem matching PK '{item_pk}' not found")
78 except Exception:
79 log_error('locate_stock_item', plugin=plugin.slug)
80 return ValidationError('Error locating stock item')
82 elif location_pk:
83 try:
84 StockLocation.objects.get(pk=location_pk)
86 offload_task(
87 call_plugin_function,
88 plugin,
89 'locate_stock_location',
90 location_pk,
91 group='plugin',
92 )
94 data['location'] = location_pk
96 return Response(data)
98 except (ValueError, StockLocation.DoesNotExist):
99 raise NotFound(f"StockLocation matching PK '{location_pk}' not found")
100 except Exception:
101 log_error('locate_stock_location', plugin=plugin.slug)
102 return ValidationError('Error locating stock location')
103 else:
104 raise ParseError("Must supply either 'item' or 'location' parameter")