From 78905a45c76f3d1f52459c4b410216325a6f68ef Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 3 Oct 2023 19:44:10 +1100 Subject: [PATCH] Plugin missing fix (#5653) * Add 'is_installed' property to PluginConfig model - Allow us to show users which plugins are "outdated" * Cleanup plugin table - Display clearly if a plugin is no longer installed * Fixes for plugin table * Revert change due to failing CI --- InvenTree/plugin/api.py | 12 ++++++ InvenTree/plugin/models.py | 17 ++++++-- InvenTree/plugin/serializers.py | 2 + InvenTree/templates/js/translated/plugin.js | 39 +++++++++---------- .../templates/js/translated/table_filters.js | 4 ++ 5 files changed, 50 insertions(+), 24 deletions(-) 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" %}' + }, }; }