diff --git a/InvenTree/plugin/api.py b/InvenTree/plugin/api.py
index 74ef2880e1..dcacdb3022 100644
--- a/InvenTree/plugin/api.py
+++ b/InvenTree/plugin/api.py
@@ -83,6 +83,18 @@ class PluginList(ListAPI):
queryset = queryset.filter(pk__in=matches)
+ # Filter queryset by 'installed' flag
+ if 'installed' in params:
+ installed = str2bool(params['installed'])
+
+ matches = []
+
+ for result in queryset:
+ if result.is_installed() == installed:
+ matches.append(result.pk)
+
+ queryset = queryset.filter(pk__in=matches)
+
return queryset
filter_backends = SEARCH_ORDER_FILTER
diff --git a/InvenTree/plugin/models.py b/InvenTree/plugin/models.py
index beeb513465..ce4cf577c6 100644
--- a/InvenTree/plugin/models.py
+++ b/InvenTree/plugin/models.py
@@ -104,9 +104,9 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model):
self.plugin: InvenTreePlugin = plugin
def __getstate__(self):
- """Customize pickeling behaviour."""
+ """Customize pickling behavior."""
state = super().__getstate__()
- state.pop("plugin", None) # plugin cannot be pickelt in some circumstances when used with drf views, remove it (#5408)
+ state.pop("plugin", None) # plugin cannot be pickled in some circumstances when used with drf views, remove it (#5408)
return state
def save(self, force_insert=False, force_update=False, *args, **kwargs):
@@ -120,14 +120,23 @@ class PluginConfig(InvenTree.models.MetadataMixin, models.Model):
self.active = True
if not reload:
- if (self.active is False and self.__org_active is True) or \
- (self.active is True and self.__org_active is False):
+ if self.active != self.__org_active:
if settings.PLUGIN_TESTING:
warnings.warn('A reload was triggered', stacklevel=2)
registry.reload_plugins()
return ret
+ @admin.display(boolean=True, description=_('Installed'))
+ def is_installed(self) -> bool:
+ """Simple check to determine if this plugin is installed.
+
+ A plugin might not be installed if it has been removed from the system,
+ but the PluginConfig associated with it still exists.
+ """
+
+ return self.plugin is not None
+
@admin.display(boolean=True, description=_('Sample plugin'))
def is_sample(self) -> bool:
"""Is this plugin a sample app?"""
diff --git a/InvenTree/plugin/serializers.py b/InvenTree/plugin/serializers.py
index 604db53cb6..604ee5c8f1 100644
--- a/InvenTree/plugin/serializers.py
+++ b/InvenTree/plugin/serializers.py
@@ -56,12 +56,14 @@ class PluginConfigSerializer(serializers.ModelSerializer):
'mixins',
'is_builtin',
'is_sample',
+ 'is_installed',
]
read_only_fields = [
'key',
'is_builtin',
'is_sample',
+ 'is_installed',
]
meta = serializers.DictField(read_only=True)
diff --git a/InvenTree/templates/js/translated/plugin.js b/InvenTree/templates/js/translated/plugin.js
index 378d23da70..29d276ff2d 100644
--- a/InvenTree/templates/js/translated/plugin.js
+++ b/InvenTree/templates/js/translated/plugin.js
@@ -45,34 +45,24 @@ function loadPluginTable(table, options={}) {
return '{% trans "No plugins found" %}';
},
columns: [
- {
- field: 'active',
- title: '',
- sortable: true,
- formatter: function(value, row) {
- if (row.active) {
- return ``;
- } else {
- return ``;
- }
- }
- },
{
field: 'name',
- title: '{% trans "Plugin Description" %}',
+ title: '{% trans "Plugin" %}',
sortable: true,
+ switchable: false,
formatter: function(value, row) {
let html = '';
- if (row.active) {
- html += `${value}`;
- if (row.meta && row.meta.description) {
- html += ` - ${row.meta.description}`;
- }
+ if (!row.is_installed) {
+ html += ``;
+ } else if (row.active) {
+ html += ``;
} else {
- html += `${value}`;
+ html += ``;
}
+ html += ` ${value}`;
+
if (row.is_builtin) {
html += `{% trans "Builtin" %}`;
}
@@ -84,6 +74,12 @@ function loadPluginTable(table, options={}) {
return html;
}
},
+ {
+ field: 'meta.description',
+ title: '{% trans "Description" %}',
+ sortable: false,
+ switchable: true,
+ },
{
field: 'meta.version',
title: '{% trans "Version" %}',
@@ -104,15 +100,18 @@ function loadPluginTable(table, options={}) {
{
field: 'meta.author',
title: '{% trans "Author" %}',
+ sortable: false,
},
{
field: 'actions',
title: '',
+ switchable: false,
+ sortable: false,
formatter: function(value, row) {
let buttons = '';
// Check if custom plugins are enabled for this instance
- if (options.custom && !row.is_builtin) {
+ if (options.custom && !row.is_builtin && row.is_installed) {
if (row.active) {
buttons += makeIconButton('fa-stop-circle icon-red', 'btn-plugin-disable', row.pk, '{% trans "Disable Plugin" %}');
} else {
diff --git a/InvenTree/templates/js/translated/table_filters.js b/InvenTree/templates/js/translated/table_filters.js
index fa89534902..65b4857f60 100644
--- a/InvenTree/templates/js/translated/table_filters.js
+++ b/InvenTree/templates/js/translated/table_filters.js
@@ -471,6 +471,10 @@ function getPluginTableFilters() {
type: 'bool',
title: '{% trans "Sample" %}',
},
+ installed: {
+ type: 'bool',
+ title: '{% trans "Installed" %}'
+ },
};
}