mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Plugin settings refactor (#4185)
* make plugin urls def cleaner * rename plugin managment endpoints * Add setting to plugin edpoint * docstring * [FR] Add API endpoint to activate plugins Fixes #4182 * fix API syntax * Fix plugin detail lookup for deactivated plugins * fix API metadata lookup * fix for api url change * use slug as error reference * fix get action * add tests for activating plugins * Add tests for check_plugin
This commit is contained in:
parent
83eaa6ef79
commit
1fce1fa695
@ -256,7 +256,7 @@ class InvenTreeMetadata(SimpleMetadata):
|
||||
if isinstance(field, serializers.PrimaryKeyRelatedField):
|
||||
model = field.queryset.model
|
||||
else:
|
||||
logger.debug("Could not extract model for:", field_info['label'], '->', field)
|
||||
logger.debug("Could not extract model for:", field_info.get('label'), '->', field)
|
||||
model = None
|
||||
|
||||
if model:
|
||||
|
@ -17,7 +17,6 @@ from plugin.base.barcodes.api import barcode_api_urls
|
||||
from plugin.base.locate.api import LocatePluginView
|
||||
from plugin.models import PluginConfig, PluginSetting
|
||||
from plugin.plugin import InvenTreePlugin
|
||||
from plugin.registry import registry
|
||||
|
||||
|
||||
class PluginList(ListAPI):
|
||||
@ -130,6 +129,12 @@ class PluginActivate(UpdateAPI):
|
||||
serializer_class = PluginSerializers.PluginConfigEmptySerializer
|
||||
permission_classes = [IsSuperuser, ]
|
||||
|
||||
def get_object(self):
|
||||
"""Returns the object for the view."""
|
||||
if self.request.data.get('pk', None):
|
||||
return self.queryset.get(pk=self.request.data.get('pk'))
|
||||
return super().get_object()
|
||||
|
||||
def perform_update(self, serializer):
|
||||
"""Activate the plugin."""
|
||||
instance = serializer.instance
|
||||
@ -161,11 +166,12 @@ class PluginSettingList(ListAPI):
|
||||
]
|
||||
|
||||
|
||||
def check_plugin(plugin_slug: str) -> InvenTreePlugin:
|
||||
def check_plugin(plugin_slug: str, plugin_pk: int) -> InvenTreePlugin:
|
||||
"""Check that a plugin for the provided slug exsists and get the config.
|
||||
|
||||
Args:
|
||||
plugin_slug (str): Slug for plugin.
|
||||
plugin_pk (int): Primary key for plugin.
|
||||
|
||||
Raises:
|
||||
NotFound: If plugin is not installed
|
||||
@ -175,22 +181,33 @@ def check_plugin(plugin_slug: str) -> InvenTreePlugin:
|
||||
Returns:
|
||||
InvenTreePlugin: The config object for the provided plugin.
|
||||
"""
|
||||
# Check that the 'plugin' specified is valid!
|
||||
if not PluginConfig.objects.filter(key=plugin_slug).exists():
|
||||
raise NotFound(detail=f"Plugin '{plugin_slug}' not installed")
|
||||
# Make sure that a plugin reference is specified
|
||||
if plugin_slug is None and plugin_pk is None:
|
||||
raise NotFound(detail="Plugin not specified")
|
||||
|
||||
# Get the list of settings available for the specified plugin
|
||||
plugin = registry.get_plugin(plugin_slug)
|
||||
# Define filter
|
||||
filter = {}
|
||||
if plugin_slug:
|
||||
filter['key'] = plugin_slug
|
||||
elif plugin_pk:
|
||||
filter['pk'] = plugin_pk
|
||||
ref = plugin_slug or plugin_pk
|
||||
|
||||
if plugin is None:
|
||||
# Check that the 'plugin' specified is valid
|
||||
try:
|
||||
plugin_cgf = PluginConfig.objects.get(**filter)
|
||||
except PluginConfig.DoesNotExist:
|
||||
raise NotFound(detail=f"Plugin '{ref}' not installed")
|
||||
|
||||
if plugin_cgf is None:
|
||||
# This only occurs if the plugin mechanism broke
|
||||
raise NotFound(detail=f"Plugin '{plugin_slug}' not found") # pragma: no cover
|
||||
raise NotFound(detail=f"Plugin '{ref}' not found") # pragma: no cover
|
||||
|
||||
# Check that the plugin is activated
|
||||
if not plugin.is_active():
|
||||
raise NotFound(detail=f"Plugin '{plugin_slug}' is not active")
|
||||
if not plugin_cgf.active:
|
||||
raise NotFound(detail=f"Plugin '{ref}' is not active")
|
||||
|
||||
return plugin
|
||||
return plugin_cgf.plugin
|
||||
|
||||
|
||||
class PluginSettingDetail(RetrieveUpdateAPI):
|
||||
@ -208,16 +225,15 @@ class PluginSettingDetail(RetrieveUpdateAPI):
|
||||
The URL provides the 'slug' of the plugin, and the 'key' of the setting.
|
||||
Both the 'slug' and 'key' must be valid, else a 404 error is raised
|
||||
"""
|
||||
plugin_slug = self.kwargs['plugin']
|
||||
key = self.kwargs['key']
|
||||
|
||||
# Look up plugin
|
||||
plugin = check_plugin(plugin_slug)
|
||||
plugin = check_plugin(plugin_slug=self.kwargs.get('plugin'), plugin_pk=self.kwargs.get('pk'))
|
||||
|
||||
settings = getattr(plugin, 'settings', {})
|
||||
|
||||
if key not in settings:
|
||||
raise NotFound(detail=f"Plugin '{plugin_slug}' has no setting matching '{key}'")
|
||||
raise NotFound(detail=f"Plugin '{plugin.slug}' has no setting matching '{key}'")
|
||||
|
||||
return PluginSetting.get_setting_object(key, plugin=plugin)
|
||||
|
||||
@ -234,12 +250,13 @@ plugin_api_urls = [
|
||||
re_path(r'^plugins/', include([
|
||||
# Plugin settings URLs
|
||||
re_path(r'^settings/', include([
|
||||
re_path(r'^(?P<plugin>\w+)/(?P<key>\w+)/', PluginSettingDetail.as_view(), name='api-plugin-setting-detail'),
|
||||
re_path(r'^(?P<plugin>\w+)/(?P<key>\w+)/', PluginSettingDetail.as_view(), name='api-plugin-setting-detail'), # Used for admin interface
|
||||
re_path(r'^.*$', PluginSettingList.as_view(), name='api-plugin-setting-list'),
|
||||
])),
|
||||
|
||||
# Detail views for a single PluginConfig item
|
||||
re_path(r'^(?P<pk>\d+)/', include([
|
||||
re_path(r'^settings/(?P<key>\w+)/', PluginSettingDetail.as_view(), name='api-plugin-setting-detail-pk'),
|
||||
re_path(r'^activate/', PluginActivate.as_view(), name='api-plugin-detail-activate'),
|
||||
re_path(r'^.*$', PluginDetail.as_view(), name='api-plugin-detail'),
|
||||
])),
|
||||
|
@ -123,13 +123,15 @@ class PluginConfig(models.Model):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.__org_active = self.active
|
||||
|
||||
# append settings from registry
|
||||
# Append settings from registry
|
||||
plugin = registry.plugins_full.get(self.key, None)
|
||||
|
||||
def get_plugin_meta(name):
|
||||
if plugin:
|
||||
return str(getattr(plugin, name, None))
|
||||
return None
|
||||
if not plugin:
|
||||
return None
|
||||
if not self.active:
|
||||
return _('Unvailable')
|
||||
return str(getattr(plugin, name, None))
|
||||
|
||||
self.meta = {
|
||||
key: get_plugin_meta(key) for key in ['slug', 'human_name', 'description', 'author',
|
||||
|
@ -2,7 +2,11 @@
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
from rest_framework.exceptions import NotFound
|
||||
|
||||
from InvenTree.api_tester import InvenTreeAPITestCase, PluginMixin
|
||||
from plugin.api import check_plugin
|
||||
from plugin.models import PluginConfig
|
||||
|
||||
|
||||
class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
@ -86,6 +90,42 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
|
||||
self.assertEqual(data['confirm'][0].title().upper(), 'Installation not confirmed'.upper())
|
||||
|
||||
def test_plugin_activate(self):
|
||||
"""Test the plugin activate."""
|
||||
|
||||
test_plg = self.plugin_confs.first()
|
||||
|
||||
def assert_plugin_active(self, active):
|
||||
self.assertEqual(PluginConfig.objects.all().first().active, active)
|
||||
|
||||
# Should not work - not a superuser
|
||||
response = self.client.post(reverse('api-plugin-activate'), {}, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
# Make user superuser
|
||||
self.user.is_superuser = True
|
||||
self.user.save()
|
||||
|
||||
# Deactivate plugin
|
||||
test_plg.active = False
|
||||
test_plg.save()
|
||||
|
||||
# Activate plugin with detail url
|
||||
assert_plugin_active(self, False)
|
||||
response = self.client.patch(reverse('api-plugin-detail-activate', kwargs={'pk': test_plg.id}), {}, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
assert_plugin_active(self, True)
|
||||
|
||||
# Deactivate plugin
|
||||
test_plg.active = False
|
||||
test_plg.save()
|
||||
|
||||
# Activate plugin
|
||||
assert_plugin_active(self, False)
|
||||
response = self.client.patch(reverse('api-plugin-activate'), {'pk': test_plg.pk}, follow=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
assert_plugin_active(self, True)
|
||||
|
||||
def test_admin_action(self):
|
||||
"""Test the PluginConfig action commands."""
|
||||
url = reverse('admin:plugin_pluginconfig_changelist')
|
||||
@ -135,3 +175,26 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
|
||||
plg_inactive.active = True
|
||||
plg_inactive.save()
|
||||
self.assertEqual(cm.warning.args[0], 'A reload was triggered')
|
||||
|
||||
def test_check_plugin(self):
|
||||
"""Test check_plugin function."""
|
||||
|
||||
# No argument
|
||||
with self.assertRaises(NotFound) as exc:
|
||||
check_plugin(plugin_slug=None, plugin_pk=None)
|
||||
self.assertEqual(str(exc.exception.detail), 'Plugin not specified')
|
||||
|
||||
# Wrong with slug
|
||||
with self.assertRaises(NotFound) as exc:
|
||||
check_plugin(plugin_slug='123abc', plugin_pk=None)
|
||||
self.assertEqual(str(exc.exception.detail), "Plugin '123abc' not installed")
|
||||
|
||||
# Wrong with pk
|
||||
with self.assertRaises(NotFound) as exc:
|
||||
check_plugin(plugin_slug=None, plugin_pk='123')
|
||||
self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed")
|
||||
|
||||
# Not active
|
||||
with self.assertRaises(NotFound) as exc:
|
||||
check_plugin(plugin_slug='inventreebarcode', plugin_pk=None)
|
||||
self.assertEqual(str(exc.exception.detail), "Plugin 'inventreebarcode' is not active")
|
||||
|
Loading…
Reference in New Issue
Block a user