Merge pull request #3027 from SchrodingersGat/locate-plugin-fix

Fix broken calls to offload_task
This commit is contained in:
Oliver 2022-05-18 23:42:26 +10:00 committed by GitHub
commit 8ceff063f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 180 additions and 14 deletions

View File

@ -13,6 +13,7 @@ import InvenTree.helpers
from plugin.helpers import MixinImplementationError, MixinNotImplementedError, render_template from plugin.helpers import MixinImplementationError, MixinNotImplementedError, render_template
from plugin.models import PluginConfig, PluginSetting from plugin.models import PluginConfig, PluginSetting
from plugin.registry import registry
from plugin.urls import PLUGIN_BASE from plugin.urls import PLUGIN_BASE
@ -204,7 +205,7 @@ class ScheduleMixin:
Schedule.objects.create( Schedule.objects.create(
name=task_name, name=task_name,
func='plugin.registry.call_function', func=registry.call_plugin_function,
args=f"'{slug}', '{func_name}'", args=f"'{slug}', '{func_name}'",
schedule_type=task['schedule'], schedule_type=task['schedule'],
minutes=task.get('minutes', None), minutes=task.get('minutes', None),

View File

@ -7,7 +7,7 @@ from rest_framework.views import APIView
from InvenTree.tasks import offload_task from InvenTree.tasks import offload_task
from plugin import registry from plugin.registry import registry
from stock.models import StockItem, StockLocation from stock.models import StockItem, StockLocation
@ -40,9 +40,6 @@ class LocatePluginView(APIView):
# StockLocation to identify # StockLocation to identify
location_pk = request.data.get('location', None) 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 = { data = {
"success": "Identification plugin activated", "success": "Identification plugin activated",
"plugin": plugin, "plugin": plugin,
@ -53,27 +50,27 @@ class LocatePluginView(APIView):
try: try:
StockItem.objects.get(pk=item_pk) StockItem.objects.get(pk=item_pk)
offload_task(registry.call_function, plugin, 'locate_stock_item', item_pk) offload_task(registry.call_plugin_function, plugin, 'locate_stock_item', item_pk)
data['item'] = item_pk data['item'] = item_pk
return Response(data) return Response(data)
except StockItem.DoesNotExist: except (ValueError, StockItem.DoesNotExist):
raise NotFound("StockItem matching PK '{item}' not found") raise NotFound(f"StockItem matching PK '{item_pk}' not found")
elif location_pk: elif location_pk:
try: try:
StockLocation.objects.get(pk=location_pk) StockLocation.objects.get(pk=location_pk)
offload_task(registry.call_function, plugin, 'locate_stock_location', location_pk) offload_task(registry.call_plugin_function, plugin, 'locate_stock_location', location_pk)
data['location'] = location_pk data['location'] = location_pk
return Response(data) return Response(data)
except StockLocation.DoesNotExist: except (ValueError, StockLocation.DoesNotExist):
raise NotFound("StockLocation matching PK {'location'} not found") raise NotFound(f"StockLocation matching PK '{location_pk}' not found")
else: else:
raise NotFound() raise ParseError("Must supply either 'item' or 'location' parameter")

View File

@ -0,0 +1,148 @@
"""
Unit tests for the 'locate' plugin mixin class
"""
from django.urls import reverse
from InvenTree.api_tester import InvenTreeAPITestCase
from plugin.registry import registry
from stock.models import StockItem, StockLocation
class LocatePluginTests(InvenTreeAPITestCase):
fixtures = [
'category',
'part',
'location',
'stock',
]
def test_installed(self):
"""Test that a locate plugin is actually installed"""
plugins = registry.with_mixin('locate')
self.assertTrue(len(plugins) > 0)
self.assertTrue('samplelocate' in [p.slug for p in plugins])
def test_locate_fail(self):
"""Test various API failure modes"""
url = reverse('api-locate-plugin')
# Post without a plugin
response = self.post(
url,
{},
expected_code=400
)
self.assertIn("'plugin' field must be supplied", str(response.data))
# Post with a plugin that does not exist, or is invalid
for slug in ['xyz', 'event', 'plugin']:
response = self.post(
url,
{
'plugin': slug,
},
expected_code=400,
)
self.assertIn(f"Plugin '{slug}' is not installed, or does not support the location mixin", str(response.data))
# Post with a valid plugin, but no other data
response = self.post(
url,
{
'plugin': 'samplelocate',
},
expected_code=400
)
self.assertIn("Must supply either 'item' or 'location' parameter", str(response.data))
# Post with valid plugin, invalid item or location
for pk in ['qq', 99999, -42]:
response = self.post(
url,
{
'plugin': 'samplelocate',
'item': pk,
},
expected_code=404
)
self.assertIn(f"StockItem matching PK '{pk}' not found", str(response.data))
response = self.post(
url,
{
'plugin': 'samplelocate',
'location': pk,
},
expected_code=404,
)
self.assertIn(f"StockLocation matching PK '{pk}' not found", str(response.data))
def test_locate_item(self):
"""
Test that the plugin correctly 'locates' a StockItem
As the background worker is not running during unit testing,
the sample 'locate' function will be called 'inline'
"""
url = reverse('api-locate-plugin')
item = StockItem.objects.get(pk=1)
# The sample plugin will set the 'located' metadata tag
item.set_metadata('located', False)
response = self.post(
url,
{
'plugin': 'samplelocate',
'item': 1,
},
expected_code=200
)
self.assertEqual(response.data['item'], 1)
item.refresh_from_db()
# Item metadata should have been altered!
self.assertTrue(item.metadata['located'])
def test_locate_location(self):
"""
Test that the plugin correctly 'locates' a StockLocation
"""
url = reverse('api-locate-plugin')
for location in StockLocation.objects.all():
location.set_metadata('located', False)
response = self.post(
url,
{
'plugin': 'samplelocate',
'location': location.pk,
},
expected_code=200
)
self.assertEqual(response.data['location'], location.pk)
location.refresh_from_db()
# Item metadata should have been altered!
self.assertTrue(location.metadata['located'])

View File

@ -23,7 +23,23 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin):
SLUG = "samplelocate" SLUG = "samplelocate"
TITLE = "Sample plugin for locating items" TITLE = "Sample plugin for locating items"
VERSION = "0.1" VERSION = "0.2"
def locate_stock_item(self, item_pk):
from stock.models import StockItem
logger.info(f"SampleLocatePlugin attempting to locate item ID {item_pk}")
try:
item = StockItem.objects.get(pk=item_pk)
logger.info(f"StockItem {item_pk} located!")
# Tag metadata
item.set_metadata('located', True)
except (ValueError, StockItem.DoesNotExist):
logger.error(f"StockItem ID {item_pk} does not exist!")
def locate_stock_location(self, location_pk): def locate_stock_location(self, location_pk):
@ -34,5 +50,9 @@ class SampleLocatePlugin(LocateMixin, InvenTreePlugin):
try: try:
location = StockLocation.objects.get(pk=location_pk) location = StockLocation.objects.get(pk=location_pk)
logger.info(f"Location exists at '{location.pathstring}'") logger.info(f"Location exists at '{location.pathstring}'")
except StockLocation.DoesNotExist:
# Tag metadata
location.set_metadata('located', True)
except (ValueError, StockLocation.DoesNotExist):
logger.error(f"Location ID {location_pk} does not exist!") logger.error(f"Location ID {location_pk} does not exist!")