Fix removed stuff from merge 99676ee

This commit is contained in:
Matthias 2022-05-30 20:09:27 +02:00
parent 0db69252d7
commit 6f498d6292
No known key found for this signature in database
GPG Key ID: AB6D0E6C4CB65093
8 changed files with 115 additions and 211 deletions

View File

@ -133,6 +133,22 @@ jobs:
invoke check-server invoke check-server
coverage run -m unittest discover -s test/ coverage run -m unittest discover -s test/
docstyle:
name: Style [Python Docstrings]
runs-on: ubuntu-20.04
needs: pre-commit
continue-on-error: true
steps:
- uses: actions/checkout@v1
- name: Enviroment Setup
uses: ./.github/actions/setup
with:
install: true
- name: Run flake8
run: flake8 InvenTree
coverage: coverage:
name: Tests - DB [SQLite] + Coverage name: Tests - DB [SQLite] + Coverage
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04

View File

@ -1,5 +1,5 @@
""" """Version information for InvenTree.
Version information for InvenTree.
Provides information on the current InvenTree version Provides information on the current InvenTree version
""" """
@ -17,12 +17,12 @@ INVENTREE_SW_VERSION = "0.8.0 dev"
def inventreeInstanceName(): def inventreeInstanceName():
""" Returns the InstanceName settings for the current database """ """Returns the InstanceName settings for the current database."""
return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "") return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "")
def inventreeInstanceTitle(): def inventreeInstanceTitle():
""" Returns the InstanceTitle for the current database """ """Returns the InstanceTitle for the current database."""
if common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE_TITLE", False): if common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE_TITLE", False):
return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "") return common.models.InvenTreeSetting.get_setting("INVENTREE_INSTANCE", "")
else: else:
@ -30,13 +30,12 @@ def inventreeInstanceTitle():
def inventreeVersion(): def inventreeVersion():
""" Returns the InvenTree version string """ """Returns the InvenTree version string."""
return INVENTREE_SW_VERSION.lower().strip() return INVENTREE_SW_VERSION.lower().strip()
def inventreeVersionTuple(version=None): def inventreeVersionTuple(version=None):
""" Return the InvenTree version string as (maj, min, sub) tuple """ """Return the InvenTree version string as (maj, min, sub) tuple."""
if version is None: if version is None:
version = INVENTREE_SW_VERSION version = INVENTREE_SW_VERSION
@ -46,21 +45,16 @@ def inventreeVersionTuple(version=None):
def isInvenTreeDevelopmentVersion(): def isInvenTreeDevelopmentVersion():
""" """Return True if current InvenTree version is a "development" version."""
Return True if current InvenTree version is a "development" version
"""
return inventreeVersion().endswith('dev') return inventreeVersion().endswith('dev')
def inventreeDocsVersion(): def inventreeDocsVersion():
""" """Return the version string matching the latest documentation.
Return the version string matching the latest documentation.
Development -> "latest" Development -> "latest"
Release -> "major.minor.sub" e.g. "0.5.2" Release -> "major.minor.sub" e.g. "0.5.2"
""" """
if isInvenTreeDevelopmentVersion(): if isInvenTreeDevelopmentVersion():
return "latest" return "latest"
else: else:
@ -68,13 +62,10 @@ def inventreeDocsVersion():
def isInvenTreeUpToDate(): def isInvenTreeUpToDate():
""" """Test if the InvenTree instance is "up to date" with the latest version.
Test if the InvenTree instance is "up to date" with the latest version.
A background task periodically queries GitHub for latest version, A background task periodically queries GitHub for latest version, and stores it to the database as INVENTREE_LATEST_VERSION
and stores it to the database as INVENTREE_LATEST_VERSION
""" """
latest = common.models.InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION', backup_value=None, create=False) latest = common.models.InvenTreeSetting.get_setting('INVENTREE_LATEST_VERSION', backup_value=None, create=False)
# No record for "latest" version - we must assume we are up to date! # No record for "latest" version - we must assume we are up to date!
@ -93,13 +84,12 @@ def inventreeApiVersion():
def inventreeDjangoVersion(): def inventreeDjangoVersion():
""" Return the version of Django library """ """Return the version of Django library."""
return django.get_version() return django.get_version()
def inventreeCommitHash(): def inventreeCommitHash():
""" Returns the git commit hash for the running codebase """ """ Returns the git commit hash for the running codebase."""
# First look in the environment variables, i.e. if running in docker # First look in the environment variables, i.e. if running in docker
commit_hash = os.environ.get('INVENTREE_COMMIT_HASH', '') commit_hash = os.environ.get('INVENTREE_COMMIT_HASH', '')
@ -113,8 +103,7 @@ def inventreeCommitHash():
def inventreeCommitDate(): def inventreeCommitDate():
""" Returns the git commit date for the running codebase """ """Returns the git commit date for the running codebase."""
# First look in the environment variables, e.g. if running in docker # First look in the environment variables, e.g. if running in docker
commit_date = os.environ.get('INVENTREE_COMMIT_DATE', '') commit_date = os.environ.get('INVENTREE_COMMIT_DATE', '')

View File

@ -12,9 +12,7 @@ logger = logging.getLogger('inventree')
# region methods # region methods
class NotificationMethod: class NotificationMethod:
""" """Base class for notification methods."""
Base class for notification methods
"""
METHOD_NAME = '' METHOD_NAME = ''
METHOD_ICON = None METHOD_ICON = None
@ -92,11 +90,11 @@ class NotificationMethod:
# region plugins # region plugins
def get_plugin(self): def get_plugin(self):
"""Returns plugin class""" """Returns plugin class."""
return False return False
def global_setting_disable(self): def global_setting_disable(self):
"""Check if the method is defined in a plugin and has a global setting""" """Check if the method is defined in a plugin and has a global setting."""
# Check if plugin has a setting # Check if plugin has a setting
if not self.GLOBAL_SETTING: if not self.GLOBAL_SETTING:
return False return False
@ -115,9 +113,7 @@ class NotificationMethod:
return False return False
def usersetting(self, target): def usersetting(self, target):
""" """Returns setting for this method for a given user."""
Returns setting for this method for a given user
"""
return NotificationUserSetting.get_setting(f'NOTIFICATION_METHOD_{self.METHOD_NAME.upper()}', user=target, method=self.METHOD_NAME) return NotificationUserSetting.get_setting(f'NOTIFICATION_METHOD_{self.METHOD_NAME.upper()}', user=target, method=self.METHOD_NAME)
# endregion # endregion
@ -204,10 +200,7 @@ class UIMessageNotification(SingleNotificationMethod):
def trigger_notification(obj, category=None, obj_ref='pk', **kwargs): def trigger_notification(obj, category=None, obj_ref='pk', **kwargs):
""" """Send out a notification."""
Send out a notification
"""
targets = kwargs.get('targets', None) targets = kwargs.get('targets', None)
target_fnc = kwargs.get('target_fnc', None) target_fnc = kwargs.get('target_fnc', None)
target_args = kwargs.get('target_args', []) target_args = kwargs.get('target_args', [])

View File

@ -21,9 +21,7 @@ from .serializers import (PartLabelSerializer, StockItemLabelSerializer,
class LabelListView(generics.ListAPIView): class LabelListView(generics.ListAPIView):
""" """Generic API class for label templates."""
Generic API class for label templates
"""
filter_backends = [ filter_backends = [
DjangoFilterBackend, DjangoFilterBackend,
@ -41,13 +39,11 @@ class LabelListView(generics.ListAPIView):
class LabelPrintMixin: class LabelPrintMixin:
""" """Mixin for printing labels."""
Mixin for printing labels
"""
def get_plugin(self, request): def get_plugin(self, request):
""" """Return the label printing plugin associated with this request.
Return the label printing plugin associated with this request.
This is provided in the url, e.g. ?plugin=myprinter This is provided in the url, e.g. ?plugin=myprinter
Requires: Requires:
@ -56,7 +52,6 @@ class LabelPrintMixin:
- matching plugin implements the 'labels' mixin - matching plugin implements the 'labels' mixin
- matching plugin is enabled - matching plugin is enabled
""" """
if not settings.PLUGINS_ENABLED: if not settings.PLUGINS_ENABLED:
return None # pragma: no cover return None # pragma: no cover
@ -80,10 +75,7 @@ class LabelPrintMixin:
raise NotFound(f"Plugin '{plugin_key}' not found") raise NotFound(f"Plugin '{plugin_key}' not found")
def print(self, request, items_to_print): def print(self, request, items_to_print):
""" """Print this label template against a number of pre-validated items."""
Print this label template against a number of pre-validated items
"""
# Check the request to determine if the user has selected a label printing plugin # Check the request to determine if the user has selected a label printing plugin
plugin = self.get_plugin(request) plugin = self.get_plugin(request)
@ -119,26 +111,20 @@ class LabelPrintMixin:
label_name += ".pdf" label_name += ".pdf"
if plugin is not None: if plugin is not None:
""" """Label printing is to be handled by a plugin, rather than being exported to PDF.
Label printing is to be handled by a plugin,
rather than being exported to PDF.
In this case, we do the following: In this case, we do the following:
- Individually generate each label, exporting as an image file - Individually generate each label, exporting as an image file
- Pass all the images through to the label printing plugin - Pass all the images through to the label printing plugin
- Return a JSON response indicating that the printing has been offloaded - Return a JSON response indicating that the printing has been offloaded
""" """
# Label instance # Label instance
label_instance = self.get_object() label_instance = self.get_object()
for idx, output in enumerate(outputs): for idx, output in enumerate(outputs):
""" """For each output, we generate a temporary image file, which will then get sent to the printer."""
For each output, we generate a temporary image file,
which will then get sent to the printer
"""
# Generate PDF data for the label # Generate PDF data for the label
pdf = output.get_document().write_pdf() pdf = output.get_document().write_pdf()
@ -159,20 +145,14 @@ class LabelPrintMixin:
}) })
elif debug_mode: elif debug_mode:
""" """Contatenate all rendered templates into a single HTML string, and return the string as a HTML response."""
Contatenate all rendered templates into a single HTML string,
and return the string as a HTML response.
"""
html = "\n".join(outputs) html = "\n".join(outputs)
return HttpResponse(html) return HttpResponse(html)
else: else:
""" """Concatenate all rendered pages into a single PDF object, and return the resulting document!"""
Concatenate all rendered pages into a single PDF object,
and return the resulting document!
"""
pages = [] pages = []
@ -198,15 +178,10 @@ class LabelPrintMixin:
class StockItemLabelMixin: class StockItemLabelMixin:
""" """Mixin for extracting stock items from query params."""
Mixin for extracting stock items from query params
"""
def get_items(self): def get_items(self):
""" """Return a list of requested stock items."""
Return a list of requested stock items
"""
items = [] items = []
params = self.request.query_params params = self.request.query_params
@ -231,25 +206,20 @@ class StockItemLabelMixin:
class StockItemLabelList(LabelListView, StockItemLabelMixin): class StockItemLabelList(LabelListView, StockItemLabelMixin):
""" """API endpoint for viewing list of StockItemLabel objects.
API endpoint for viewing list of StockItemLabel objects.
Filterable by: Filterable by:
- enabled: Filter by enabled / disabled status - enabled: Filter by enabled / disabled status
- item: Filter by single stock item - item: Filter by single stock item
- items: Filter by list of stock items - items: Filter by list of stock items
""" """
queryset = StockItemLabel.objects.all() queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer serializer_class = StockItemLabelSerializer
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
""" """Filter the StockItem label queryset."""
Filter the StockItem label queryset.
"""
queryset = super().filter_queryset(queryset) queryset = super().filter_queryset(queryset)
# List of StockItem objects to match against # List of StockItem objects to match against
@ -304,42 +274,30 @@ class StockItemLabelList(LabelListView, StockItemLabelMixin):
class StockItemLabelDetail(generics.RetrieveUpdateDestroyAPIView): class StockItemLabelDetail(generics.RetrieveUpdateDestroyAPIView):
""" """API endpoint for a single StockItemLabel object."""
API endpoint for a single StockItemLabel object
"""
queryset = StockItemLabel.objects.all() queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer serializer_class = StockItemLabelSerializer
class StockItemLabelPrint(generics.RetrieveAPIView, StockItemLabelMixin, LabelPrintMixin): class StockItemLabelPrint(generics.RetrieveAPIView, StockItemLabelMixin, LabelPrintMixin):
""" """API endpoint for printing a StockItemLabel object."""
API endpoint for printing a StockItemLabel object
"""
queryset = StockItemLabel.objects.all() queryset = StockItemLabel.objects.all()
serializer_class = StockItemLabelSerializer serializer_class = StockItemLabelSerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" """Check if valid stock item(s) have been provided."""
Check if valid stock item(s) have been provided.
"""
items = self.get_items() items = self.get_items()
return self.print(request, items) return self.print(request, items)
class StockLocationLabelMixin: class StockLocationLabelMixin:
""" """Mixin for extracting stock locations from query params."""
Mixin for extracting stock locations from query params
"""
def get_locations(self): def get_locations(self):
""" """Return a list of requested stock locations."""
Return a list of requested stock locations
"""
locations = [] locations = []
params = self.request.query_params params = self.request.query_params
@ -364,8 +322,7 @@ class StockLocationLabelMixin:
class StockLocationLabelList(LabelListView, StockLocationLabelMixin): class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
""" """API endpoint for viewiing list of StockLocationLabel objects.
API endpoint for viewiing list of StockLocationLabel objects.
Filterable by: Filterable by:
@ -378,10 +335,7 @@ class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
serializer_class = StockLocationLabelSerializer serializer_class = StockLocationLabelSerializer
def filter_queryset(self, queryset): def filter_queryset(self, queryset):
""" """Filter the StockLocationLabel queryset."""
Filter the StockLocationLabel queryset
"""
queryset = super().filter_queryset(queryset) queryset = super().filter_queryset(queryset)
# List of StockLocation objects to match against # List of StockLocation objects to match against
@ -436,18 +390,14 @@ class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
class StockLocationLabelDetail(generics.RetrieveUpdateDestroyAPIView): class StockLocationLabelDetail(generics.RetrieveUpdateDestroyAPIView):
""" """API endpoint for a single StockLocationLabel object."""
API endpoint for a single StockLocationLabel object
"""
queryset = StockLocationLabel.objects.all() queryset = StockLocationLabel.objects.all()
serializer_class = StockLocationLabelSerializer serializer_class = StockLocationLabelSerializer
class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin, LabelPrintMixin): class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin, LabelPrintMixin):
""" """API endpoint for printing a StockLocationLabel object."""
API endpoint for printing a StockLocationLabel object
"""
queryset = StockLocationLabel.objects.all() queryset = StockLocationLabel.objects.all()
seiralizer_class = StockLocationLabelSerializer seiralizer_class = StockLocationLabelSerializer
@ -460,15 +410,10 @@ class StockLocationLabelPrint(generics.RetrieveAPIView, StockLocationLabelMixin,
class PartLabelMixin: class PartLabelMixin:
""" """Mixin for extracting Part objects from query parameters."""
Mixin for extracting Part objects from query parameters
"""
def get_parts(self): def get_parts(self):
""" """Return a list of requested Part objects."""
Return a list of requested Part objects
"""
parts = [] parts = []
params = self.request.query_params params = self.request.query_params
@ -491,9 +436,7 @@ class PartLabelMixin:
class PartLabelList(LabelListView, PartLabelMixin): class PartLabelList(LabelListView, PartLabelMixin):
""" """API endpoint for viewing list of PartLabel objects."""
API endpoint for viewing list of PartLabel objects
"""
queryset = PartLabel.objects.all() queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer serializer_class = PartLabelSerializer
@ -539,27 +482,20 @@ class PartLabelList(LabelListView, PartLabelMixin):
class PartLabelDetail(generics.RetrieveUpdateDestroyAPIView): class PartLabelDetail(generics.RetrieveUpdateDestroyAPIView):
""" """API endpoint for a single PartLabel object."""
API endpoint for a single PartLabel object
"""
queryset = PartLabel.objects.all() queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer serializer_class = PartLabelSerializer
class PartLabelPrint(generics.RetrieveAPIView, PartLabelMixin, LabelPrintMixin): class PartLabelPrint(generics.RetrieveAPIView, PartLabelMixin, LabelPrintMixin):
""" """API endpoint for printing a PartLabel object."""
API endpoint for printing a PartLabel object
"""
queryset = PartLabel.objects.all() queryset = PartLabel.objects.all()
serializer_class = PartLabelSerializer serializer_class = PartLabelSerializer
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
""" """Check if valid part(s) have been provided."""
Check if valid part(s) have been provided
"""
parts = self.get_parts() parts = self.get_parts()
return self.print(request, parts) return self.print(request, parts)

View File

@ -1,4 +1,5 @@
"""Functions to print a label to a mixin printer""" """Functions to print a label to a mixin printer."""
import logging import logging
import sys import sys
import traceback import traceback
@ -16,20 +17,19 @@ from plugin.registry import registry
logger = logging.getLogger('inventree') logger = logging.getLogger('inventree')
def print_label(plugin_slug, pdf_data, filename=None, label_instance=None, user=None): def print_label(plugin_slug: str, pdf_data, filename=None, label_instance=None, user=None):
""" """Print label with the provided plugin.
Print label with the provided plugin.
This task is nominally handled by the background worker. This task is nominally handled by the background worker.
If the printing fails (throws an exception) then the user is notified. If the printing fails (throws an exception) then the user is notified.
Arguments: Args:
plugin_slug: The unique slug (key) of the plugin plugin_slug (str): The unique slug (key) of the plugin.
pdf_data: Binary PDF data pdf_data: Binary PDF data.
filename: The intended name of the printed label filename: The intended name of the printed label. Defaults to None.
label_instance (Union[LabelTemplate, None], optional): The template instance that should be printed. Defaults to None.
user (Union[User, None], optional): User that should be informed of errors. Defaults to None.
""" """
logger.info(f"Plugin '{plugin_slug}' is printing a label '{filename}'") logger.info(f"Plugin '{plugin_slug}' is printing a label '{filename}'")
plugin = registry.plugins.get(plugin_slug, None) plugin = registry.plugins.get(plugin_slug, None)

View File

@ -1,11 +1,10 @@
"""Plugin mixin classes for label plugins""" """Plugin mixin classes for label plugins."""
from plugin.helpers import MixinNotImplementedError from plugin.helpers import MixinNotImplementedError
class LabelPrintingMixin: class LabelPrintingMixin:
""" """Mixin which enables direct printing of stock labels.
Mixin which enables direct printing of stock labels.
Each plugin must provide a NAME attribute, which is used to uniquely identify the printer. Each plugin must provide a NAME attribute, which is used to uniquely identify the printer.
@ -13,9 +12,7 @@ class LabelPrintingMixin:
""" """
class MixinMeta: class MixinMeta:
""" """Meta options for this mixin."""
Meta options for this mixin
"""
MIXIN_NAME = 'Label printing' MIXIN_NAME = 'Label printing'
def __init__(self): # pragma: no cover def __init__(self): # pragma: no cover
@ -24,8 +21,7 @@ class LabelPrintingMixin:
self.add_mixin('labels', True, __class__) self.add_mixin('labels', True, __class__)
def print_label(self, **kwargs): def print_label(self, **kwargs):
""" """Callback to print a single label.
Callback to print a single label
kwargs: kwargs:
pdf_data: Raw PDF data of the rendered label pdf_data: Raw PDF data of the rendered label
@ -36,6 +32,5 @@ class LabelPrintingMixin:
filename: The filename of this PDF label filename: The filename of this PDF label
user: The user who printed this label user: The user who printed this label
""" """
# Unimplemented (to be implemented by the particular plugin class) # Unimplemented (to be implemented by the particular plugin class)
raise MixinNotImplementedError('This Plugin must implement a `print_label` method') raise MixinNotImplementedError('This Plugin must implement a `print_label` method')

View File

@ -1,4 +1,5 @@
"""Unit tests for the label printing mixin""" """Unit tests for the label printing mixin."""
import os import os
from django.apps import apps from django.apps import apps
@ -18,7 +19,7 @@ from stock.models import StockItem, StockLocation
class LabelMixinTests(InvenTreeAPITestCase): class LabelMixinTests(InvenTreeAPITestCase):
"""Test that the Label mixin operates correctly""" """Test that the Label mixin operates correctly."""
fixtures = [ fixtures = [
'category', 'category',
@ -30,14 +31,13 @@ class LabelMixinTests(InvenTreeAPITestCase):
roles = 'all' roles = 'all'
def do_activate_plugin(self): def do_activate_plugin(self):
"""Activate the 'samplelabel' plugin""" """Activate the 'samplelabel' plugin."""
config = registry.get_plugin('samplelabel').plugin_config() config = registry.get_plugin('samplelabel').plugin_config()
config.active = True config.active = True
config.save() config.save()
def do_url(self, parts, plugin_ref, label, url_name: str = 'api-part-label-print', url_single: str = 'part', invalid: bool = False): def do_url(self, parts, plugin_ref, label, url_name: str = 'api-part-label-print', url_single: str = 'part', invalid: bool = False):
"""Generate an URL to print a label""" """Generate an URL to print a label."""
# Construct URL # Construct URL
kwargs = {} kwargs = {}
if label: if label:
@ -64,7 +64,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
return url return url
def test_wrong_implementation(self): def test_wrong_implementation(self):
"""Test that a wrong implementation raises an error""" """Test that a wrong implementation raises an error."""
class WrongPlugin(LabelPrintingMixin, InvenTreePlugin): class WrongPlugin(LabelPrintingMixin, InvenTreePlugin):
pass pass
@ -74,8 +74,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
plugin.print_label(filename='test') plugin.print_label(filename='test')
def test_installed(self): def test_installed(self):
"""Test that the sample printing plugin is installed""" """Test that the sample printing plugin is installed."""
# Get all label plugins # Get all label plugins
plugins = registry.with_mixin('labels') plugins = registry.with_mixin('labels')
self.assertEqual(len(plugins), 1) self.assertEqual(len(plugins), 1)
@ -85,8 +84,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
self.assertEqual(len(plugins), 0) self.assertEqual(len(plugins), 0)
def test_api(self): def test_api(self):
"""Test that we can filter the API endpoint by mixin""" """Test that we can filter the API endpoint by mixin."""
url = reverse('api-plugin-list') url = reverse('api-plugin-list')
# Try POST (disallowed) # Try POST (disallowed)
@ -130,8 +128,7 @@ class LabelMixinTests(InvenTreeAPITestCase):
self.assertEqual(data['key'], 'samplelabel') self.assertEqual(data['key'], 'samplelabel')
def test_printing_process(self): def test_printing_process(self):
"""Test that a label can be printed""" """Test that a label can be printed."""
# Ensure the labels were created # Ensure the labels were created
apps.get_app_config('label').create_labels() apps.get_app_config('label').create_labels()
@ -194,13 +191,13 @@ class LabelMixinTests(InvenTreeAPITestCase):
self.do_activate_plugin() self.do_activate_plugin()
def run_print_test(label, qs, url_name, url_single): def run_print_test(label, qs, url_name, url_single):
"""Run tests on single and multiple page printing """Run tests on single and multiple page printing.
Args: Args:
label (_type_): class of the label label: class of the label
qs (_type_): class of the base queryset qs: class of the base queryset
url_name (_type_): url for endpoints url_name: url for endpoints
url_single (_type_): item lookup reference url_single: item lookup reference
""" """
label = label.objects.first() label = label.objects.first()
qs = qs.objects.all() qs = qs.objects.all()

View File

@ -1,5 +1,4 @@
""" """Registry for loading and managing multiple plugins at run-time.
Registry for loading and managing multiple plugins at run-time
- Holds the class and the object that contains all code to maintain plugin states - Holds the class and the object that contains all code to maintain plugin states
- Manages setup and teardown of plugin class instances - Manages setup and teardown of plugin class instances
@ -31,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:
"""Initialize registry. """Initialize registry.
@ -59,10 +56,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
@ -70,15 +64,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:
@ -91,7 +83,7 @@ class PluginsRegistry:
# region public functions # region public functions
# region loading / unloading # region loading / unloading
def load_plugins(self, full_reload: bool = False): def load_plugins(self, full_reload: bool = False):
"""Load and activate all IntegrationPlugins """Load and activate all IntegrationPlugins.
Args: Args:
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
@ -154,10 +146,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
@ -181,12 +170,11 @@ class PluginsRegistry:
logger.info('Finished unloading plugins') logger.info('Finished unloading plugins')
def reload_plugins(self, full_reload: bool = False): def reload_plugins(self, full_reload: bool = False):
"""Safely reload IntegrationPlugins """Safely reload.
Args: Args:
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
""" """
# 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
@ -200,8 +188,7 @@ class PluginsRegistry:
logger.info('Finished reloading plugins') logger.info('Finished reloading plugins')
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
@ -230,10 +217,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
@ -254,9 +238,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():
@ -277,14 +259,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')
@ -348,7 +328,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, full_reload: bool = False): def _activate_plugins(self, force_reload=False, full_reload: bool = False):
"""Run activation functions for all plugins """Run activation functions for all plugins.
Args: Args:
force_reload (bool, optional): Also reload base apps. Defaults to False. force_reload (bool, optional): Also reload base apps. Defaults to False.
@ -363,8 +343,7 @@ class PluginsRegistry:
self.activate_plugin_app(plugins, force_reload=force_reload, full_reload=full_reload) self.activate_plugin_app(plugins, force_reload=force_reload, full_reload=full_reload)
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()
@ -437,21 +416,20 @@ 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 here.
""" """
pass pass
def activate_plugin_app(self, plugins, force_reload=False, full_reload: bool = False): def activate_plugin_app(self, plugins, force_reload=False, full_reload: bool = False):
"""Activate AppMixin plugins - add custom apps and reload """Activate AppMixin plugins - add custom apps and reload.
Args: Args:
plugins (dict): List of IntegrationPlugins that should be installed plugins (dict): List of IntegrationPlugins that should be installed
force_reload (bool, optional): Only reload base apps. Defaults to False. force_reload (bool, optional): Only reload base apps. Defaults to False.
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
""" """
from common.models import InvenTreeSetting from common.models import InvenTreeSetting
if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_APP'): if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_APP'):
@ -516,8 +494,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
""" """
@ -601,13 +580,12 @@ class PluginsRegistry:
clear_url_caches() clear_url_caches()
def _reload_apps(self, force_reload: bool = False, full_reload: bool = False): def _reload_apps(self, force_reload: bool = False, full_reload: bool = False):
"""Internal: reload apps using django internal functions """Internal: reload apps using django internal functions.
Args: Args:
force_reload (bool, optional): Also reload base apps. Defaults to False. force_reload (bool, optional): Also reload base apps. Defaults to False.
full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False. full_reload (bool, optional): Reload everything - including plugin mechanism. Defaults to False.
""" """
# If full_reloading is set to true we do not want to set the flag # If full_reloading is set to true we do not want to set the flag
if not full_reload: if not full_reload:
self.is_loading = True # set flag to disable loop reloading self.is_loading = True # set flag to disable loop reloading
@ -622,9 +600,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)
@ -638,5 +616,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)