mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-15 19:45:46 +00:00
fix docstrings 7
This commit is contained in:
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""JSON API for the plugin app"""
|
||||||
JSON API for the plugin app
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.urls import include, re_path
|
from django.urls import include, re_path
|
||||||
@ -20,7 +18,7 @@ from plugin.registry import registry
|
|||||||
|
|
||||||
|
|
||||||
class PluginList(generics.ListAPIView):
|
class PluginList(generics.ListAPIView):
|
||||||
""" API endpoint for list of PluginConfig objects
|
"""API endpoint for list of PluginConfig objects
|
||||||
|
|
||||||
- GET: Return a list of all PluginConfig objects
|
- GET: Return a list of all PluginConfig objects
|
||||||
"""
|
"""
|
||||||
@ -79,7 +77,7 @@ class PluginList(generics.ListAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class PluginDetail(generics.RetrieveUpdateDestroyAPIView):
|
class PluginDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
""" API detail endpoint for PluginConfig object
|
"""API detail endpoint for PluginConfig object
|
||||||
|
|
||||||
get:
|
get:
|
||||||
Return a single PluginConfig object
|
Return a single PluginConfig object
|
||||||
@ -96,9 +94,8 @@ class PluginDetail(generics.RetrieveUpdateDestroyAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class PluginInstall(generics.CreateAPIView):
|
class PluginInstall(generics.CreateAPIView):
|
||||||
"""
|
"""Endpoint for installing a new plugin"""
|
||||||
Endpoint for installing a new plugin
|
|
||||||
"""
|
|
||||||
queryset = PluginConfig.objects.none()
|
queryset = PluginConfig.objects.none()
|
||||||
serializer_class = PluginSerializers.PluginConfigInstallSerializer
|
serializer_class = PluginSerializers.PluginConfigInstallSerializer
|
||||||
|
|
||||||
@ -115,8 +112,7 @@ class PluginInstall(generics.CreateAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class PluginSettingList(generics.ListAPIView):
|
class PluginSettingList(generics.ListAPIView):
|
||||||
"""
|
"""List endpoint for all plugin related settings.
|
||||||
List endpoint for all plugin related settings.
|
|
||||||
|
|
||||||
- read only
|
- read only
|
||||||
- only accessible by staff users
|
- only accessible by staff users
|
||||||
@ -140,8 +136,7 @@ class PluginSettingList(generics.ListAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class PluginSettingDetail(generics.RetrieveUpdateAPIView):
|
class PluginSettingDetail(generics.RetrieveUpdateAPIView):
|
||||||
"""
|
"""Detail endpoint for a plugin-specific setting.
|
||||||
Detail endpoint for a plugin-specific setting.
|
|
||||||
|
|
||||||
Note that these cannot be created or deleted via the API
|
Note that these cannot be created or deleted via the API
|
||||||
"""
|
"""
|
||||||
@ -150,13 +145,11 @@ class PluginSettingDetail(generics.RetrieveUpdateAPIView):
|
|||||||
serializer_class = PluginSerializers.PluginSettingSerializer
|
serializer_class = PluginSerializers.PluginSettingSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
"""
|
"""Lookup the plugin setting object, based on the URL.
|
||||||
Lookup the plugin setting object, based on the URL.
|
|
||||||
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
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plugin_slug = self.kwargs['plugin']
|
plugin_slug = self.kwargs['plugin']
|
||||||
key = self.kwargs['key']
|
key = self.kwargs['key']
|
||||||
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Import helper for events"""
|
||||||
Import helper for events
|
|
||||||
"""
|
|
||||||
|
|
||||||
from plugin.base.event.events import (process_event, register_event,
|
from plugin.base.event.events import (process_event, register_event,
|
||||||
trigger_event)
|
trigger_event)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
"""
|
"""Helpers for plugin app"""
|
||||||
Helpers for plugin app
|
|
||||||
"""
|
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -20,9 +19,8 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
# region logging / errors
|
# region logging / errors
|
||||||
class IntegrationPluginError(Exception):
|
class IntegrationPluginError(Exception):
|
||||||
"""
|
"""Error that encapsulates another error and adds the path / reference of the raising plugin"""
|
||||||
Error that encapsulates another error and adds the path / reference of the raising plugin
|
|
||||||
"""
|
|
||||||
def __init__(self, path, message):
|
def __init__(self, path, message):
|
||||||
self.path = path
|
self.path = path
|
||||||
self.message = message
|
self.message = message
|
||||||
@ -32,24 +30,20 @@ class IntegrationPluginError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class MixinImplementationError(ValueError):
|
class MixinImplementationError(ValueError):
|
||||||
"""
|
"""Error if mixin was implemented wrong in plugin
|
||||||
Error if mixin was implemented wrong in plugin
|
|
||||||
Mostly raised if constant is missing
|
Mostly raised if constant is missing
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MixinNotImplementedError(NotImplementedError):
|
class MixinNotImplementedError(NotImplementedError):
|
||||||
"""
|
"""Error if necessary mixin function was not overwritten"""
|
||||||
Error if necessary mixin function was not overwritten
|
|
||||||
"""
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def log_error(error, reference: str = 'general'):
|
def log_error(error, reference: str = 'general'):
|
||||||
"""
|
"""Log an plugin error"""
|
||||||
Log an plugin error
|
|
||||||
"""
|
|
||||||
from plugin import registry
|
from plugin import registry
|
||||||
|
|
||||||
# make sure the registry is set up
|
# make sure the registry is set up
|
||||||
@ -61,9 +55,7 @@ def log_error(error, reference: str = 'general'):
|
|||||||
|
|
||||||
|
|
||||||
def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: str = ''):
|
def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: str = ''):
|
||||||
"""
|
"""Handles an error and casts it as an IntegrationPluginError"""
|
||||||
Handles an error and casts it as an IntegrationPluginError
|
|
||||||
"""
|
|
||||||
package_path = traceback.extract_tb(error.__traceback__)[-1].filename
|
package_path = traceback.extract_tb(error.__traceback__)[-1].filename
|
||||||
install_path = sysconfig.get_paths()["purelib"]
|
install_path = sysconfig.get_paths()["purelib"]
|
||||||
try:
|
try:
|
||||||
@ -99,9 +91,7 @@ def handle_error(error, do_raise: bool = True, do_log: bool = True, log_name: st
|
|||||||
|
|
||||||
# region git-helpers
|
# region git-helpers
|
||||||
def get_git_log(path):
|
def get_git_log(path):
|
||||||
"""
|
"""Get dict with info of the last commit to file named in path"""
|
||||||
Get dict with info of the last commit to file named in path
|
|
||||||
"""
|
|
||||||
from plugin import registry
|
from plugin import registry
|
||||||
|
|
||||||
output = None
|
output = None
|
||||||
@ -122,8 +112,7 @@ def get_git_log(path):
|
|||||||
|
|
||||||
|
|
||||||
def check_git_version():
|
def check_git_version():
|
||||||
"""returns if the current git version supports modern features"""
|
"""Returns if the current git version supports modern features"""
|
||||||
|
|
||||||
# get version string
|
# get version string
|
||||||
try:
|
try:
|
||||||
output = str(subprocess.check_output(['git', '--version'], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
|
output = str(subprocess.check_output(['git', '--version'], cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')
|
||||||
@ -143,13 +132,11 @@ def check_git_version():
|
|||||||
|
|
||||||
|
|
||||||
class GitStatus:
|
class GitStatus:
|
||||||
"""
|
"""Class for resolving git gpg singing state"""
|
||||||
Class for resolving git gpg singing state
|
|
||||||
"""
|
|
||||||
class Definition:
|
class Definition:
|
||||||
"""
|
"""Definition of a git gpg sing state"""
|
||||||
Definition of a git gpg sing state
|
|
||||||
"""
|
|
||||||
key: str = 'N'
|
key: str = 'N'
|
||||||
status: int = 2
|
status: int = 2
|
||||||
msg: str = ''
|
msg: str = ''
|
||||||
@ -172,8 +159,7 @@ class GitStatus:
|
|||||||
|
|
||||||
# region plugin finders
|
# region plugin finders
|
||||||
def get_modules(pkg):
|
def get_modules(pkg):
|
||||||
"""get all modules in a package"""
|
"""Get all modules in a package"""
|
||||||
|
|
||||||
context = {}
|
context = {}
|
||||||
for loader, name, ispkg in pkgutil.walk_packages(pkg.__path__):
|
for loader, name, ispkg in pkgutil.walk_packages(pkg.__path__):
|
||||||
try:
|
try:
|
||||||
@ -195,18 +181,16 @@ def get_modules(pkg):
|
|||||||
|
|
||||||
|
|
||||||
def get_classes(module):
|
def get_classes(module):
|
||||||
"""get all classes in a given module"""
|
"""Get all classes in a given module"""
|
||||||
return inspect.getmembers(module, inspect.isclass)
|
return inspect.getmembers(module, inspect.isclass)
|
||||||
|
|
||||||
|
|
||||||
def get_plugins(pkg, baseclass):
|
def get_plugins(pkg, baseclass):
|
||||||
"""
|
"""Return a list of all modules under a given package.
|
||||||
Return a list of all modules under a given package.
|
|
||||||
|
|
||||||
- Modules must be a subclass of the provided 'baseclass'
|
- Modules must be a subclass of the provided 'baseclass'
|
||||||
- Modules must have a non-empty NAME parameter
|
- Modules must have a non-empty NAME parameter
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plugins = []
|
plugins = []
|
||||||
|
|
||||||
modules = get_modules(pkg)
|
modules = get_modules(pkg)
|
||||||
@ -225,10 +209,7 @@ def get_plugins(pkg, baseclass):
|
|||||||
|
|
||||||
# region templates
|
# region templates
|
||||||
def render_template(plugin, template_file, context=None):
|
def render_template(plugin, template_file, context=None):
|
||||||
"""
|
"""Locate and render a template file, available in the global template context."""
|
||||||
Locate and render a template file, available in the global template context.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tmp = template.loader.get_template(template_file)
|
tmp = template.loader.get_template(template_file)
|
||||||
except template.TemplateDoesNotExist:
|
except template.TemplateDoesNotExist:
|
||||||
@ -247,10 +228,7 @@ def render_template(plugin, template_file, context=None):
|
|||||||
|
|
||||||
|
|
||||||
def render_text(text, context=None):
|
def render_text(text, context=None):
|
||||||
"""
|
"""Locate a raw string with provided context"""
|
||||||
Locate a raw string with provided context
|
|
||||||
"""
|
|
||||||
|
|
||||||
ctx = template.Context(context)
|
ctx = template.Context(context)
|
||||||
|
|
||||||
return template.Template(text).render(ctx)
|
return template.Template(text).render(ctx)
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Plugin model definitions"""
|
||||||
Plugin model definitions
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@ -14,8 +12,7 @@ from plugin import InvenTreePlugin, registry
|
|||||||
|
|
||||||
|
|
||||||
class MetadataMixin(models.Model):
|
class MetadataMixin(models.Model):
|
||||||
"""
|
"""Model mixin class which adds a JSON metadata field to a model,
|
||||||
Model mixin class which adds a JSON metadata field to a model,
|
|
||||||
for use by any (and all) plugins.
|
for use by any (and all) plugins.
|
||||||
|
|
||||||
The intent of this mixin is to provide a metadata field on a model instance,
|
The intent of this mixin is to provide a metadata field on a model instance,
|
||||||
@ -37,8 +34,7 @@ class MetadataMixin(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_metadata(self, key: str, backup_value=None):
|
def get_metadata(self, key: str, backup_value=None):
|
||||||
"""
|
"""Finds metadata for this model instance, using the provided key for lookup
|
||||||
Finds metadata for this model instance, using the provided key for lookup
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: String key for requesting metadata. e.g. if a plugin is accessing the metadata, the plugin slug should be used
|
key: String key for requesting metadata. e.g. if a plugin is accessing the metadata, the plugin slug should be used
|
||||||
@ -46,22 +42,19 @@ class MetadataMixin(models.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
Python dict object containing requested metadata. If no matching metadata is found, returns None
|
Python dict object containing requested metadata. If no matching metadata is found, returns None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.metadata is None:
|
if self.metadata is None:
|
||||||
return backup_value
|
return backup_value
|
||||||
|
|
||||||
return self.metadata.get(key, backup_value)
|
return self.metadata.get(key, backup_value)
|
||||||
|
|
||||||
def set_metadata(self, key: str, data, commit=True):
|
def set_metadata(self, key: str, data, commit=True):
|
||||||
"""
|
"""Save the provided metadata under the provided key.
|
||||||
Save the provided metadata under the provided key.
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
key: String key for saving metadata
|
key: String key for saving metadata
|
||||||
data: Data object to save - must be able to be rendered as a JSON string
|
data: Data object to save - must be able to be rendered as a JSON string
|
||||||
overwrite: If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted
|
overwrite: If true, existing metadata with the provided key will be overwritten. If false, a merge will be attempted
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self.metadata is None:
|
if self.metadata is None:
|
||||||
# Handle a null field value
|
# Handle a null field value
|
||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
@ -73,14 +66,14 @@ class MetadataMixin(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class PluginConfig(models.Model):
|
class PluginConfig(models.Model):
|
||||||
"""
|
"""A PluginConfig object holds settings for plugins.
|
||||||
A PluginConfig object holds settings for plugins.
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
key: slug of the plugin (this must be unique across all installed plugins!)
|
key: slug of the plugin (this must be unique across all installed plugins!)
|
||||||
name: PluginName of the plugin - serves for a manual double check if the right plugin is used
|
name: PluginName of the plugin - serves for a manual double check if the right plugin is used
|
||||||
active: Should the plugin be loaded?
|
active: Should the plugin be loaded?
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("Plugin Configuration")
|
verbose_name = _("Plugin Configuration")
|
||||||
verbose_name_plural = _("Plugin Configurations")
|
verbose_name_plural = _("Plugin Configurations")
|
||||||
@ -123,10 +116,7 @@ class PluginConfig(models.Model):
|
|||||||
# functions
|
# functions
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""
|
"""Override to set original state of the plugin-config instance"""
|
||||||
Override to set original state of the plugin-config instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.__org_active = self.active
|
self.__org_active = self.active
|
||||||
|
|
||||||
@ -145,9 +135,7 @@ class PluginConfig(models.Model):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
def save(self, force_insert=False, force_update=False, *args, **kwargs):
|
||||||
"""
|
"""Extend save method to reload plugins if the 'active' status changes"""
|
||||||
Extend save method to reload plugins if the 'active' status changes
|
|
||||||
"""
|
|
||||||
reload = kwargs.pop('no_reload', False) # check if no_reload flag is set
|
reload = kwargs.pop('no_reload', False) # check if no_reload flag is set
|
||||||
|
|
||||||
ret = super().save(force_insert, force_update, *args, **kwargs)
|
ret = super().save(force_insert, force_update, *args, **kwargs)
|
||||||
@ -163,9 +151,7 @@ class PluginConfig(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class PluginSetting(common.models.BaseInvenTreeSetting):
|
class PluginSetting(common.models.BaseInvenTreeSetting):
|
||||||
"""
|
"""This model represents settings for individual plugins"""
|
||||||
This model represents settings for individual plugins
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [
|
unique_together = [
|
||||||
@ -182,8 +168,7 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_setting_definition(cls, key, **kwargs):
|
def get_setting_definition(cls, key, **kwargs):
|
||||||
"""
|
"""In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS',
|
||||||
In the BaseInvenTreeSetting class, we have a class attribute named 'SETTINGS',
|
|
||||||
which is a dict object that fully defines all the setting parameters.
|
which is a dict object that fully defines all the setting parameters.
|
||||||
|
|
||||||
Here, unlike the BaseInvenTreeSetting, we do not know the definitions of all settings
|
Here, unlike the BaseInvenTreeSetting, we do not know the definitions of all settings
|
||||||
@ -209,20 +194,14 @@ class PluginSetting(common.models.BaseInvenTreeSetting):
|
|||||||
return super().get_setting_definition(key, **kwargs)
|
return super().get_setting_definition(key, **kwargs)
|
||||||
|
|
||||||
def get_kwargs(self):
|
def get_kwargs(self):
|
||||||
"""
|
"""Explicit kwargs required to uniquely identify a particular setting object, in addition to the 'key' parameter"""
|
||||||
Explicit kwargs required to uniquely identify a particular setting object,
|
|
||||||
in addition to the 'key' parameter
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'plugin': self.plugin,
|
'plugin': self.plugin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class NotificationUserSetting(common.models.BaseInvenTreeSetting):
|
class NotificationUserSetting(common.models.BaseInvenTreeSetting):
|
||||||
"""
|
"""This model represents notification settings for a user"""
|
||||||
This model represents notification settings for a user
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = [
|
unique_together = [
|
||||||
@ -238,11 +217,7 @@ class NotificationUserSetting(common.models.BaseInvenTreeSetting):
|
|||||||
return super().get_setting_definition(key, **kwargs)
|
return super().get_setting_definition(key, **kwargs)
|
||||||
|
|
||||||
def get_kwargs(self):
|
def get_kwargs(self):
|
||||||
"""
|
"""Explicit kwargs required to uniquely identify a particular setting object, in addition to the 'key' parameter"""
|
||||||
Explicit kwargs required to uniquely identify a particular setting object,
|
|
||||||
in addition to the 'key' parameter
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'method': self.method,
|
'method': self.method,
|
||||||
'user': self.user,
|
'user': self.user,
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
"""Base Class for InvenTree plugins"""
|
||||||
"""
|
|
||||||
Base Class for InvenTree plugins
|
|
||||||
"""
|
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@ -55,24 +53,18 @@ class MetaBase:
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def plugin_name(self):
|
def plugin_name(self):
|
||||||
"""
|
"""Name of plugin"""
|
||||||
Name of plugin
|
|
||||||
"""
|
|
||||||
return self.get_meta_value('NAME', 'PLUGIN_NAME')
|
return self.get_meta_value('NAME', 'PLUGIN_NAME')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""
|
"""Name of plugin"""
|
||||||
Name of plugin
|
|
||||||
"""
|
|
||||||
return self.plugin_name()
|
return self.plugin_name()
|
||||||
|
|
||||||
def plugin_slug(self):
|
def plugin_slug(self):
|
||||||
"""
|
"""Slug of plugin
|
||||||
Slug of plugin
|
|
||||||
If not set plugin name slugified
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
If not set plugin name slugified"""
|
||||||
slug = self.get_meta_value('SLUG', 'PLUGIN_SLUG', None)
|
slug = self.get_meta_value('SLUG', 'PLUGIN_SLUG', None)
|
||||||
if not slug:
|
if not slug:
|
||||||
slug = self.plugin_name()
|
slug = self.plugin_name()
|
||||||
@ -81,16 +73,11 @@ class MetaBase:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def slug(self):
|
def slug(self):
|
||||||
"""
|
"""Slug of plugin"""
|
||||||
Slug of plugin
|
|
||||||
"""
|
|
||||||
return self.plugin_slug()
|
return self.plugin_slug()
|
||||||
|
|
||||||
def plugin_title(self):
|
def plugin_title(self):
|
||||||
"""
|
"""Title of plugin"""
|
||||||
Title of plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
title = self.get_meta_value('TITLE', 'PLUGIN_TITLE', None)
|
title = self.get_meta_value('TITLE', 'PLUGIN_TITLE', None)
|
||||||
if title:
|
if title:
|
||||||
return title
|
return title
|
||||||
@ -98,16 +85,11 @@ class MetaBase:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def human_name(self):
|
def human_name(self):
|
||||||
"""
|
"""Human readable name of plugin"""
|
||||||
Human readable name of plugin
|
|
||||||
"""
|
|
||||||
return self.plugin_title()
|
return self.plugin_title()
|
||||||
|
|
||||||
def plugin_config(self):
|
def plugin_config(self):
|
||||||
"""
|
"""Return the PluginConfig object associated with this plugin"""
|
||||||
Return the PluginConfig object associated with this plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import plugin.models
|
import plugin.models
|
||||||
|
|
||||||
@ -121,10 +103,7 @@ class MetaBase:
|
|||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
"""
|
"""Return True if this plugin is currently active"""
|
||||||
Return True if this plugin is currently active
|
|
||||||
"""
|
|
||||||
|
|
||||||
cfg = self.plugin_config()
|
cfg = self.plugin_config()
|
||||||
|
|
||||||
if cfg:
|
if cfg:
|
||||||
@ -134,9 +113,7 @@ class MetaBase:
|
|||||||
|
|
||||||
|
|
||||||
class MixinBase:
|
class MixinBase:
|
||||||
"""
|
"""Base set of mixin functions and mechanisms"""
|
||||||
Base set of mixin functions and mechanisms
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
self._mixinreg = {}
|
self._mixinreg = {}
|
||||||
@ -144,15 +121,11 @@ class MixinBase:
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
def mixin(self, key):
|
def mixin(self, key):
|
||||||
"""
|
"""Check if mixin is registered"""
|
||||||
Check if mixin is registered
|
|
||||||
"""
|
|
||||||
return key in self._mixins
|
return key in self._mixins
|
||||||
|
|
||||||
def mixin_enabled(self, key):
|
def mixin_enabled(self, key):
|
||||||
"""
|
"""Check if mixin is registered, enabled and ready"""
|
||||||
Check if mixin is registered, enabled and ready
|
|
||||||
"""
|
|
||||||
if self.mixin(key):
|
if self.mixin(key):
|
||||||
fnc_name = self._mixins.get(key)
|
fnc_name = self._mixins.get(key)
|
||||||
|
|
||||||
@ -164,18 +137,12 @@ class MixinBase:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def add_mixin(self, key: str, fnc_enabled=True, cls=None):
|
def add_mixin(self, key: str, fnc_enabled=True, cls=None):
|
||||||
"""
|
"""Add a mixin to the plugins registry"""
|
||||||
Add a mixin to the plugins registry
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._mixins[key] = fnc_enabled
|
self._mixins[key] = fnc_enabled
|
||||||
self.setup_mixin(key, cls=cls)
|
self.setup_mixin(key, cls=cls)
|
||||||
|
|
||||||
def setup_mixin(self, key, cls=None):
|
def setup_mixin(self, key, cls=None):
|
||||||
"""
|
"""Define mixin details for the current mixin -> provides meta details for all active mixins"""
|
||||||
Define mixin details for the current mixin -> provides meta details for all active mixins
|
|
||||||
"""
|
|
||||||
|
|
||||||
# get human name
|
# get human name
|
||||||
human_name = getattr(cls.MixinMeta, 'MIXIN_NAME', key) if cls and hasattr(cls, 'MixinMeta') else key
|
human_name = getattr(cls.MixinMeta, 'MIXIN_NAME', key) if cls and hasattr(cls, 'MixinMeta') else key
|
||||||
|
|
||||||
@ -187,10 +154,7 @@ class MixinBase:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def registered_mixins(self, with_base: bool = False):
|
def registered_mixins(self, with_base: bool = False):
|
||||||
"""
|
"""Get all registered mixins for the plugin"""
|
||||||
Get all registered mixins for the plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
mixins = getattr(self, '_mixinreg', None)
|
mixins = getattr(self, '_mixinreg', None)
|
||||||
if mixins:
|
if mixins:
|
||||||
# filter out base
|
# filter out base
|
||||||
@ -202,8 +166,7 @@ class MixinBase:
|
|||||||
|
|
||||||
|
|
||||||
class InvenTreePlugin(MixinBase, MetaBase):
|
class InvenTreePlugin(MixinBase, MetaBase):
|
||||||
"""
|
"""The InvenTreePlugin class is used to integrate with 3rd party software
|
||||||
The InvenTreePlugin class is used to integrate with 3rd party software
|
|
||||||
|
|
||||||
DO NOT USE THIS DIRECTLY, USE plugin.InvenTreePlugin
|
DO NOT USE THIS DIRECTLY, USE plugin.InvenTreePlugin
|
||||||
"""
|
"""
|
||||||
@ -226,9 +189,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
|||||||
# region properties
|
# region properties
|
||||||
@property
|
@property
|
||||||
def description(self):
|
def description(self):
|
||||||
"""
|
"""Description of plugin"""
|
||||||
Description of plugin
|
|
||||||
"""
|
|
||||||
description = getattr(self, 'DESCRIPTION', None)
|
description = getattr(self, 'DESCRIPTION', None)
|
||||||
if not description:
|
if not description:
|
||||||
description = self.plugin_name()
|
description = self.plugin_name()
|
||||||
@ -236,9 +197,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def author(self):
|
def author(self):
|
||||||
"""
|
"""Author of plugin - either from plugin settings or git"""
|
||||||
Author of plugin - either from plugin settings or git
|
|
||||||
"""
|
|
||||||
author = getattr(self, 'AUTHOR', None)
|
author = getattr(self, 'AUTHOR', None)
|
||||||
if not author:
|
if not author:
|
||||||
author = self.package.get('author')
|
author = self.package.get('author')
|
||||||
@ -248,9 +207,7 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def pub_date(self):
|
def pub_date(self):
|
||||||
"""
|
"""Publishing date of plugin - either from plugin settings or git"""
|
||||||
Publishing date of plugin - either from plugin settings or git
|
|
||||||
"""
|
|
||||||
pub_date = getattr(self, 'PUBLISH_DATE', None)
|
pub_date = getattr(self, 'PUBLISH_DATE', None)
|
||||||
if not pub_date:
|
if not pub_date:
|
||||||
pub_date = self.package.get('date')
|
pub_date = self.package.get('date')
|
||||||
@ -262,77 +219,57 @@ class InvenTreePlugin(MixinBase, MetaBase):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def version(self):
|
def version(self):
|
||||||
"""
|
"""Version of plugin"""
|
||||||
Version of plugin
|
|
||||||
"""
|
|
||||||
version = getattr(self, 'VERSION', None)
|
version = getattr(self, 'VERSION', None)
|
||||||
return version
|
return version
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def website(self):
|
def website(self):
|
||||||
"""
|
"""Website of plugin - if set else None"""
|
||||||
Website of plugin - if set else None
|
|
||||||
"""
|
|
||||||
website = getattr(self, 'WEBSITE', None)
|
website = getattr(self, 'WEBSITE', None)
|
||||||
return website
|
return website
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def license(self):
|
def license(self):
|
||||||
"""
|
"""License of plugin"""
|
||||||
License of plugin
|
|
||||||
"""
|
|
||||||
lic = getattr(self, 'LICENSE', None)
|
lic = getattr(self, 'LICENSE', None)
|
||||||
return lic
|
return lic
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _is_package(self):
|
def _is_package(self):
|
||||||
"""
|
"""Is the plugin delivered as a package"""
|
||||||
Is the plugin delivered as a package
|
|
||||||
"""
|
|
||||||
return getattr(self, 'is_package', False)
|
return getattr(self, 'is_package', False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_sample(self):
|
def is_sample(self):
|
||||||
"""
|
"""Is this plugin part of the samples?"""
|
||||||
Is this plugin part of the samples?
|
|
||||||
"""
|
|
||||||
path = str(self.package_path)
|
path = str(self.package_path)
|
||||||
return path.startswith('plugin/samples/')
|
return path.startswith('plugin/samples/')
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def package_path(self):
|
def package_path(self):
|
||||||
"""
|
"""Path to the plugin"""
|
||||||
Path to the plugin
|
|
||||||
"""
|
|
||||||
if self._is_package:
|
if self._is_package:
|
||||||
return self.__module__ # pragma: no cover
|
return self.__module__ # pragma: no cover
|
||||||
return pathlib.Path(self.def_path).relative_to(settings.BASE_DIR)
|
return pathlib.Path(self.def_path).relative_to(settings.BASE_DIR)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def settings_url(self):
|
def settings_url(self):
|
||||||
"""
|
"""URL to the settings panel for this plugin"""
|
||||||
URL to the settings panel for this plugin
|
|
||||||
"""
|
|
||||||
return f'{reverse("settings")}#select-plugin-{self.slug}'
|
return f'{reverse("settings")}#select-plugin-{self.slug}'
|
||||||
|
|
||||||
# region package info
|
# region package info
|
||||||
def _get_package_commit(self):
|
def _get_package_commit(self):
|
||||||
"""
|
"""Get last git commit for the plugin"""
|
||||||
Get last git commit for the plugin
|
|
||||||
"""
|
|
||||||
return get_git_log(self.def_path)
|
return get_git_log(self.def_path)
|
||||||
|
|
||||||
def _get_package_metadata(self):
|
def _get_package_metadata(self):
|
||||||
"""
|
"""Get package metadata for plugin"""
|
||||||
Get package metadata for plugin
|
|
||||||
"""
|
|
||||||
return {} # pragma: no cover # TODO add usage for package metadata
|
return {} # pragma: no cover # TODO add usage for package metadata
|
||||||
|
|
||||||
def define_package(self):
|
def define_package(self):
|
||||||
"""
|
"""Add package info of the plugin into plugins context"""
|
||||||
Add package info of the plugin into plugins context
|
|
||||||
"""
|
|
||||||
package = self._get_package_metadata() if self._is_package else self._get_package_commit()
|
package = self._get_package_metadata() if self._is_package else self._get_package_commit()
|
||||||
|
|
||||||
# process date
|
# process date
|
||||||
|
@ -30,9 +30,7 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
class PluginsRegistry:
|
class PluginsRegistry:
|
||||||
"""
|
"""The PluginsRegistry class"""
|
||||||
The PluginsRegistry class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
# plugin registry
|
# plugin registry
|
||||||
@ -54,10 +52,7 @@ class PluginsRegistry:
|
|||||||
self.mixins_settings = {}
|
self.mixins_settings = {}
|
||||||
|
|
||||||
def get_plugin(self, slug):
|
def get_plugin(self, slug):
|
||||||
"""
|
"""Lookup plugin by slug (unique key)."""
|
||||||
Lookup plugin by slug (unique key).
|
|
||||||
"""
|
|
||||||
|
|
||||||
if slug not in self.plugins:
|
if slug not in self.plugins:
|
||||||
logger.warning(f"Plugin registry has no record of plugin '{slug}'")
|
logger.warning(f"Plugin registry has no record of plugin '{slug}'")
|
||||||
return None
|
return None
|
||||||
@ -65,15 +60,13 @@ class PluginsRegistry:
|
|||||||
return self.plugins[slug]
|
return self.plugins[slug]
|
||||||
|
|
||||||
def call_plugin_function(self, slug, func, *args, **kwargs):
|
def call_plugin_function(self, slug, func, *args, **kwargs):
|
||||||
"""
|
"""Call a member function (named by 'func') of the plugin named by 'slug'.
|
||||||
Call a member function (named by 'func') of the plugin named by 'slug'.
|
|
||||||
|
|
||||||
As this is intended to be run by the background worker,
|
As this is intended to be run by the background worker,
|
||||||
we do not perform any try/except here.
|
we do not perform any try/except here.
|
||||||
|
|
||||||
Instead, any error messages are returned to the worker.
|
Instead, any error messages are returned to the worker.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plugin = self.get_plugin(slug)
|
plugin = self.get_plugin(slug)
|
||||||
|
|
||||||
if not plugin:
|
if not plugin:
|
||||||
@ -86,9 +79,7 @@ class PluginsRegistry:
|
|||||||
# region public functions
|
# region public functions
|
||||||
# region loading / unloading
|
# region loading / unloading
|
||||||
def load_plugins(self):
|
def load_plugins(self):
|
||||||
"""
|
"""Load and activate all IntegrationPlugins"""
|
||||||
Load and activate all IntegrationPlugins
|
|
||||||
"""
|
|
||||||
if not settings.PLUGINS_ENABLED:
|
if not settings.PLUGINS_ENABLED:
|
||||||
# Plugins not enabled, do nothing
|
# Plugins not enabled, do nothing
|
||||||
return # pragma: no cover
|
return # pragma: no cover
|
||||||
@ -143,10 +134,7 @@ class PluginsRegistry:
|
|||||||
logger.info('Finished loading plugins')
|
logger.info('Finished loading plugins')
|
||||||
|
|
||||||
def unload_plugins(self):
|
def unload_plugins(self):
|
||||||
"""
|
"""Unload and deactivate all IntegrationPlugins"""
|
||||||
Unload and deactivate all IntegrationPlugins
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not settings.PLUGINS_ENABLED:
|
if not settings.PLUGINS_ENABLED:
|
||||||
# Plugins not enabled, do nothing
|
# Plugins not enabled, do nothing
|
||||||
return # pragma: no cover
|
return # pragma: no cover
|
||||||
@ -170,10 +158,7 @@ class PluginsRegistry:
|
|||||||
logger.info('Finished unloading plugins')
|
logger.info('Finished unloading plugins')
|
||||||
|
|
||||||
def reload_plugins(self):
|
def reload_plugins(self):
|
||||||
"""
|
"""Safely reload IntegrationPlugins"""
|
||||||
Safely reload IntegrationPlugins
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Do not reload whe currently loading
|
# Do not reload whe currently loading
|
||||||
if self.is_loading:
|
if self.is_loading:
|
||||||
return # pragma: no cover
|
return # pragma: no cover
|
||||||
@ -188,7 +173,6 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
def collect_plugins(self):
|
def collect_plugins(self):
|
||||||
"""Collect plugins from all possible ways of loading"""
|
"""Collect plugins from all possible ways of loading"""
|
||||||
|
|
||||||
if not settings.PLUGINS_ENABLED:
|
if not settings.PLUGINS_ENABLED:
|
||||||
# Plugins not enabled, do nothing
|
# Plugins not enabled, do nothing
|
||||||
return # pragma: no cover
|
return # pragma: no cover
|
||||||
@ -217,10 +201,7 @@ class PluginsRegistry:
|
|||||||
logger.info(", ".join([a.__module__ for a in self.plugin_modules]))
|
logger.info(", ".join([a.__module__ for a in self.plugin_modules]))
|
||||||
|
|
||||||
def install_plugin_file(self):
|
def install_plugin_file(self):
|
||||||
"""
|
"""Make sure all plugins are installed in the current enviroment"""
|
||||||
Make sure all plugins are installed in the current enviroment
|
|
||||||
"""
|
|
||||||
|
|
||||||
if settings.PLUGIN_FILE_CHECKED:
|
if settings.PLUGIN_FILE_CHECKED:
|
||||||
logger.info('Plugin file was already checked')
|
logger.info('Plugin file was already checked')
|
||||||
return True
|
return True
|
||||||
@ -241,9 +222,7 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
# region registry functions
|
# region registry functions
|
||||||
def with_mixin(self, mixin: str, active=None):
|
def with_mixin(self, mixin: str, active=None):
|
||||||
"""
|
"""Returns reference to all plugins that have a specified mixin enabled"""
|
||||||
Returns reference to all plugins that have a specified mixin enabled
|
|
||||||
"""
|
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
for plugin in self.plugins.values():
|
for plugin in self.plugins.values():
|
||||||
@ -264,14 +243,12 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
# region general internal loading /activating / deactivating / deloading
|
# region general internal loading /activating / deactivating / deloading
|
||||||
def _init_plugins(self, disabled=None):
|
def _init_plugins(self, disabled=None):
|
||||||
"""
|
"""Initialise all found plugins
|
||||||
Initialise all found plugins
|
|
||||||
|
|
||||||
:param disabled: loading path of disabled app, defaults to None
|
:param disabled: loading path of disabled app, defaults to None
|
||||||
:type disabled: str, optional
|
:type disabled: str, optional
|
||||||
:raises error: IntegrationPluginError
|
:raises error: IntegrationPluginError
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from plugin.models import PluginConfig
|
from plugin.models import PluginConfig
|
||||||
|
|
||||||
logger.info('Starting plugin initialisation')
|
logger.info('Starting plugin initialisation')
|
||||||
@ -335,8 +312,7 @@ class PluginsRegistry:
|
|||||||
self.plugins_inactive[plug_key] = plugin_db_setting # pragma: no cover
|
self.plugins_inactive[plug_key] = plugin_db_setting # pragma: no cover
|
||||||
|
|
||||||
def _activate_plugins(self, force_reload=False):
|
def _activate_plugins(self, force_reload=False):
|
||||||
"""
|
"""Run activation functions for all plugins
|
||||||
Run activation functions for all plugins
|
|
||||||
|
|
||||||
:param force_reload: force reload base apps, defaults to False
|
:param force_reload: force reload base apps, defaults to False
|
||||||
:type force_reload: bool, optional
|
:type force_reload: bool, optional
|
||||||
@ -351,7 +327,6 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
def _deactivate_plugins(self):
|
def _deactivate_plugins(self):
|
||||||
"""Run deactivation functions for all plugins"""
|
"""Run deactivation functions for all plugins"""
|
||||||
|
|
||||||
self.deactivate_plugin_app()
|
self.deactivate_plugin_app()
|
||||||
self.deactivate_plugin_schedule()
|
self.deactivate_plugin_schedule()
|
||||||
self.deactivate_plugin_settings()
|
self.deactivate_plugin_settings()
|
||||||
@ -425,15 +400,14 @@ class PluginsRegistry:
|
|||||||
logger.warning("activate_integration_schedule failed, database not ready")
|
logger.warning("activate_integration_schedule failed, database not ready")
|
||||||
|
|
||||||
def deactivate_plugin_schedule(self):
|
def deactivate_plugin_schedule(self):
|
||||||
"""
|
"""Deactivate ScheduleMixin
|
||||||
Deactivate ScheduleMixin
|
|
||||||
currently nothing is done
|
currently nothing is done
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def activate_plugin_app(self, plugins, force_reload=False):
|
def activate_plugin_app(self, plugins, force_reload=False):
|
||||||
"""
|
"""Activate AppMixin plugins - add custom apps and reload
|
||||||
Activate AppMixin plugins - add custom apps and reload
|
|
||||||
|
|
||||||
:param plugins: list of IntegrationPlugins that should be installed
|
:param plugins: list of IntegrationPlugins that should be installed
|
||||||
:type plugins: dict
|
:type plugins: dict
|
||||||
@ -471,9 +445,10 @@ class PluginsRegistry:
|
|||||||
self._update_urls()
|
self._update_urls()
|
||||||
|
|
||||||
def _reregister_contrib_apps(self):
|
def _reregister_contrib_apps(self):
|
||||||
"""fix reloading of contrib apps - models and admin
|
"""Fix reloading of contrib apps - models and admin
|
||||||
this is needed if plugins were loaded earlier and then reloaded as models and admins rely on imports
|
|
||||||
those register models and admin in their respective objects (e.g. admin.site for admin)
|
This is needed if plugins were loaded earlier and then reloaded as models and admins rely on imports.
|
||||||
|
Those register models and admin in their respective objects (e.g. admin.site for admin).
|
||||||
"""
|
"""
|
||||||
for plugin_path in self.installed_apps:
|
for plugin_path in self.installed_apps:
|
||||||
try:
|
try:
|
||||||
@ -503,8 +478,9 @@ class PluginsRegistry:
|
|||||||
reload(app_config.module.admin)
|
reload(app_config.module.admin)
|
||||||
|
|
||||||
def _get_plugin_path(self, plugin):
|
def _get_plugin_path(self, plugin):
|
||||||
"""parse plugin path
|
"""Parse plugin path
|
||||||
the input can be eiter:
|
|
||||||
|
The input can be eiter:
|
||||||
- a local file / dir
|
- a local file / dir
|
||||||
- a package
|
- a package
|
||||||
"""
|
"""
|
||||||
@ -518,7 +494,6 @@ class PluginsRegistry:
|
|||||||
|
|
||||||
def deactivate_plugin_app(self):
|
def deactivate_plugin_app(self):
|
||||||
"""Deactivate AppMixin plugins - some magic required"""
|
"""Deactivate AppMixin plugins - some magic required"""
|
||||||
|
|
||||||
# unregister models from admin
|
# unregister models from admin
|
||||||
for plugin_path in self.installed_apps:
|
for plugin_path in self.installed_apps:
|
||||||
models = [] # the modelrefs need to be collected as poping an item in a iter is not welcomed
|
models = [] # the modelrefs need to be collected as poping an item in a iter is not welcomed
|
||||||
@ -601,9 +576,9 @@ class PluginsRegistry:
|
|||||||
self.is_loading = False
|
self.is_loading = False
|
||||||
|
|
||||||
def _try_reload(self, cmd, *args, **kwargs):
|
def _try_reload(self, cmd, *args, **kwargs):
|
||||||
"""
|
"""Wrapper to try reloading the apps
|
||||||
wrapper to try reloading the apps
|
|
||||||
throws an custom error that gets handled by the loading function
|
Throws an custom error that gets handled by the loading function
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cmd(*args, **kwargs)
|
cmd(*args, **kwargs)
|
||||||
@ -617,5 +592,5 @@ registry = PluginsRegistry()
|
|||||||
|
|
||||||
|
|
||||||
def call_function(plugin_name, function_name, *args, **kwargs):
|
def call_function(plugin_name, function_name, *args, **kwargs):
|
||||||
""" Global helper function to call a specific member function of a plugin """
|
"""Global helper function to call a specific member function of a plugin"""
|
||||||
return registry.call_plugin_function(plugin_name, function_name, *args, **kwargs)
|
return registry.call_plugin_function(plugin_name, function_name, *args, **kwargs)
|
||||||
|
Reference in New Issue
Block a user