2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-11 15:34:15 +00:00

Builtin plugins (#3889)

* Allow loading of "builtin" plugins, even if "plugins" are not explicitly loaded

* Updates for 'admin' buttons:

- Make them work like proper links
- Hidden if 'hide_admin_link' customization option is set
- Check for user staff status

* Cleanup rendering of "plugins" display

* Consolidate InvenTree barcode plugins into single plugin class

* Hide "install plugin" button if plugins are not enabled

* Add info message is external plugins are not enabled

* Fixes for loading plugins

- Always load 'builtin' plugins
- Refactor calls to "is_active" at various points in codebase

* Various tweaks

- Improve builtin plugin descriptions
- Spelling fixes

* Adjust plugin detail for builtin plugins

* Simplify barcode plugin class

* Simplify template rendering

* Bug fix for inventree barcode plugin

* Revert "Simplify template rendering"

This reverts commit 3a6755a659.

* Re-re-improve template rendering

- Required as the template has been refactored for both "active" and "inactive" plugins

* Fixing unit tests for barcode plugin

* Ensure that barcode scan actions do not take a "long time":

- Add a default timeout of 0.1s to any POST or GET request in the testing framework
- Can be overridden by calling method if desired

* Display plugin "builtin" status in admin panel

* Fix unit tests for plugin API

* Further unit testing fixes

* Version number tweaks

* Further tweaks for unit testing

* Allow longer timeout for report printing via API

* Increase default timeout for API tests

- Sometimes CPU spike can cause the test to fail :|

* label printing can take a bit longer

* Remove timeout requirement from API tester

- Too variable to be reliable for CI
This commit is contained in:
Oliver
2022-11-05 01:05:54 +11:00
committed by GitHub
parent fe1b8cbfce
commit b50a6826ef
28 changed files with 349 additions and 267 deletions

View File

@ -1,26 +0,0 @@
"""Sample implementation for ActionMixin."""
from plugin import InvenTreePlugin
from plugin.mixins import ActionMixin
class SimpleActionPlugin(ActionMixin, InvenTreePlugin):
"""An EXTREMELY simple action plugin which demonstrates the capability of the ActionMixin class."""
NAME = "SimpleActionPlugin"
ACTION_NAME = "simple"
def perform_action(self, user=None, data=None):
"""Sample method."""
print("Action plugin in action!")
def get_info(self, user, data=None):
"""Sample method."""
return {
"user": user.username,
"hello": "world",
}
def get_result(self, user=None, data=None):
"""Sample method."""
return True

View File

@ -1,36 +0,0 @@
"""Unit tests for action plugins."""
from InvenTree.helpers import InvenTreeTestCase
from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin
class SimpleActionPluginTests(InvenTreeTestCase):
"""Tests for SampleIntegrationPlugin."""
def setUp(self):
"""Setup for tests."""
super().setUp()
self.plugin = SimpleActionPlugin()
def test_name(self):
"""Check plugn names."""
self.assertEqual(self.plugin.plugin_name(), "SimpleActionPlugin")
self.assertEqual(self.plugin.action_name(), "simple")
def test_function(self):
"""Check if functions work."""
# test functions
response = self.client.post('/api/action/', data={'action': "simple", 'data': {'foo': "bar", }})
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
str(response.content, encoding='utf8'),
{
"action": 'simple',
"result": True,
"info": {
"user": self.username,
"hello": "world",
},
}
)

View File

@ -9,6 +9,8 @@ references model objects actually exist in the database.
import json
from django.utils.translation import gettext_lazy as _
from company.models import SupplierPart
from InvenTree.helpers import hash_barcode
from part.models import Part
@ -17,8 +19,14 @@ from plugin.mixins import BarcodeMixin
from stock.models import StockItem, StockLocation
class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
"""Generic base class for handling InvenTree barcodes"""
class InvenTreeInternalBarcodePlugin(BarcodeMixin, InvenTreePlugin):
"""Builtin BarcodePlugin for matching and generating internal barcodes."""
NAME = "InvenTreeBarcode"
TITLE = _("Inventree Barcodes")
DESCRIPTION = _("Provides native support for barcodes")
VERSION = "2.0.0"
AUTHOR = _("InvenTree contributors")
@staticmethod
def get_supported_barcode_models():
@ -58,58 +66,43 @@ class InvenTreeBarcodePlugin(BarcodeMixin, InvenTreePlugin):
return response
class InvenTreeInternalBarcodePlugin(InvenTreeBarcodePlugin):
"""Builtin BarcodePlugin for matching and generating internal barcodes."""
NAME = "InvenTreeInternalBarcode"
def scan(self, barcode_data):
"""Scan a barcode against this plugin.
Here we are looking for a dict object which contains a reference to a particular InvenTree database object
"""
# Create hash from raw barcode data
barcode_hash = hash_barcode(barcode_data)
# Attempt to coerce the barcode data into a dict object
# This is the internal barcode representation that InvenTree uses
barcode_dict = None
if type(barcode_data) is dict:
pass
barcode_dict = barcode_data
elif type(barcode_data) is str:
try:
barcode_data = json.loads(barcode_data)
barcode_dict = json.loads(barcode_data)
except json.JSONDecodeError:
return None
else:
return None
pass
if type(barcode_data) is not dict:
return None
if barcode_dict is not None and type(barcode_dict) is dict:
# Look for various matches. First good match will be returned
for model in self.get_supported_barcode_models():
label = model.barcode_model_type()
# Look for various matches. First good match will be returned
for model in self.get_supported_barcode_models():
label = model.barcode_model_type()
if label in barcode_data:
try:
instance = model.objects.get(pk=barcode_data[label])
return self.format_matched_response(label, model, instance)
except (ValueError, model.DoesNotExist):
pass
class InvenTreeExternalBarcodePlugin(InvenTreeBarcodePlugin):
"""Builtin BarcodePlugin for matching arbitrary external barcodes."""
NAME = "InvenTreeExternalBarcode"
def scan(self, barcode_data):
"""Scan a barcode against this plugin.
Here we are looking for a dict object which contains a reference to a particular InvenTree databse object
"""
if label in barcode_dict:
try:
instance = model.objects.get(pk=barcode_dict[label])
return self.format_matched_response(label, model, instance)
except (ValueError, model.DoesNotExist):
pass
# If no "direct" hits are found, look for assigned third-party barcodes
for model in self.get_supported_barcode_models():
label = model.barcode_model_type()
barcode_hash = hash_barcode(barcode_data)
instance = model.lookup_barcode(barcode_hash)
if instance is not None:

View File

@ -29,7 +29,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
'barcode': barcode_data,
'stockitem': 521
},
expected_code=400
expected_code=400,
)
self.assertIn('error', response.data)
@ -250,7 +250,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
)
self.assertIn('success', response.data)
self.assertEqual(response.data['plugin'], 'InvenTreeExternalBarcode')
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
self.assertEqual(response.data['part']['pk'], 1)
# Attempting to assign the same barcode to a different part should result in an error
@ -347,7 +347,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
response = self.scan({'barcode': 'blbla=10004'}, expected_code=200)
self.assertEqual(response.data['barcode_data'], 'blbla=10004')
self.assertEqual(response.data['plugin'], 'InvenTreeExternalBarcode')
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
# Scan for a StockItem instance
si = stock.models.StockItem.objects.get(pk=1)
@ -402,7 +402,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
self.assertEqual(response.data['stocklocation']['pk'], 5)
self.assertEqual(response.data['stocklocation']['api_url'], '/api/stock/location/5/')
self.assertEqual(response.data['stocklocation']['web_url'], '/stock/location/5/')
self.assertEqual(response.data['plugin'], 'InvenTreeInternalBarcode')
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
# Scan a Part object
response = self.scan(
@ -423,7 +423,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
)
self.assertEqual(response.data['supplierpart']['pk'], 1)
self.assertEqual(response.data['plugin'], 'InvenTreeInternalBarcode')
self.assertEqual(response.data['plugin'], 'InvenTreeBarcode')
self.assertIn('success', response.data)
self.assertIn('barcode_data', response.data)

View File

@ -27,8 +27,10 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
"""Core notification methods for InvenTree."""
NAME = "CoreNotificationsPlugin"
TITLE = _("InvenTree Notifications")
AUTHOR = _('InvenTree contributors')
DESCRIPTION = _('Integrated outgoing notificaton methods')
VERSION = "1.0.0"
SETTINGS = {
'ENABLE_NOTIFICATION_EMAILS': {