mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 12:05:53 +00:00
refactor app
This commit is contained in:
@ -1,70 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Class for ActionPlugin"""
|
||||
|
||||
import logging
|
||||
|
||||
import plugins.plugin as plugin
|
||||
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
class ActionPlugin(plugin.InvenTreePlugin):
|
||||
"""
|
||||
The ActionPlugin class is used to perform custom actions
|
||||
"""
|
||||
|
||||
ACTION_NAME = ""
|
||||
|
||||
@classmethod
|
||||
def action_name(cls):
|
||||
"""
|
||||
Return the action name for this plugin.
|
||||
If the ACTION_NAME parameter is empty,
|
||||
look at the PLUGIN_NAME instead.
|
||||
"""
|
||||
action = cls.ACTION_NAME
|
||||
|
||||
if not action:
|
||||
action = cls.PLUGIN_NAME
|
||||
|
||||
return action
|
||||
|
||||
def __init__(self, user, data=None):
|
||||
"""
|
||||
An action plugin takes a user reference, and an optional dataset (dict)
|
||||
"""
|
||||
plugin.InvenTreePlugin.__init__(self)
|
||||
|
||||
self.user = user
|
||||
self.data = data
|
||||
|
||||
def perform_action(self):
|
||||
"""
|
||||
Override this method to perform the action!
|
||||
"""
|
||||
|
||||
def get_result(self):
|
||||
"""
|
||||
Result of the action?
|
||||
"""
|
||||
|
||||
# Re-implement this for cutsom actions
|
||||
return False
|
||||
|
||||
def get_info(self):
|
||||
"""
|
||||
Extra info? Can be a string / dict / etc
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_response(self):
|
||||
"""
|
||||
Return a response. Default implementation is a simple response
|
||||
which can be overridden.
|
||||
"""
|
||||
return {
|
||||
"action": self.action_name(),
|
||||
"result": self.get_result(),
|
||||
"info": self.get_info(),
|
||||
}
|
@ -1,362 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""class for IntegrationPluginBase and Mixins for it"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import inspect
|
||||
from datetime import datetime
|
||||
|
||||
from django.conf.urls import url, include
|
||||
from django.conf import settings
|
||||
from django.utils.text import slugify
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import plugins.plugin as plugin
|
||||
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
# region mixins
|
||||
class MixinBase:
|
||||
"""general base for mixins"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._mixinreg = {}
|
||||
self._mixins = {}
|
||||
|
||||
def add_mixin(self, key: str, fnc_enabled=True, cls=None):
|
||||
"""add a mixin to the plugins registry"""
|
||||
self._mixins[key] = fnc_enabled
|
||||
self.setup_mixin(key, cls=cls)
|
||||
|
||||
def setup_mixin(self, key, cls=None):
|
||||
"""define mixin details for the current mixin -> provides meta details for all active mixins"""
|
||||
# get human name
|
||||
human_name = getattr(cls.Meta, 'MIXIN_NAME', key) if cls and hasattr(cls, 'Meta') else key
|
||||
|
||||
# register
|
||||
self._mixinreg[key] = {
|
||||
'key': key,
|
||||
'human_name': human_name,
|
||||
}
|
||||
|
||||
@property
|
||||
def registered_mixins(self, with_base: bool = False):
|
||||
"""get all registered mixins for the plugin"""
|
||||
mixins = getattr(self, '_mixinreg', None)
|
||||
if mixins:
|
||||
# filter out base
|
||||
if not with_base and 'base' in mixins:
|
||||
del mixins['base']
|
||||
# only return dict
|
||||
mixins = [a for a in mixins.values()]
|
||||
return mixins
|
||||
|
||||
|
||||
class SettingsMixin:
|
||||
"""Mixin that enables settings for the plugin"""
|
||||
class Meta:
|
||||
"""meta options for this mixin"""
|
||||
MIXIN_NAME = 'Settings'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_mixin('settings', 'has_settings', __class__)
|
||||
self.settings = self.setup_settings()
|
||||
|
||||
def setup_settings(self):
|
||||
"""
|
||||
setup settings for this plugin
|
||||
"""
|
||||
return getattr(self, 'SETTINGS', None)
|
||||
|
||||
@property
|
||||
def has_settings(self):
|
||||
"""
|
||||
does this plugin use custom settings
|
||||
"""
|
||||
return bool(self.settings)
|
||||
|
||||
@property
|
||||
def settingspatterns(self):
|
||||
"""
|
||||
get patterns for InvenTreeSetting defintion
|
||||
"""
|
||||
if self.has_settings:
|
||||
return {f'PLUGIN_{self.slug.upper()}_{key}': value for key, value in self.settings.items()}
|
||||
return None
|
||||
|
||||
def get_setting(self, key):
|
||||
"""
|
||||
get plugin setting by key
|
||||
"""
|
||||
from common.models import InvenTreeSetting
|
||||
return InvenTreeSetting.get_setting(f'PLUGIN_{self.slug.upper()}_{key}')
|
||||
|
||||
|
||||
class UrlsMixin:
|
||||
"""Mixin that enables urls for the plugin"""
|
||||
class Meta:
|
||||
"""meta options for this mixin"""
|
||||
MIXIN_NAME = 'URLs'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_mixin('urls', 'has_urls', __class__)
|
||||
self.urls = self.setup_urls()
|
||||
|
||||
def setup_urls(self):
|
||||
"""
|
||||
setup url endpoints for this plugin
|
||||
"""
|
||||
return getattr(self, 'URLS', None)
|
||||
|
||||
@property
|
||||
def base_url(self):
|
||||
"""
|
||||
returns base url for this plugin
|
||||
"""
|
||||
return f'{settings.PLUGIN_URL}/{self.slug}/'
|
||||
|
||||
@property
|
||||
def internal_name(self):
|
||||
"""
|
||||
returns the internal url pattern name
|
||||
"""
|
||||
return f'plugin:{self.slug}:'
|
||||
|
||||
@property
|
||||
def urlpatterns(self):
|
||||
"""
|
||||
returns the urlpatterns for this plugin
|
||||
"""
|
||||
if self.has_urls:
|
||||
return url(f'^{self.slug}/', include((self.urls, self.slug)), name=self.slug)
|
||||
return None
|
||||
|
||||
@property
|
||||
def has_urls(self):
|
||||
"""
|
||||
does this plugin use custom urls
|
||||
"""
|
||||
return bool(self.urls)
|
||||
|
||||
|
||||
class NavigationMixin:
|
||||
"""Mixin that enables adding navigation links with the plugin"""
|
||||
NAVIGATION_TAB_NAME = None
|
||||
NAVIGATION_TAB_ICON = "fas fa-question"
|
||||
|
||||
class Meta:
|
||||
"""meta options for this mixin"""
|
||||
MIXIN_NAME = 'Navigation Links'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_mixin('navigation', 'has_naviation', __class__)
|
||||
self.navigation = self.setup_navigation()
|
||||
|
||||
def setup_navigation(self):
|
||||
"""
|
||||
setup navigation links for this plugin
|
||||
"""
|
||||
nav_links = getattr(self, 'NAVIGATION', None)
|
||||
if nav_links:
|
||||
# check if needed values are configured
|
||||
for link in nav_links:
|
||||
if False in [a in link for a in ('link', 'name', )]:
|
||||
raise NotImplementedError('Wrong Link definition', link)
|
||||
return nav_links
|
||||
|
||||
@property
|
||||
def has_naviation(self):
|
||||
"""
|
||||
does this plugin define navigation elements
|
||||
"""
|
||||
return bool(self.navigation)
|
||||
|
||||
@property
|
||||
def navigation_name(self):
|
||||
"""name for navigation tab"""
|
||||
name = getattr(self, 'NAVIGATION_TAB_NAME', None)
|
||||
if not name:
|
||||
name = self.human_name
|
||||
return name
|
||||
|
||||
@property
|
||||
def navigation_icon(self):
|
||||
"""icon for navigation tab"""
|
||||
return getattr(self, 'NAVIGATION_TAB_ICON', "fas fa-question")
|
||||
|
||||
|
||||
class AppMixin:
|
||||
"""Mixin that enables full django app functions for a plugin"""
|
||||
class Meta:
|
||||
"""meta options for this mixin"""
|
||||
MIXIN_NAME = 'App registration'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_mixin('app', 'has_app', __class__)
|
||||
|
||||
@property
|
||||
def has_app(self):
|
||||
"""
|
||||
this plugin is always an app with this plugin
|
||||
"""
|
||||
return True
|
||||
|
||||
# endregion
|
||||
|
||||
|
||||
# region git-helpers
|
||||
def get_git_log(path):
|
||||
"""get dict with info of the last commit to file named in path"""
|
||||
path = path.replace(os.path.dirname(settings.BASE_DIR), '')[1:]
|
||||
command = ['git', 'log', '-n', '1', "--pretty=format:'%H%n%aN%n%aE%n%aI%n%f%n%G?%n%GK'", '--follow', '--', path]
|
||||
try:
|
||||
output = str(subprocess.check_output(command, cwd=os.path.dirname(settings.BASE_DIR)), 'utf-8')[1:-1]
|
||||
if output:
|
||||
output = output.split('\n')
|
||||
else:
|
||||
output = 7 * ['']
|
||||
except subprocess.CalledProcessError:
|
||||
output = 7 * ['']
|
||||
return {'hash': output[0], 'author': output[1], 'mail': output[2], 'date': output[3], 'message': output[4], 'verified': output[5], 'key': output[6]}
|
||||
|
||||
|
||||
class GitStatus:
|
||||
"""class for resolving git gpg singing state"""
|
||||
class Definition:
|
||||
"""definition of a git gpg sing state"""
|
||||
key: str = 'N'
|
||||
status: int = 2
|
||||
msg: str = ''
|
||||
|
||||
def __init__(self, key: str = 'N', status: int = 2, msg: str = '') -> None:
|
||||
self.key = key
|
||||
self.status = status
|
||||
self.msg = msg
|
||||
|
||||
N = Definition(key='N', status=2, msg='no signature',)
|
||||
G = Definition(key='G', status=0, msg='valid signature',)
|
||||
B = Definition(key='B', status=2, msg='bad signature',)
|
||||
U = Definition(key='U', status=1, msg='good signature, unknown validity',)
|
||||
X = Definition(key='X', status=1, msg='good signature, expired',)
|
||||
Y = Definition(key='Y', status=1, msg='good signature, expired key',)
|
||||
R = Definition(key='R', status=2, msg='good signature, revoked key',)
|
||||
E = Definition(key='E', status=1, msg='cannot be checked',)
|
||||
# endregion
|
||||
|
||||
|
||||
class IntegrationPluginBase(MixinBase, plugin.InvenTreePlugin):
|
||||
"""
|
||||
The IntegrationPluginBase class is used to integrate with 3rd party software
|
||||
"""
|
||||
PLUGIN_SLUG = None
|
||||
PLUGIN_TITLE = None
|
||||
|
||||
AUTHOR = None
|
||||
PUBLISH_DATE = None
|
||||
VERSION = None
|
||||
WEBSITE = None
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_mixin('base')
|
||||
self.def_path = inspect.getfile(self.__class__)
|
||||
self.path = os.path.dirname(self.def_path)
|
||||
|
||||
self.set_sign_values()
|
||||
|
||||
# properties
|
||||
@property
|
||||
def slug(self):
|
||||
"""slug for the plugin"""
|
||||
name = getattr(self, 'PLUGIN_SLUG', None)
|
||||
if not name:
|
||||
name = self.plugin_name()
|
||||
return slugify(name)
|
||||
|
||||
@property
|
||||
def human_name(self):
|
||||
"""human readable name for labels etc."""
|
||||
name = getattr(self, 'PLUGIN_TITLE', None)
|
||||
if not name:
|
||||
name = self.plugin_name()
|
||||
return name
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
"""returns author of plugin - either from plugin settings or git"""
|
||||
name = getattr(self, 'AUTHOR', None)
|
||||
if not name:
|
||||
name = self.commit.get('author')
|
||||
if not name:
|
||||
name = _('No author found')
|
||||
return name
|
||||
|
||||
@property
|
||||
def pub_date(self):
|
||||
"""returns publishing date of plugin - either from plugin settings or git"""
|
||||
name = getattr(self, 'PUBLISH_DATE', None)
|
||||
if not name:
|
||||
name = self.commit.get('date')
|
||||
else:
|
||||
name = datetime.fromisoformat(name)
|
||||
if not name:
|
||||
name = _('No date found')
|
||||
return name
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""returns version of plugin"""
|
||||
name = getattr(self, 'VERSION', None)
|
||||
return name
|
||||
|
||||
@property
|
||||
def website(self):
|
||||
"""returns website of plugin"""
|
||||
name = getattr(self, 'WEBSITE', None)
|
||||
return name
|
||||
|
||||
# mixins
|
||||
def mixin(self, key):
|
||||
"""check if mixin is registered"""
|
||||
return key in self._mixins
|
||||
|
||||
def mixin_enabled(self, key):
|
||||
"""check if mixin is enabled and ready"""
|
||||
if self.mixin(key):
|
||||
fnc_name = self._mixins.get(key)
|
||||
return getattr(self, fnc_name, True)
|
||||
return False
|
||||
|
||||
# git
|
||||
def get_plugin_commit(self):
|
||||
"""get last git commit for plugin"""
|
||||
return get_git_log(self.def_path)
|
||||
|
||||
def set_sign_values(self):
|
||||
"""add the last commit of the plugins class file into plugins context"""
|
||||
# fetch git log
|
||||
commit = self.get_plugin_commit()
|
||||
# resolve state
|
||||
sign_state = getattr(GitStatus, commit['verified'], GitStatus.N)
|
||||
|
||||
# set variables
|
||||
self.commit = commit
|
||||
self.sign_state = sign_state
|
||||
|
||||
# process date
|
||||
if self.commit['date']:
|
||||
self.commit['date'] = datetime.fromisoformat(self.commit['date'])
|
||||
|
||||
if sign_state.status == 0:
|
||||
self.sign_color = 'success'
|
||||
elif sign_state.status == 1:
|
||||
self.sign_color = 'warning'
|
||||
else:
|
||||
self.sign_color = 'danger'
|
@ -1,19 +0,0 @@
|
||||
"""
|
||||
load templates for loaded plugins
|
||||
"""
|
||||
from django.conf import settings
|
||||
|
||||
from django.template.loaders.filesystem import Loader as FilesystemLoader
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class PluginTemplateLoader(FilesystemLoader):
|
||||
|
||||
def get_dirs(self):
|
||||
dirname = 'templates'
|
||||
template_dirs = []
|
||||
for plugin in settings.INTEGRATION_PLUGINS:
|
||||
new_path = Path(plugin.path) / dirname
|
||||
if Path(new_path).is_dir():
|
||||
template_dirs.append(new_path)
|
||||
return tuple(template_dirs)
|
@ -1,18 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Base Class for InvenTree plugins"""
|
||||
|
||||
|
||||
class InvenTreePlugin():
|
||||
"""
|
||||
Base class for a plugin
|
||||
"""
|
||||
|
||||
# Override the plugin name for each concrete plugin instance
|
||||
PLUGIN_NAME = ''
|
||||
|
||||
def plugin_name(self):
|
||||
"""get plugin name"""
|
||||
return self.PLUGIN_NAME
|
||||
|
||||
def __init__(self):
|
||||
pass
|
@ -1,101 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""general functions for plugin handeling"""
|
||||
|
||||
import inspect
|
||||
import importlib
|
||||
import pkgutil
|
||||
import logging
|
||||
|
||||
# Action plugins
|
||||
import plugins.samples.action as action
|
||||
from plugins.action import ActionPlugin
|
||||
|
||||
import plugins.samples.integration as integration
|
||||
from plugins.integration import IntegrationPluginBase
|
||||
|
||||
|
||||
logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
def iter_namespace(pkg):
|
||||
"""get all modules in a package"""
|
||||
return pkgutil.iter_modules(pkg.__path__, pkg.__name__ + ".")
|
||||
|
||||
|
||||
def get_modules(pkg):
|
||||
"""get all modules in a package"""
|
||||
return [importlib.import_module(name) for finder, name, ispkg in iter_namespace(pkg)]
|
||||
|
||||
|
||||
def get_classes(module):
|
||||
"""get all classes in a given module"""
|
||||
return inspect.getmembers(module, inspect.isclass)
|
||||
|
||||
|
||||
def get_plugins(pkg, baseclass):
|
||||
"""
|
||||
Return a list of all modules under a given package.
|
||||
|
||||
- Modules must be a subclass of the provided 'baseclass'
|
||||
- Modules must have a non-empty PLUGIN_NAME parameter
|
||||
"""
|
||||
|
||||
plugins = []
|
||||
|
||||
modules = get_modules(pkg)
|
||||
|
||||
# Iterate through each module in the package
|
||||
for mod in modules:
|
||||
# Iterate through each class in the module
|
||||
for item in get_classes(mod):
|
||||
plugin = item[1]
|
||||
if issubclass(plugin, baseclass) and plugin.PLUGIN_NAME:
|
||||
plugins.append(plugin)
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
def load_plugins(name: str, module, cls):
|
||||
"""general function to load a plugin class
|
||||
|
||||
:param name: name of the plugin for logs
|
||||
:type name: str
|
||||
:param module: module from which the plugins should be loaded
|
||||
:return: class of the to-be-loaded plugin
|
||||
"""
|
||||
|
||||
logger.debug("Loading %s plugins", name)
|
||||
|
||||
plugins = get_plugins(module, cls)
|
||||
|
||||
if len(plugins) > 0:
|
||||
logger.info("Discovered %i %s plugins:", len(plugins), name)
|
||||
|
||||
for plugin in plugins:
|
||||
logger.debug(" - %s", plugin.PLUGIN_NAME)
|
||||
|
||||
return plugins
|
||||
|
||||
|
||||
def load_action_plugins():
|
||||
"""
|
||||
Return a list of all registered action plugins
|
||||
"""
|
||||
return load_plugins('action', action, ActionPlugin)
|
||||
|
||||
|
||||
def load_integration_plugins():
|
||||
"""
|
||||
Return a list of all registered integration plugins
|
||||
"""
|
||||
return load_plugins('integration', integration, IntegrationPluginBase)
|
||||
|
||||
|
||||
def load_barcode_plugins():
|
||||
"""
|
||||
Return a list of all registered barcode plugins
|
||||
"""
|
||||
from barcodes import plugins as BarcodePlugins
|
||||
from barcodes.barcode import BarcodePlugin
|
||||
|
||||
return load_plugins('barcode', BarcodePlugins, BarcodePlugin)
|
@ -1,25 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""sample implementation for ActionPlugin"""
|
||||
from plugins.action import ActionPlugin
|
||||
|
||||
|
||||
class SimpleActionPlugin(ActionPlugin):
|
||||
"""
|
||||
An EXTREMELY simple action plugin which demonstrates
|
||||
the capability of the ActionPlugin class
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = "SimpleActionPlugin"
|
||||
ACTION_NAME = "simple"
|
||||
|
||||
def perform_action(self):
|
||||
print("Action plugin in action!")
|
||||
|
||||
def get_info(self):
|
||||
return {
|
||||
"user": self.user.username,
|
||||
"hello": "world",
|
||||
}
|
||||
|
||||
def get_result(self):
|
||||
return True
|
@ -1,40 +0,0 @@
|
||||
""" Unit tests for action plugins """
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from plugins.samples.action.simpleactionplugin import SimpleActionPlugin
|
||||
|
||||
|
||||
class SimpleActionPluginTests(TestCase):
|
||||
""" Tests for SampleIntegrationPlugin """
|
||||
|
||||
def setUp(self):
|
||||
# Create a user for auth
|
||||
user = get_user_model()
|
||||
self.test_user = user.objects.create_user('testuser', 'test@testing.com', 'password')
|
||||
|
||||
self.client.login(username='testuser', password='password')
|
||||
self.plugin = SimpleActionPlugin(user=self.test_user)
|
||||
|
||||
def test_name(self):
|
||||
"""check plugn names """
|
||||
self.assertEqual(self.plugin.plugin_name(), "SimpleActionPlugin")
|
||||
self.assertEqual(self.plugin.action_name(), "simple")
|
||||
|
||||
def test_function(self):
|
||||
"""check if functions work """
|
||||
# test functions
|
||||
response = self.client.post('/api/action/', data={'action': "simple", 'data': {'foo': "bar", }})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
str(response.content, encoding='utf8'),
|
||||
{
|
||||
"action": 'simple',
|
||||
"result": True,
|
||||
"info": {
|
||||
"user": "testuser",
|
||||
"hello": "world",
|
||||
},
|
||||
}
|
||||
)
|
@ -1,18 +0,0 @@
|
||||
"""sample implementation for IntegrationPlugin"""
|
||||
from plugins.integration import IntegrationPluginBase, UrlsMixin
|
||||
|
||||
|
||||
class NoIntegrationPlugin(IntegrationPluginBase):
|
||||
"""
|
||||
An basic integration plugin
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = "NoIntegrationPlugin"
|
||||
|
||||
|
||||
class WrongIntegrationPlugin(UrlsMixin, IntegrationPluginBase):
|
||||
"""
|
||||
An basic integration plugin
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = "WrongIntegrationPlugin"
|
@ -1,47 +0,0 @@
|
||||
"""sample implementations for IntegrationPlugin"""
|
||||
from plugins.integration import AppMixin, SettingsMixin, UrlsMixin, NavigationMixin, IntegrationPluginBase
|
||||
|
||||
from django.http import HttpResponse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf.urls import url, include
|
||||
|
||||
|
||||
class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixin, IntegrationPluginBase):
|
||||
"""
|
||||
An full integration plugin
|
||||
"""
|
||||
|
||||
PLUGIN_NAME = "SampleIntegrationPlugin"
|
||||
PLUGIN_SLUG = "sample"
|
||||
PLUGIN_TITLE = "Sample Plugin"
|
||||
|
||||
NAVIGATION_TAB_NAME = "Sample Nav"
|
||||
NAVIGATION_TAB_ICON = 'fas fa-plus'
|
||||
|
||||
def view_test(self, request):
|
||||
"""very basic view"""
|
||||
return HttpResponse(f'Hi there {request.user.username} this works')
|
||||
|
||||
def setup_urls(self):
|
||||
he_urls = [
|
||||
url(r'^he/', self.view_test, name='he'),
|
||||
url(r'^ha/', self.view_test, name='ha'),
|
||||
]
|
||||
|
||||
return [
|
||||
url(r'^hi/', self.view_test, name='hi'),
|
||||
url(r'^ho/', include(he_urls), name='ho'),
|
||||
]
|
||||
|
||||
SETTINGS = {
|
||||
'PO_FUNCTION_ENABLE': {
|
||||
'name': _('Enable PO'),
|
||||
'description': _('Enable PO functionality in InvenTree interface'),
|
||||
'default': True,
|
||||
'validator': bool,
|
||||
},
|
||||
}
|
||||
|
||||
NAVIGATION = [
|
||||
{'name': 'SampleIntegration', 'link': 'plugin:sample:hi'},
|
||||
]
|
@ -1,21 +0,0 @@
|
||||
""" Unit tests for action plugins """
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
|
||||
class SampleIntegrationPluginTests(TestCase):
|
||||
""" Tests for SampleIntegrationPlugin """
|
||||
|
||||
def setUp(self):
|
||||
# Create a user for auth
|
||||
user = get_user_model()
|
||||
user.objects.create_user('testuser', 'test@testing.com', 'password')
|
||||
|
||||
self.client.login(username='testuser', password='password')
|
||||
|
||||
def test_view(self):
|
||||
"""check the function of the custom sample plugin """
|
||||
response = self.client.get('/plugin/sample/ho/he/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'Hi there testuser this works')
|
@ -1,61 +0,0 @@
|
||||
""" Unit tests for action plugins """
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from plugins.action import ActionPlugin
|
||||
|
||||
|
||||
class ActionPluginTests(TestCase):
|
||||
""" Tests for ActionPlugin """
|
||||
ACTION_RETURN = 'a action was performed'
|
||||
|
||||
def setUp(self):
|
||||
self.plugin = ActionPlugin('user')
|
||||
|
||||
class TestActionPlugin(ActionPlugin):
|
||||
"""a action plugin"""
|
||||
ACTION_NAME = 'abc123'
|
||||
|
||||
def perform_action(self):
|
||||
return ActionPluginTests.ACTION_RETURN + 'action'
|
||||
|
||||
def get_result(self):
|
||||
return ActionPluginTests.ACTION_RETURN + 'result'
|
||||
|
||||
def get_info(self):
|
||||
return ActionPluginTests.ACTION_RETURN + 'info'
|
||||
|
||||
self.action_plugin = TestActionPlugin('user')
|
||||
|
||||
class NameActionPlugin(ActionPlugin):
|
||||
PLUGIN_NAME = 'Aplugin'
|
||||
|
||||
self.action_name = NameActionPlugin('user')
|
||||
|
||||
def test_action_name(self):
|
||||
"""check the name definition possibilities"""
|
||||
self.assertEqual(self.plugin.action_name(), '')
|
||||
self.assertEqual(self.action_plugin.action_name(), 'abc123')
|
||||
self.assertEqual(self.action_name.action_name(), 'Aplugin')
|
||||
|
||||
def test_function(self):
|
||||
"""check functions"""
|
||||
# the class itself
|
||||
self.assertIsNone(self.plugin.perform_action())
|
||||
self.assertEqual(self.plugin.get_result(), False)
|
||||
self.assertIsNone(self.plugin.get_info())
|
||||
self.assertEqual(self.plugin.get_response(), {
|
||||
"action": '',
|
||||
"result": False,
|
||||
"info": None,
|
||||
})
|
||||
|
||||
# overriden functions
|
||||
self.assertEqual(self.action_plugin.perform_action(), self.ACTION_RETURN + 'action')
|
||||
self.assertEqual(self.action_plugin.get_result(), self.ACTION_RETURN + 'result')
|
||||
self.assertEqual(self.action_plugin.get_info(), self.ACTION_RETURN + 'info')
|
||||
self.assertEqual(self.action_plugin.get_response(), {
|
||||
"action": 'abc123',
|
||||
"result": self.ACTION_RETURN + 'result',
|
||||
"info": self.ACTION_RETURN + 'info',
|
||||
})
|
@ -1,171 +0,0 @@
|
||||
""" Unit tests for integration plugins """
|
||||
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url, include
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from plugins.integration import AppMixin, IntegrationPluginBase, SettingsMixin, UrlsMixin, NavigationMixin
|
||||
|
||||
|
||||
class BaseMixinDefinition:
|
||||
def test_mixin_name(self):
|
||||
# mixin name
|
||||
self.assertEqual(self.mixin.registered_mixins[0]['key'], self.MIXIN_NAME)
|
||||
# human name
|
||||
self.assertEqual(self.mixin.registered_mixins[0]['human_name'], self.MIXIN_HUMAN_NAME)
|
||||
|
||||
|
||||
class SettingsMixinTest(BaseMixinDefinition, TestCase):
|
||||
MIXIN_HUMAN_NAME = 'Settings'
|
||||
MIXIN_NAME = 'settings'
|
||||
MIXIN_ENABLE_CHECK = 'has_settings'
|
||||
|
||||
TEST_SETTINGS = {'setting1': [1, 2, 3]}
|
||||
|
||||
def setUp(self):
|
||||
class SettingsCls(SettingsMixin, IntegrationPluginBase):
|
||||
SETTINGS = self.TEST_SETTINGS
|
||||
self.mixin = SettingsCls()
|
||||
|
||||
class NoSettingsCls(SettingsMixin, IntegrationPluginBase):
|
||||
pass
|
||||
self.mixin_nothing = NoSettingsCls()
|
||||
|
||||
def test_function(self):
|
||||
# settings variable
|
||||
self.assertEqual(self.mixin.settings, self.TEST_SETTINGS)
|
||||
|
||||
# settings pattern
|
||||
target_pattern = {f'PLUGIN_{self.mixin.slug.upper()}_{key}': value for key, value in self.mixin.settings.items()}
|
||||
self.assertEqual(self.mixin.settingspatterns, target_pattern)
|
||||
|
||||
# no settings
|
||||
self.assertIsNone(self.mixin_nothing.settings)
|
||||
self.assertIsNone(self.mixin_nothing.settingspatterns)
|
||||
|
||||
|
||||
class UrlsMixinTest(BaseMixinDefinition, TestCase):
|
||||
MIXIN_HUMAN_NAME = 'URLs'
|
||||
MIXIN_NAME = 'urls'
|
||||
MIXIN_ENABLE_CHECK = 'has_urls'
|
||||
|
||||
def setUp(self):
|
||||
class UrlsCls(UrlsMixin, IntegrationPluginBase):
|
||||
def test():
|
||||
return 'ccc'
|
||||
URLS = [url('testpath', test, name='test'), ]
|
||||
self.mixin = UrlsCls()
|
||||
|
||||
class NoUrlsCls(UrlsMixin, IntegrationPluginBase):
|
||||
pass
|
||||
self.mixin_nothing = NoUrlsCls()
|
||||
|
||||
def test_function(self):
|
||||
plg_name = self.mixin.plugin_name()
|
||||
|
||||
# base_url
|
||||
target_url = f'{settings.PLUGIN_URL}/{plg_name}/'
|
||||
self.assertEqual(self.mixin.base_url, target_url)
|
||||
|
||||
# urlpattern
|
||||
target_pattern = url(f'^{plg_name}/', include((self.mixin.urls, plg_name)), name=plg_name)
|
||||
self.assertEqual(self.mixin.urlpatterns.reverse_dict, target_pattern.reverse_dict)
|
||||
|
||||
# resolve the view
|
||||
self.assertEqual(self.mixin.urlpatterns.resolve('/testpath').func(), 'ccc')
|
||||
self.assertEqual(self.mixin.urlpatterns.reverse('test'), 'testpath')
|
||||
|
||||
# no url
|
||||
self.assertIsNone(self.mixin_nothing.urls)
|
||||
self.assertIsNone(self.mixin_nothing.urlpatterns)
|
||||
|
||||
|
||||
class AppMixinTest(BaseMixinDefinition, TestCase):
|
||||
MIXIN_HUMAN_NAME = 'App registration'
|
||||
MIXIN_NAME = 'app'
|
||||
MIXIN_ENABLE_CHECK = 'has_app'
|
||||
|
||||
def setUp(self):
|
||||
class TestCls(AppMixin, IntegrationPluginBase):
|
||||
pass
|
||||
self.mixin = TestCls()
|
||||
|
||||
def test_function(self):
|
||||
# test that this plugin is in settings
|
||||
self.assertIn('plugins.samples.integration', settings.INSTALLED_APPS)
|
||||
|
||||
|
||||
class NavigationMixinTest(BaseMixinDefinition, TestCase):
|
||||
MIXIN_HUMAN_NAME = 'Navigation Links'
|
||||
MIXIN_NAME = 'navigation'
|
||||
MIXIN_ENABLE_CHECK = 'has_naviation'
|
||||
|
||||
def setUp(self):
|
||||
class NavigationCls(NavigationMixin, IntegrationPluginBase):
|
||||
NAVIGATION = [
|
||||
{'name': 'aa', 'link': 'plugin:test:test_view'},
|
||||
]
|
||||
self.mixin = NavigationCls()
|
||||
|
||||
def test_function(self):
|
||||
# check right configuration
|
||||
self.assertEqual(self.mixin.navigation, [{'name': 'aa', 'link': 'plugin:test:test_view'}, ])
|
||||
# check wrong links fails
|
||||
with self.assertRaises(NotImplementedError):
|
||||
class NavigationCls(NavigationMixin, IntegrationPluginBase):
|
||||
NAVIGATION = ['aa', 'aa']
|
||||
NavigationCls()
|
||||
|
||||
|
||||
class IntegrationPluginBaseTests(TestCase):
|
||||
""" Tests for IntegrationPluginBase """
|
||||
|
||||
def setUp(self):
|
||||
self.plugin = IntegrationPluginBase()
|
||||
|
||||
class SimpeIntegrationPluginBase(IntegrationPluginBase):
|
||||
PLUGIN_NAME = 'SimplePlugin'
|
||||
|
||||
self.plugin_simple = SimpeIntegrationPluginBase()
|
||||
|
||||
class NameIntegrationPluginBase(IntegrationPluginBase):
|
||||
PLUGIN_NAME = 'Aplugin'
|
||||
PLUGIN_SLUG = 'a'
|
||||
PLUGIN_TITLE = 'a titel'
|
||||
PUBLISH_DATE = "1111-11-11"
|
||||
VERSION = '1.2.3a'
|
||||
WEBSITE = 'http://aa.bb/cc'
|
||||
|
||||
self.plugin_name = NameIntegrationPluginBase()
|
||||
|
||||
def test_action_name(self):
|
||||
"""check the name definition possibilities"""
|
||||
# plugin_name
|
||||
self.assertEqual(self.plugin.plugin_name(), '')
|
||||
self.assertEqual(self.plugin_simple.plugin_name(), 'SimplePlugin')
|
||||
self.assertEqual(self.plugin_name.plugin_name(), 'Aplugin')
|
||||
|
||||
# slug
|
||||
self.assertEqual(self.plugin.slug, '')
|
||||
self.assertEqual(self.plugin_simple.slug, 'simpleplugin')
|
||||
self.assertEqual(self.plugin_name.slug, 'a')
|
||||
|
||||
# human_name
|
||||
self.assertEqual(self.plugin.human_name, '')
|
||||
self.assertEqual(self.plugin_simple.human_name, 'SimplePlugin')
|
||||
self.assertEqual(self.plugin_name.human_name, 'a titel')
|
||||
|
||||
# pub_date
|
||||
self.assertEqual(self.plugin_name.pub_date, datetime(1111, 11, 11, 0, 0))
|
||||
|
||||
# version
|
||||
self.assertEqual(self.plugin.version, None)
|
||||
self.assertEqual(self.plugin_simple.version, None)
|
||||
self.assertEqual(self.plugin_name.version, '1.2.3a')
|
||||
|
||||
# website
|
||||
self.assertEqual(self.plugin.website, None)
|
||||
self.assertEqual(self.plugin_simple.website, None)
|
||||
self.assertEqual(self.plugin_name.website, 'http://aa.bb/cc')
|
@ -1,74 +0,0 @@
|
||||
""" Unit tests for plugins """
|
||||
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
|
||||
import plugins.plugin
|
||||
import plugins.integration
|
||||
from plugins.samples.integration.sample import SampleIntegrationPlugin
|
||||
from plugins.samples.integration.another_sample import WrongIntegrationPlugin, NoIntegrationPlugin
|
||||
from plugins.plugins import load_integration_plugins # , load_action_plugins, load_barcode_plugins
|
||||
import part.templatetags.plugin_extras as plugin_tags
|
||||
|
||||
|
||||
class InvenTreePluginTests(TestCase):
|
||||
""" Tests for InvenTreePlugin """
|
||||
def setUp(self):
|
||||
self.plugin = plugins.plugin.InvenTreePlugin()
|
||||
|
||||
class NamedPlugin(plugins.plugin.InvenTreePlugin):
|
||||
"""a named plugin"""
|
||||
PLUGIN_NAME = 'abc123'
|
||||
|
||||
self.named_plugin = NamedPlugin()
|
||||
|
||||
def test_basic_plugin_init(self):
|
||||
"""check if a basic plugin intis"""
|
||||
self.assertEqual(self.plugin.PLUGIN_NAME, '')
|
||||
self.assertEqual(self.plugin.plugin_name(), '')
|
||||
|
||||
def test_basic_plugin_name(self):
|
||||
"""check if the name of a basic plugin can be set"""
|
||||
self.assertEqual(self.named_plugin.PLUGIN_NAME, 'abc123')
|
||||
self.assertEqual(self.named_plugin.plugin_name(), 'abc123')
|
||||
|
||||
|
||||
class PluginIntegrationTests(TestCase):
|
||||
""" Tests for general plugin functions """
|
||||
|
||||
def test_plugin_loading(self):
|
||||
"""check if plugins load as expected"""
|
||||
plugin_names_integration = [a().plugin_name() for a in load_integration_plugins()]
|
||||
# plugin_names_barcode = [a().plugin_name() for a in load_barcode_plugins()] # TODO refactor barcode plugin to support standard loading
|
||||
# plugin_names_action = [a().plugin_name() for a in load_action_plugins()] # TODO refactor action plugin to support standard loading
|
||||
|
||||
self.assertEqual(plugin_names_integration, ['NoIntegrationPlugin', 'WrongIntegrationPlugin', 'SampleIntegrationPlugin'])
|
||||
# self.assertEqual(plugin_names_action, '')
|
||||
# self.assertEqual(plugin_names_barcode, '')
|
||||
|
||||
|
||||
class PluginTagTests(TestCase):
|
||||
""" Tests for the plugin extras """
|
||||
|
||||
def setUp(self):
|
||||
self.sample = SampleIntegrationPlugin()
|
||||
self.plugin_no = NoIntegrationPlugin()
|
||||
self.plugin_wrong = WrongIntegrationPlugin()
|
||||
|
||||
def test_tag_plugin_list(self):
|
||||
"""test that all plugins are listed"""
|
||||
self.assertEqual(plugin_tags.plugin_list(), settings.INTEGRATION_PLUGIN_LIST)
|
||||
|
||||
def test_tag_plugin_settings(self):
|
||||
"""check all plugins are listed"""
|
||||
self.assertEqual(plugin_tags.plugin_settings(self.sample), settings.INTEGRATION_PLUGIN_SETTING.get(self.sample))
|
||||
|
||||
def test_tag_mixin_enabled(self):
|
||||
"""check that mixin enabled functions work"""
|
||||
key = 'urls'
|
||||
# mixin enabled
|
||||
self.assertEqual(plugin_tags.mixin_enabled(self.sample, key), True)
|
||||
# mixin not enabled
|
||||
self.assertEqual(plugin_tags.mixin_enabled(self.plugin_wrong, key), False)
|
||||
# mxixn not existing
|
||||
self.assertEqual(plugin_tags.mixin_enabled(self.plugin_no, key), False)
|
Reference in New Issue
Block a user