mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge pull request #3027 from SchrodingersGat/locate-plugin-fix
Fix broken calls to offload_task
This commit is contained in:
commit
8ceff063f8
@ -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),
|
||||||
|
@ -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")
|
||||||
|
148
InvenTree/plugin/base/locate/test_locate.py
Normal file
148
InvenTree/plugin/base/locate/test_locate.py
Normal 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'])
|
@ -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!")
|
||||||
|
Loading…
Reference in New Issue
Block a user