2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-04-28 19:46:46 +00:00

Plugin setting fix (#7258)

* Fix CUI URLs

* Fix for plugin setting API

- Fix conflict between "key" for PluginConfig and "key" for setting
- Needs to be "plugin" for plugin lookup, which accesses the "key" field in the PluginConfig model

* Fix for editing setting in PUI

* Add 'r' back in

* Remove debug code

* Update unit tests

* Bump API version

* Another unit test fix
This commit is contained in:
Oliver 2024-05-19 18:00:26 +10:00 committed by GitHub
parent 2431fc6d58
commit e4dedb63f4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 39 additions and 25 deletions

View File

@ -1,11 +1,14 @@
"""InvenTree API version information.""" """InvenTree API version information."""
# InvenTree API version # InvenTree API version
INVENTREE_API_VERSION = 197 INVENTREE_API_VERSION = 198
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about.""" """Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """ INVENTREE_API_TEXT = """
v198 - 2024-05-19 : https://github.com/inventree/InvenTree/pull/7258
- Fixed lookup field conflicts in the plugins API
v197 - 2024-05-14 : https://github.com/inventree/InvenTree/pull/7224 v197 - 2024-05-14 : https://github.com/inventree/InvenTree/pull/7224
- Refactor the plugin API endpoints to use the plugin "key" for lookup, rather than the PK value - Refactor the plugin API endpoints to use the plugin "key" for lookup, rather than the PK value

View File

@ -620,7 +620,7 @@ class PluginSettingsApiTest(PluginMixin, InvenTreeAPITestCase):
# get data # get data
url = reverse( url = reverse(
'api-plugin-setting-detail', kwargs={'key': 'sample', 'setting': 'API_KEY'} 'api-plugin-setting-detail', kwargs={'plugin': 'sample', 'key': 'API_KEY'}
) )
response = self.get(url, expected_code=200) response = self.get(url, expected_code=200)
@ -637,7 +637,7 @@ class PluginSettingsApiTest(PluginMixin, InvenTreeAPITestCase):
# Non-existent plugin # Non-existent plugin
url = reverse( url = reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail',
kwargs={'key': 'doesnotexist', 'setting': 'doesnotmatter'}, kwargs={'plugin': 'doesnotexist', 'key': 'doesnotmatter'},
) )
response = self.get(url, expected_code=404) response = self.get(url, expected_code=404)
self.assertIn("Plugin 'doesnotexist' not installed", str(response.data)) self.assertIn("Plugin 'doesnotexist' not installed", str(response.data))
@ -645,7 +645,7 @@ class PluginSettingsApiTest(PluginMixin, InvenTreeAPITestCase):
# Wrong key # Wrong key
url = reverse( url = reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail',
kwargs={'key': 'sample', 'setting': 'doesnotexist'}, kwargs={'plugin': 'sample', 'key': 'doesnotexist'},
) )
response = self.get(url, expected_code=404) response = self.get(url, expected_code=404)
self.assertIn( self.assertIn(

View File

@ -156,6 +156,7 @@ class PluginDetail(RetrieveUpdateDestroyAPI):
queryset = PluginConfig.objects.all() queryset = PluginConfig.objects.all()
serializer_class = PluginSerializers.PluginConfigSerializer serializer_class = PluginSerializers.PluginConfigSerializer
lookup_field = 'key' lookup_field = 'key'
lookup_url_kwarg = 'plugin'
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
"""Handle DELETE request for a PluginConfig instance. """Handle DELETE request for a PluginConfig instance.
@ -202,6 +203,7 @@ class PluginUninstall(UpdateAPI):
serializer_class = PluginSerializers.PluginUninstallSerializer serializer_class = PluginSerializers.PluginUninstallSerializer
permission_classes = [IsSuperuser] permission_classes = [IsSuperuser]
lookup_field = 'key' lookup_field = 'key'
lookup_url_kwarg = 'plugin'
def perform_update(self, serializer): def perform_update(self, serializer):
"""Uninstall the plugin.""" """Uninstall the plugin."""
@ -222,6 +224,7 @@ class PluginActivate(UpdateAPI):
serializer_class = PluginSerializers.PluginActivateSerializer serializer_class = PluginSerializers.PluginActivateSerializer
permission_classes = [IsSuperuser] permission_classes = [IsSuperuser]
lookup_field = 'key' lookup_field = 'key'
lookup_url_kwarg = 'plugin'
def get_object(self): def get_object(self):
"""Returns the object for the view.""" """Returns the object for the view."""
@ -323,10 +326,10 @@ class PluginAllSettingList(APIView):
@extend_schema( @extend_schema(
responses={200: PluginSerializers.PluginSettingSerializer(many=True)} responses={200: PluginSerializers.PluginSettingSerializer(many=True)}
) )
def get(self, request, key): def get(self, request, plugin):
"""Get all settings for a plugin config.""" """Get all settings for a plugin config."""
# look up the plugin # look up the plugin
plugin = check_plugin(key, None) plugin = check_plugin(plugin, None)
settings = getattr(plugin, 'settings', {}) settings = getattr(plugin, 'settings', {})
@ -355,10 +358,10 @@ class PluginSettingDetail(RetrieveUpdateAPI):
The URL provides the 'slug' of the plugin, and the 'key' of the setting. 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 Both the 'slug' and 'key' must be valid, else a 404 error is raised
""" """
setting_key = self.kwargs['setting'] setting_key = self.kwargs['key']
# Look up plugin # Look up plugin
plugin = check_plugin(self.kwargs.pop('key', None), None) plugin = check_plugin(self.kwargs.get('plugin', None), None)
settings = getattr(plugin, 'settings', {}) settings = getattr(plugin, 'settings', {})
@ -433,13 +436,13 @@ plugin_api_urls = [
), ),
# Lookup for individual plugins (based on 'key', not 'pk') # Lookup for individual plugins (based on 'key', not 'pk')
path( path(
'<str:key>/', '<str:plugin>/',
include([ include([
path( path(
'settings/', 'settings/',
include([ include([
re_path( re_path(
r'^(?P<setting>\w+)/', r'^(?P<key>\w+)/',
PluginSettingDetail.as_view(), PluginSettingDetail.as_view(),
name='api-plugin-setting-detail', name='api-plugin-setting-detail',
), ),

View File

@ -97,7 +97,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
assert plgs is not None assert plgs is not None
self.assertEqual(plgs.active, active) self.assertEqual(plgs.active, active)
url = reverse('api-plugin-detail-activate', kwargs={'key': test_plg.key}) url = reverse('api-plugin-detail-activate', kwargs={'plugin': test_plg.key})
# Should not work - not a superuser # Should not work - not a superuser
response = self.client.post(url, {}, follow=True) response = self.client.post(url, {}, follow=True)
@ -227,7 +227,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
cfg = PluginConfig.objects.filter(key='sample').first() cfg = PluginConfig.objects.filter(key='sample').first()
assert cfg is not None assert cfg is not None
url = reverse('api-plugin-detail-activate', kwargs={'key': cfg.key}) url = reverse('api-plugin-detail-activate', kwargs={'plugin': cfg.key})
self.client.patch(url, {}, expected_code=200) self.client.patch(url, {}, expected_code=200)
# Valid plugin settings endpoints # Valid plugin settings endpoints
@ -236,8 +236,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
for key in valid_settings: for key in valid_settings:
response = self.get( response = self.get(
reverse( reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail', kwargs={'plugin': 'sample', 'key': key}
kwargs={'key': 'sample', 'setting': key},
) )
) )
@ -247,7 +246,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
response = self.get( response = self.get(
reverse( reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail',
kwargs={'key': 'sample', 'setting': 'INVALID_SETTING'}, kwargs={'plugin': 'sample', 'key': 'INVALID_SETTING'},
), ),
expected_code=404, expected_code=404,
) )
@ -256,7 +255,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
response = self.get( response = self.get(
reverse( reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail',
kwargs={'key': 'sample', 'setting': 'PROTECTED_SETTING'}, kwargs={'plugin': 'sample', 'key': 'PROTECTED_SETTING'},
), ),
expected_code=200, expected_code=200,
) )
@ -267,7 +266,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
response = self.patch( response = self.patch(
reverse( reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail',
kwargs={'key': 'sample', 'setting': 'NUMERICAL_SETTING'}, kwargs={'plugin': 'sample', 'key': 'NUMERICAL_SETTING'},
), ),
{'value': 456}, {'value': 456},
expected_code=200, expected_code=200,
@ -279,7 +278,7 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase):
response = self.get( response = self.get(
reverse( reverse(
'api-plugin-setting-detail', 'api-plugin-setting-detail',
kwargs={'key': 'sample', 'setting': 'NUMERICAL_SETTING'}, kwargs={'plugin': 'sample', 'key': 'NUMERICAL_SETTING'},
), ),
expected_code=200, expected_code=200,
) )

View File

@ -19,7 +19,7 @@ $('table').find('.boolean-setting').change(function() {
if (notification) { if (notification) {
url = `/api/settings/notification/${pk}/`; url = `/api/settings/notification/${pk}/`;
} else if (plugin) { } else if (plugin) {
url = `/api/plugins/settings/${plugin}/${setting}/`; url = `/api/plugins/${plugin}/settings/${setting}/`;
} else if (user) { } else if (user) {
url = `/api/settings/user/${setting}/`; url = `/api/settings/user/${setting}/`;
} }

View File

@ -44,7 +44,7 @@ function editSetting(key, options={}) {
var url = ''; var url = '';
if (plugin) { if (plugin) {
url = `/api/plugins/settings/${plugin}/${key}/`; url = `/api/plugins/${plugin}/settings/${key}/`;
} else if (notification) { } else if (notification) {
url = `/api/settings/notification/${pk}/`; url = `/api/settings/notification/${pk}/`;
} else if (global) { } else if (global) {

View File

@ -47,6 +47,19 @@ export function SettingList({
const [setting, setSetting] = useState<Setting | undefined>(undefined); const [setting, setSetting] = useState<Setting | undefined>(undefined);
// Determine the field type of the setting
const fieldType = useMemo(() => {
if (setting?.type != undefined) {
return setting.type;
}
if (setting?.choices != undefined && setting.choices.length > 0) {
return 'choice';
}
return 'string';
}, [setting]);
const editSettingModal = useEditApiFormModal({ const editSettingModal = useEditApiFormModal({
url: settingsState.endpoint, url: settingsState.endpoint,
pk: setting?.key, pk: setting?.key,
@ -54,11 +67,7 @@ export function SettingList({
title: t`Edit Setting`, title: t`Edit Setting`,
fields: { fields: {
value: { value: {
value: setting?.value ?? '', field_type: fieldType,
field_type:
setting?.type ?? (setting?.choices?.length ?? 0) > 0
? 'choice'
: 'string',
label: setting?.name, label: setting?.name,
description: setting?.description, description: setting?.description,
api_url: setting?.api_url ?? '', api_url: setting?.api_url ?? '',