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:
Matthias Mair 2023-01-27 06:45:14 +01:00 committed by GitHub
parent 83eaa6ef79
commit 1fce1fa695
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 103 additions and 21 deletions

View File

@ -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:

View File

@ -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'),
])),

View File

@ -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',

View File

@ -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")