diff --git a/docs/docs/extend/plugins/settings.md b/docs/docs/extend/plugins/settings.md
index 15d1a5969596..27fd3f7fe838 100644
--- a/docs/docs/extend/plugins/settings.md
+++ b/docs/docs/extend/plugins/settings.md
@@ -62,7 +62,7 @@ class PluginWithSettings(SettingsMixin, InvenTreePlugin):
'name': _('Assembled Part'),
'description': _('Settings can point to internal database models'),
'model': 'part.part',
- 'filters': {
+ 'model_filters': {
'active': True,
'assembly': True
}
diff --git a/src/backend/InvenTree/plugin/samples/integration/sample.py b/src/backend/InvenTree/plugin/samples/integration/sample.py
index 9d617f19042c..15161713421f 100644
--- a/src/backend/InvenTree/plugin/samples/integration/sample.py
+++ b/src/backend/InvenTree/plugin/samples/integration/sample.py
@@ -72,14 +72,16 @@ def setup_urls(self):
'default': 'A',
},
'SELECT_COMPANY': {
- 'name': 'Company',
- 'description': 'Select a company object from the database',
+ 'name': 'Supplier',
+ 'description': 'Select a supplier object from the database',
'model': 'company.company',
+ 'model_filters': {'is_supplier': True},
},
'SELECT_PART': {
'name': 'Part',
'description': 'Select a part object from the database',
'model': 'part.part',
+ 'model_filters': {'active': True},
},
'PROTECTED_SETTING': {
'name': 'Protected Setting',
diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx
index 531317f31bca..d8b642f868d2 100644
--- a/src/frontend/src/components/details/Details.tsx
+++ b/src/frontend/src/components/details/Details.tsx
@@ -146,7 +146,7 @@ function NameBadge({
return ;
}
- // Rendering a user's rame for the badge
+ // Rendering a user's name for the badge
function _render_name() {
if (!data || !data.pk) {
return '';
diff --git a/src/frontend/src/components/settings/SettingItem.tsx b/src/frontend/src/components/settings/SettingItem.tsx
index b6bc866ad30e..3dc6aef71560 100644
--- a/src/frontend/src/components/settings/SettingItem.tsx
+++ b/src/frontend/src/components/settings/SettingItem.tsx
@@ -9,11 +9,16 @@ import {
useMantineColorScheme
} from '@mantine/core';
import { IconEdit } from '@tabler/icons-react';
-import { useMemo } from 'react';
+import { useEffect, useMemo, useState } from 'react';
+import { api } from '../../App';
+import { ModelType } from '../../enums/ModelType';
+import { apiUrl } from '../../states/ApiState';
import type { Setting } from '../../states/states';
import { vars } from '../../theme';
import { Boundary } from '../Boundary';
+import { RenderInstance } from '../render/Instance';
+import { ModelInformationDict } from '../render/ModelType';
/**
* Render a single setting value
@@ -44,12 +49,61 @@ function SettingValue({
return value;
}, [setting]);
+ const [modelInstance, setModelInstance] = useState(null);
+
+ // Does this setting map to an internal database model?
+ const modelType: ModelType | null = useMemo(() => {
+ if (setting.model_name) {
+ const model = setting.model_name.split('.')[1];
+ return ModelType[model as keyof typeof ModelType] || null;
+ }
+ return null;
+ }, [setting]);
+
+ useEffect(() => {
+ setModelInstance(null);
+
+ if (modelType && setting.value) {
+ const endpoint = ModelInformationDict[modelType].api_endpoint;
+
+ api
+ .get(apiUrl(endpoint, setting.value))
+ .then((response) => {
+ if (response.data) {
+ setModelInstance(response.data);
+ } else {
+ setModelInstance(null);
+ }
+ })
+ .catch((error) => {
+ setModelInstance(null);
+ });
+ }
+ }, [setting, modelType]);
+
+ // If a full model instance is available, render it
+ if (modelInstance && modelType && setting.value) {
+ return (
+
+
+
+
+ );
+ }
+
switch (setting?.type || 'string') {
case 'boolean':
return (
onToggle(setting, event.currentTarget.checked)}
style={{
@@ -61,12 +115,20 @@ function SettingValue({
return valueText ? (
-
) : (
- onEdit(setting)}>
+ onEdit(setting)}
+ >
);
diff --git a/src/frontend/src/tables/Search.tsx b/src/frontend/src/tables/Search.tsx
index 1d847da40939..3f6acc7dd327 100644
--- a/src/frontend/src/tables/Search.tsx
+++ b/src/frontend/src/tables/Search.tsx
@@ -22,6 +22,7 @@ export function TableSearchInput({
}
placeholder={t`Search`}
onChange={(event) => setValue(event.target.value)}
diff --git a/src/frontend/tests/pui_plugins.spec.ts b/src/frontend/tests/pui_plugins.spec.ts
index 887a21fe6a49..09d52e690437 100644
--- a/src/frontend/tests/pui_plugins.spec.ts
+++ b/src/frontend/tests/pui_plugins.spec.ts
@@ -1,9 +1,54 @@
import test from 'playwright/test';
-import { loadTab, navigate } from './helpers.js';
+import { clearTableFilters, loadTab, navigate } from './helpers.js';
import { doQuickLogin } from './login.js';
import { setPluginState, setSettingState } from './settings.js';
+// Unit test for plugin settings
+test('Plugins - Settings', async ({ page, request }) => {
+ await doQuickLogin(page, 'admin', 'inventree');
+
+ // Ensure that the SampleIntegration plugin is enabled
+ await setPluginState({
+ request,
+ plugin: 'sample',
+ state: true
+ });
+
+ // Navigate and select the plugin
+ await navigate(page, 'settings/admin/plugin/');
+ await clearTableFilters(page);
+ await page.getByLabel('table-search-input').fill('integration');
+
+ await page
+ .getByRole('row', { name: 'SampleIntegrationPlugin' })
+ .getByRole('paragraph')
+ .click();
+ await page.getByRole('button', { name: 'Plugin Information' }).click();
+ await page
+ .getByLabel('Plugin Detail -')
+ .getByRole('button', { name: 'Plugin Settings' })
+ .waitFor();
+
+ // Edit numerical value
+ await page.getByLabel('edit-setting-NUMERICAL_SETTING').click();
+ const originalValue = await page.getByLabel('number-field-value').innerText();
+ await page
+ .getByLabel('number-field-value')
+ .fill(originalValue == '999' ? '1000' : '999');
+ await page.getByRole('button', { name: 'Submit' }).click();
+
+ // Change it back
+ await page.getByLabel('edit-setting-NUMERICAL_SETTING').click();
+ await page.getByLabel('number-field-value').fill(originalValue);
+ await page.getByRole('button', { name: 'Submit' }).click();
+
+ // Select supplier
+ await page.getByLabel('edit-setting-SELECT_COMPANY').click();
+ await page.getByLabel('related-field-value').fill('mouser');
+ await page.getByText('Mouser Electronics').click();
+});
+
test('Plugins - Panels', async ({ page, request }) => {
await doQuickLogin(page, 'admin', 'inventree');