diff --git a/InvenTree/common/serializers.py b/InvenTree/common/serializers.py index 10616c251a..4cb6a5dd45 100644 --- a/InvenTree/common/serializers.py +++ b/InvenTree/common/serializers.py @@ -13,6 +13,25 @@ from InvenTree.serializers import (InvenTreeImageSerializerField, InvenTreeModelSerializer) +class SettingsValueField(serializers.Field): + """Custom serializer field for a settings value.""" + + def get_attribute(self, instance): + """Return the object instance, not the attribute value.""" + return instance + + def to_representation(self, instance): + """Return the value of the setting: + + - Protected settings are returned as '***' + """ + return '***' if instance.protected else str(instance.value) + + def to_internal_value(self, data): + """Return the internal value of the setting""" + return str(data) + + class SettingsSerializer(InvenTreeModelSerializer): """Base serializer for a settings object.""" @@ -30,6 +49,8 @@ class SettingsSerializer(InvenTreeModelSerializer): api_url = serializers.CharField(read_only=True) + value = SettingsValueField() + def get_choices(self, obj): """Returns the choices available for a given item.""" results = [] @@ -45,16 +66,6 @@ class SettingsSerializer(InvenTreeModelSerializer): return results - def get_value(self, obj): - """Make sure protected values are not returned.""" - # never return protected values - if obj.protected: - result = '***' - else: - result = obj.value - - return result - class GlobalSettingsSerializer(SettingsSerializer): """Serializer for the InvenTreeSetting model.""" diff --git a/InvenTree/plugin/samples/integration/sample.py b/InvenTree/plugin/samples/integration/sample.py index 402159455a..7a04fbbcca 100644 --- a/InvenTree/plugin/samples/integration/sample.py +++ b/InvenTree/plugin/samples/integration/sample.py @@ -72,6 +72,12 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi 'description': 'Select a part object from the database', 'model': 'part.part', }, + 'PROTECTED_SETTING': { + 'name': 'Protected Setting', + 'description': 'A protected setting, hidden from the UI', + 'default': 'ABC-123', + 'protected': True, + } } NAVIGATION = [ diff --git a/InvenTree/plugin/test_api.py b/InvenTree/plugin/test_api.py index d181cb487d..c0f88d4658 100644 --- a/InvenTree/plugin/test_api.py +++ b/InvenTree/plugin/test_api.py @@ -193,3 +193,76 @@ class PluginDetailAPITest(PluginMixin, InvenTreeAPITestCase): with self.assertRaises(NotFound) as exc: check_plugin(plugin_slug=None, plugin_pk='123') self.assertEqual(str(exc.exception.detail), "Plugin '123' not installed") + + def test_plugin_settings(self): + """Test plugin settings access via the API""" + + # Ensure we have superuser permissions + self.user.is_superuser = True + self.user.save() + + # Activate the 'sample' plugin via the API + cfg = PluginConfig.objects.filter(key='sample').first() + url = reverse('api-plugin-detail-activate', kwargs={'pk': cfg.pk}) + self.client.patch(url, {}, expected_code=200) + + # Valid plugin settings endpoints + valid_settings = [ + 'SELECT_PART', + 'API_KEY', + 'NUMERICAL_SETTING', + ] + + for key in valid_settings: + response = self.get( + reverse('api-plugin-setting-detail', kwargs={ + 'plugin': 'sample', + 'key': key + })) + + self.assertEqual(response.data['key'], key) + + # Test that an invalid setting key raises a 404 error + response = self.get( + reverse('api-plugin-setting-detail', kwargs={ + 'plugin': 'sample', + 'key': 'INVALID_SETTING' + }), + expected_code=404 + ) + + # Test that a protected setting returns hidden value + response = self.get( + reverse('api-plugin-setting-detail', kwargs={ + 'plugin': 'sample', + 'key': 'PROTECTED_SETTING' + }), + expected_code=200 + ) + + self.assertEqual(response.data['value'], '***') + + # Test that we can update a setting value + response = self.patch( + reverse('api-plugin-setting-detail', kwargs={ + 'plugin': 'sample', + 'key': 'NUMERICAL_SETTING' + }), + { + 'value': 456 + }, + expected_code=200 + ) + + self.assertEqual(response.data['value'], '456') + + # Retrieve the value again + response = self.get( + reverse('api-plugin-setting-detail', kwargs={ + 'plugin': 'sample', + 'key': 'NUMERICAL_SETTING' + }), + expected_code=200 + ) + + self.assertEqual(response.data['value'], '456') diff --git a/InvenTree/templates/InvenTree/settings/setting.html b/InvenTree/templates/InvenTree/settings/setting.html index 13b0ea9c5e..1c3629d2ff 100644 --- a/InvenTree/templates/InvenTree/settings/setting.html +++ b/InvenTree/templates/InvenTree/settings/setting.html @@ -22,7 +22,9 @@ {{ setting.description }}