mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-15 19:45:46 +00:00
fix docstrings 5
This commit is contained in:
@ -1,6 +1,5 @@
|
|||||||
"""
|
"""Plugin mixin classes for barcode plugin"""
|
||||||
Plugin mixin classes for barcode plugin
|
|
||||||
"""
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import string
|
import string
|
||||||
|
|
||||||
@ -10,15 +9,13 @@ from stock.serializers import LocationSerializer, StockItemSerializer
|
|||||||
|
|
||||||
|
|
||||||
def hash_barcode(barcode_data):
|
def hash_barcode(barcode_data):
|
||||||
"""
|
"""Calculate an MD5 hash of barcode data.
|
||||||
Calculate an MD5 hash of barcode data.
|
|
||||||
|
|
||||||
HACK: Remove any 'non printable' characters from the hash,
|
HACK: Remove any 'non printable' characters from the hash,
|
||||||
as it seems browers will remove special control characters...
|
as it seems browers will remove special control characters...
|
||||||
|
|
||||||
TODO: Work out a way around this!
|
TODO: Work out a way around this!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
barcode_data = str(barcode_data).strip()
|
barcode_data = str(barcode_data).strip()
|
||||||
|
|
||||||
printable_chars = filter(lambda x: x in string.printable, barcode_data)
|
printable_chars = filter(lambda x: x in string.printable, barcode_data)
|
||||||
@ -30,16 +27,16 @@ def hash_barcode(barcode_data):
|
|||||||
|
|
||||||
|
|
||||||
class BarcodeMixin:
|
class BarcodeMixin:
|
||||||
"""
|
"""Mixin that enables barcode handeling
|
||||||
Mixin that enables barcode handeling
|
|
||||||
Custom barcode plugins should use and extend this mixin as necessary.
|
Custom barcode plugins should use and extend this mixin as necessary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
ACTION_NAME = ""
|
ACTION_NAME = ""
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""
|
"""Meta options for this mixin"""
|
||||||
meta options for this mixin
|
|
||||||
"""
|
|
||||||
MIXIN_NAME = 'Barcode'
|
MIXIN_NAME = 'Barcode'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -48,35 +45,27 @@ class BarcodeMixin:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_barcode(self):
|
def has_barcode(self):
|
||||||
"""
|
"""Does this plugin have everything needed to process a barcode"""
|
||||||
Does this plugin have everything needed to process a barcode
|
|
||||||
"""
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def init(self, barcode_data):
|
def init(self, barcode_data):
|
||||||
"""
|
"""Initialize the BarcodePlugin instance
|
||||||
Initialize the BarcodePlugin instance
|
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
barcode_data - The raw barcode data
|
barcode_data - The raw barcode data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.data = barcode_data
|
self.data = barcode_data
|
||||||
|
|
||||||
def getStockItem(self):
|
def getStockItem(self):
|
||||||
"""
|
"""Attempt to retrieve a StockItem associated with this barcode.
|
||||||
Attempt to retrieve a StockItem associated with this barcode.
|
|
||||||
Default implementation returns None
|
Default implementation returns None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return None # pragma: no cover
|
return None # pragma: no cover
|
||||||
|
|
||||||
def getStockItemByHash(self):
|
def getStockItemByHash(self):
|
||||||
|
"""Attempt to retrieve a StockItem associated with this barcode, based on the barcode hash.
|
||||||
"""
|
"""
|
||||||
Attempt to retrieve a StockItem associated with this barcode,
|
|
||||||
based on the barcode hash.
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
item = StockItem.objects.get(uid=self.hash())
|
item = StockItem.objects.get(uid=self.hash())
|
||||||
return item
|
return item
|
||||||
@ -84,48 +73,37 @@ class BarcodeMixin:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def renderStockItem(self, item):
|
def renderStockItem(self, item):
|
||||||
"""
|
"""Render a stock item to JSON response"""
|
||||||
Render a stock item to JSON response
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
|
serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def getStockLocation(self):
|
def getStockLocation(self):
|
||||||
"""
|
"""Attempt to retrieve a StockLocation associated with this barcode.
|
||||||
Attempt to retrieve a StockLocation associated with this barcode.
|
|
||||||
Default implementation returns None
|
Default implementation returns None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return None # pragma: no cover
|
return None # pragma: no cover
|
||||||
|
|
||||||
def renderStockLocation(self, loc):
|
def renderStockLocation(self, loc):
|
||||||
"""
|
"""Render a stock location to a JSON response"""
|
||||||
Render a stock location to a JSON response
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer = LocationSerializer(loc)
|
serializer = LocationSerializer(loc)
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def getPart(self):
|
def getPart(self):
|
||||||
"""
|
"""Attempt to retrieve a Part associated with this barcode.
|
||||||
Attempt to retrieve a Part associated with this barcode.
|
|
||||||
Default implementation returns None
|
Default implementation returns None
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return None # pragma: no cover
|
return None # pragma: no cover
|
||||||
|
|
||||||
def renderPart(self, part):
|
def renderPart(self, part):
|
||||||
"""
|
"""Render a part to JSON response"""
|
||||||
Render a part to JSON response
|
|
||||||
"""
|
|
||||||
|
|
||||||
serializer = PartSerializer(part)
|
serializer = PartSerializer(part)
|
||||||
return serializer.data
|
return serializer.data
|
||||||
|
|
||||||
def hash(self):
|
def hash(self):
|
||||||
"""
|
"""Calculate a hash for the barcode data.
|
||||||
Calculate a hash for the barcode data.
|
|
||||||
This is supposed to uniquely identify the barcode contents,
|
This is supposed to uniquely identify the barcode contents,
|
||||||
at least within the bardcode sub-type.
|
at least within the bardcode sub-type.
|
||||||
|
|
||||||
@ -134,13 +112,9 @@ class BarcodeMixin:
|
|||||||
|
|
||||||
This may be sufficient for most applications, but can obviously be overridden
|
This may be sufficient for most applications, but can obviously be overridden
|
||||||
by a subclass.
|
by a subclass.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return hash_barcode(self.data)
|
return hash_barcode(self.data)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
"""
|
"""Default implementation returns False"""
|
||||||
Default implementation returns False
|
|
||||||
"""
|
|
||||||
return False # pragma: no cover
|
return False # pragma: no cover
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
"""Unit tests for Barcode endpoints"""
|
||||||
|
|
||||||
"""
|
|
||||||
Unit tests for Barcode endpoints
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -62,10 +58,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertIsNone(data['plugin'])
|
self.assertIsNone(data['plugin'])
|
||||||
|
|
||||||
def test_find_part(self):
|
def test_find_part(self):
|
||||||
"""
|
"""Test that we can lookup a part based on ID"""
|
||||||
Test that we can lookup a part based on ID
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.scan_url,
|
self.scan_url,
|
||||||
{
|
{
|
||||||
@ -98,10 +91,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(response.data['part'], 'Part does not exist')
|
self.assertEqual(response.data['part'], 'Part does not exist')
|
||||||
|
|
||||||
def test_find_stock_item(self):
|
def test_find_stock_item(self):
|
||||||
"""
|
"""Test that we can lookup a stock item based on ID"""
|
||||||
Test that we can lookup a stock item based on ID
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.scan_url,
|
self.scan_url,
|
||||||
{
|
{
|
||||||
@ -119,7 +109,6 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def test_invalid_item(self):
|
def test_invalid_item(self):
|
||||||
"""Test response for invalid stock item"""
|
"""Test response for invalid stock item"""
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.scan_url,
|
self.scan_url,
|
||||||
{
|
{
|
||||||
@ -135,10 +124,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(response.data['stockitem'], 'Stock item does not exist')
|
self.assertEqual(response.data['stockitem'], 'Stock item does not exist')
|
||||||
|
|
||||||
def test_find_location(self):
|
def test_find_location(self):
|
||||||
"""
|
"""Test that we can lookup a stock location based on ID"""
|
||||||
Test that we can lookup a stock location based on ID
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.scan_url,
|
self.scan_url,
|
||||||
{
|
{
|
||||||
@ -156,7 +142,6 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def test_invalid_location(self):
|
def test_invalid_location(self):
|
||||||
"""Test response for an invalid location"""
|
"""Test response for an invalid location"""
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
self.scan_url,
|
self.scan_url,
|
||||||
{
|
{
|
||||||
@ -215,10 +200,7 @@ class BarcodeAPITest(InvenTreeAPITestCase):
|
|||||||
self.assertEqual(pk, item.pk)
|
self.assertEqual(pk, item.pk)
|
||||||
|
|
||||||
def test_association(self):
|
def test_association(self):
|
||||||
"""
|
"""Test that a barcode can be associated with a StockItem"""
|
||||||
Test that a barcode can be associated with a StockItem
|
|
||||||
"""
|
|
||||||
|
|
||||||
item = StockItem.objects.get(pk=522)
|
item = StockItem.objects.get(pk=522)
|
||||||
|
|
||||||
self.assertEqual(len(item.uid), 0)
|
self.assertEqual(len(item.uid), 0)
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Functions for triggering and responding to server side events"""
|
||||||
Functions for triggering and responding to server side events
|
|
||||||
"""
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@ -17,13 +15,11 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
def trigger_event(event, *args, **kwargs):
|
def trigger_event(event, *args, **kwargs):
|
||||||
"""
|
"""Trigger an event with optional arguments.
|
||||||
Trigger an event with optional arguments.
|
|
||||||
|
|
||||||
This event will be stored in the database,
|
This event will be stored in the database,
|
||||||
and the worker will respond to it later on.
|
and the worker will respond to it later on.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if not settings.PLUGINS_ENABLED:
|
if not settings.PLUGINS_ENABLED:
|
||||||
# Do nothing if plugins are not enabled
|
# Do nothing if plugins are not enabled
|
||||||
return # pragma: no cover
|
return # pragma: no cover
|
||||||
@ -44,8 +40,7 @@ def trigger_event(event, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def register_event(event, *args, **kwargs):
|
def register_event(event, *args, **kwargs):
|
||||||
"""
|
"""Register the event with any interested plugins.
|
||||||
Register the event with any interested plugins.
|
|
||||||
|
|
||||||
Note: This function is processed by the background worker,
|
Note: This function is processed by the background worker,
|
||||||
as it performs multiple database access operations.
|
as it performs multiple database access operations.
|
||||||
@ -80,14 +75,11 @@ def register_event(event, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def process_event(plugin_slug, event, *args, **kwargs):
|
def process_event(plugin_slug, event, *args, **kwargs):
|
||||||
"""
|
"""Respond to a triggered event.
|
||||||
Respond to a triggered event.
|
|
||||||
|
|
||||||
This function is run by the background worker process.
|
This function is run by the background worker process.
|
||||||
|
|
||||||
This function may queue multiple functions to be handled by the background worker.
|
This function may queue multiple functions to be handled by the background worker.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'")
|
logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'")
|
||||||
|
|
||||||
plugin = registry.plugins.get(plugin_slug, None)
|
plugin = registry.plugins.get(plugin_slug, None)
|
||||||
@ -100,11 +92,10 @@ def process_event(plugin_slug, event, *args, **kwargs):
|
|||||||
|
|
||||||
|
|
||||||
def allow_table_event(table_name):
|
def allow_table_event(table_name):
|
||||||
"""
|
"""Determine if an automatic event should be fired for a given table.
|
||||||
Determine if an automatic event should be fired for a given table.
|
|
||||||
We *do not* want events to be fired for some tables!
|
We *do not* want events to be fired for some tables!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isImportingData():
|
if isImportingData():
|
||||||
# Prevent table events during the data import process
|
# Prevent table events during the data import process
|
||||||
return False # pragma: no cover
|
return False # pragma: no cover
|
||||||
@ -143,10 +134,7 @@ def allow_table_event(table_name):
|
|||||||
|
|
||||||
@receiver(post_save)
|
@receiver(post_save)
|
||||||
def after_save(sender, instance, created, **kwargs):
|
def after_save(sender, instance, created, **kwargs):
|
||||||
"""
|
"""Trigger an event whenever a database entry is saved"""
|
||||||
Trigger an event whenever a database entry is saved
|
|
||||||
"""
|
|
||||||
|
|
||||||
table = sender.objects.model._meta.db_table
|
table = sender.objects.model._meta.db_table
|
||||||
|
|
||||||
instance_id = getattr(instance, 'id', None)
|
instance_id = getattr(instance, 'id', None)
|
||||||
@ -173,10 +161,7 @@ def after_save(sender, instance, created, **kwargs):
|
|||||||
|
|
||||||
@receiver(post_delete)
|
@receiver(post_delete)
|
||||||
def after_delete(sender, instance, **kwargs):
|
def after_delete(sender, instance, **kwargs):
|
||||||
"""
|
"""Trigger an event whenever a database entry is deleted"""
|
||||||
Trigger an event whenever a database entry is deleted
|
|
||||||
"""
|
|
||||||
|
|
||||||
table = sender.objects.model._meta.db_table
|
table = sender.objects.model._meta.db_table
|
||||||
|
|
||||||
if not allow_table_event(table):
|
if not allow_table_event(table):
|
||||||
|
@ -4,24 +4,22 @@ from plugin.helpers import MixinNotImplementedError
|
|||||||
|
|
||||||
|
|
||||||
class EventMixin:
|
class EventMixin:
|
||||||
"""
|
"""Mixin that provides support for responding to triggered events.
|
||||||
Mixin that provides support for responding to triggered events.
|
|
||||||
|
|
||||||
Implementing classes must provide a "process_event" function:
|
Implementing classes must provide a "process_event" function:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def process_event(self, event, *args, **kwargs):
|
def process_event(self, event, *args, **kwargs):
|
||||||
"""
|
"""Function to handle events
|
||||||
Function to handle events
|
|
||||||
Must be overridden by plugin
|
Must be overridden by plugin
|
||||||
"""
|
"""
|
||||||
# Default implementation does not do anything
|
# Default implementation does not do anything
|
||||||
raise MixinNotImplementedError
|
raise MixinNotImplementedError
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""
|
"""Meta options for this mixin"""
|
||||||
Meta options for this mixin
|
|
||||||
"""
|
|
||||||
MIXIN_NAME = 'Events'
|
MIXIN_NAME = 'Events'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Plugin mixin classes"""
|
||||||
Plugin mixin classes
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@ -21,9 +19,7 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
class SettingsMixin:
|
class SettingsMixin:
|
||||||
"""
|
"""Mixin that enables global settings for the plugin"""
|
||||||
Mixin that enables global settings for the plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
MIXIN_NAME = 'Settings'
|
MIXIN_NAME = 'Settings'
|
||||||
@ -35,23 +31,15 @@ class SettingsMixin:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_settings(self):
|
def has_settings(self):
|
||||||
"""
|
"""Does this plugin use custom global settings"""
|
||||||
Does this plugin use custom global settings
|
|
||||||
"""
|
|
||||||
return bool(self.settings)
|
return bool(self.settings)
|
||||||
|
|
||||||
def get_setting(self, key):
|
def get_setting(self, key):
|
||||||
"""
|
"""Return the 'value' of the setting associated with this plugin"""
|
||||||
Return the 'value' of the setting associated with this plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
return PluginSetting.get_setting(key, plugin=self)
|
return PluginSetting.get_setting(key, plugin=self)
|
||||||
|
|
||||||
def set_setting(self, key, value, user=None):
|
def set_setting(self, key, value, user=None):
|
||||||
"""
|
"""Set plugin setting value by key"""
|
||||||
Set plugin setting value by key
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plugin, _ = PluginConfig.objects.get_or_create(key=self.plugin_slug(), name=self.plugin_name())
|
plugin, _ = PluginConfig.objects.get_or_create(key=self.plugin_slug(), name=self.plugin_name())
|
||||||
except (OperationalError, ProgrammingError): # pragma: no cover
|
except (OperationalError, ProgrammingError): # pragma: no cover
|
||||||
@ -66,8 +54,7 @@ class SettingsMixin:
|
|||||||
|
|
||||||
|
|
||||||
class ScheduleMixin:
|
class ScheduleMixin:
|
||||||
"""
|
"""Mixin that provides support for scheduled tasks.
|
||||||
Mixin that provides support for scheduled tasks.
|
|
||||||
|
|
||||||
Implementing classes must provide a dict object called SCHEDULED_TASKS,
|
Implementing classes must provide a dict object called SCHEDULED_TASKS,
|
||||||
which provides information on the tasks to be scheduled.
|
which provides information on the tasks to be scheduled.
|
||||||
@ -99,9 +86,8 @@ class ScheduleMixin:
|
|||||||
SCHEDULED_TASKS = {}
|
SCHEDULED_TASKS = {}
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""
|
"""Meta options for this mixin"""
|
||||||
Meta options for this mixin
|
|
||||||
"""
|
|
||||||
MIXIN_NAME = 'Schedule'
|
MIXIN_NAME = 'Schedule'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -116,16 +102,11 @@ class ScheduleMixin:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_scheduled_tasks(self):
|
def has_scheduled_tasks(self):
|
||||||
"""
|
"""Are tasks defined for this plugin"""
|
||||||
Are tasks defined for this plugin
|
|
||||||
"""
|
|
||||||
return bool(self.scheduled_tasks)
|
return bool(self.scheduled_tasks)
|
||||||
|
|
||||||
def validate_scheduled_tasks(self):
|
def validate_scheduled_tasks(self):
|
||||||
"""
|
"""Check that the provided scheduled tasks are valid"""
|
||||||
Check that the provided scheduled tasks are valid
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.has_scheduled_tasks:
|
if not self.has_scheduled_tasks:
|
||||||
raise MixinImplementationError("SCHEDULED_TASKS not defined")
|
raise MixinImplementationError("SCHEDULED_TASKS not defined")
|
||||||
|
|
||||||
@ -147,25 +128,18 @@ class ScheduleMixin:
|
|||||||
raise MixinImplementationError(f"Task '{key}' is missing 'minutes' parameter")
|
raise MixinImplementationError(f"Task '{key}' is missing 'minutes' parameter")
|
||||||
|
|
||||||
def get_task_name(self, key):
|
def get_task_name(self, key):
|
||||||
"""
|
"""Task name for key"""
|
||||||
Task name for key
|
|
||||||
"""
|
|
||||||
# Generate a 'unique' task name
|
# Generate a 'unique' task name
|
||||||
slug = self.plugin_slug()
|
slug = self.plugin_slug()
|
||||||
return f"plugin.{slug}.{key}"
|
return f"plugin.{slug}.{key}"
|
||||||
|
|
||||||
def get_task_names(self):
|
def get_task_names(self):
|
||||||
"""
|
"""All defined task names"""
|
||||||
All defined task names
|
|
||||||
"""
|
|
||||||
# Returns a list of all task names associated with this plugin instance
|
# Returns a list of all task names associated with this plugin instance
|
||||||
return [self.get_task_name(key) for key in self.scheduled_tasks.keys()]
|
return [self.get_task_name(key) for key in self.scheduled_tasks.keys()]
|
||||||
|
|
||||||
def register_tasks(self):
|
def register_tasks(self):
|
||||||
"""
|
"""Register the tasks with the database"""
|
||||||
Register the tasks with the database
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django_q.models import Schedule
|
from django_q.models import Schedule
|
||||||
|
|
||||||
@ -182,10 +156,7 @@ class ScheduleMixin:
|
|||||||
func_name = task['func'].strip()
|
func_name = task['func'].strip()
|
||||||
|
|
||||||
if '.' in func_name:
|
if '.' in func_name:
|
||||||
"""
|
"""Dotted notation indicates that we wish to run a globally defined function, from a specified Python module."""
|
||||||
Dotted notation indicates that we wish to run a globally defined function,
|
|
||||||
from a specified Python module.
|
|
||||||
"""
|
|
||||||
|
|
||||||
Schedule.objects.create(
|
Schedule.objects.create(
|
||||||
name=task_name,
|
name=task_name,
|
||||||
@ -196,8 +167,7 @@ class ScheduleMixin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
"""
|
"""Non-dotted notation indicates that we wish to call a 'member function' of the calling plugin.
|
||||||
Non-dotted notation indicates that we wish to call a 'member function' of the calling plugin.
|
|
||||||
|
|
||||||
This is managed by the plugin registry itself.
|
This is managed by the plugin registry itself.
|
||||||
"""
|
"""
|
||||||
@ -218,10 +188,7 @@ class ScheduleMixin:
|
|||||||
logger.warning("register_tasks failed, database not ready")
|
logger.warning("register_tasks failed, database not ready")
|
||||||
|
|
||||||
def unregister_tasks(self):
|
def unregister_tasks(self):
|
||||||
"""
|
"""Deregister the tasks with the database"""
|
||||||
Deregister the tasks with the database
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from django_q.models import Schedule
|
from django_q.models import Schedule
|
||||||
|
|
||||||
@ -240,14 +207,11 @@ class ScheduleMixin:
|
|||||||
|
|
||||||
|
|
||||||
class UrlsMixin:
|
class UrlsMixin:
|
||||||
"""
|
"""Mixin that enables custom URLs for the plugin"""
|
||||||
Mixin that enables custom URLs for the plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""
|
"""Meta options for this mixin"""
|
||||||
Meta options for this mixin
|
|
||||||
"""
|
|
||||||
MIXIN_NAME = 'URLs'
|
MIXIN_NAME = 'URLs'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -256,54 +220,41 @@ class UrlsMixin:
|
|||||||
self.urls = self.setup_urls()
|
self.urls = self.setup_urls()
|
||||||
|
|
||||||
def setup_urls(self):
|
def setup_urls(self):
|
||||||
"""
|
"""Setup url endpoints for this plugin"""
|
||||||
Setup url endpoints for this plugin
|
|
||||||
"""
|
|
||||||
return getattr(self, 'URLS', None)
|
return getattr(self, 'URLS', None)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_url(self):
|
def base_url(self):
|
||||||
"""
|
"""Base url for this plugin"""
|
||||||
Base url for this plugin
|
|
||||||
"""
|
|
||||||
return f'{PLUGIN_BASE}/{self.slug}/'
|
return f'{PLUGIN_BASE}/{self.slug}/'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def internal_name(self):
|
def internal_name(self):
|
||||||
"""
|
"""Internal url pattern name"""
|
||||||
Internal url pattern name
|
|
||||||
"""
|
|
||||||
return f'plugin:{self.slug}:'
|
return f'plugin:{self.slug}:'
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def urlpatterns(self):
|
def urlpatterns(self):
|
||||||
"""
|
"""Urlpatterns for this plugin"""
|
||||||
Urlpatterns for this plugin
|
|
||||||
"""
|
|
||||||
if self.has_urls:
|
if self.has_urls:
|
||||||
return re_path(f'^{self.slug}/', include((self.urls, self.slug)), name=self.slug)
|
return re_path(f'^{self.slug}/', include((self.urls, self.slug)), name=self.slug)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_urls(self):
|
def has_urls(self):
|
||||||
"""
|
"""Does this plugin use custom urls"""
|
||||||
Does this plugin use custom urls
|
|
||||||
"""
|
|
||||||
return bool(self.urls)
|
return bool(self.urls)
|
||||||
|
|
||||||
|
|
||||||
class NavigationMixin:
|
class NavigationMixin:
|
||||||
"""
|
"""Mixin that enables custom navigation links with the plugin"""
|
||||||
Mixin that enables custom navigation links with the plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAVIGATION_TAB_NAME = None
|
NAVIGATION_TAB_NAME = None
|
||||||
NAVIGATION_TAB_ICON = "fas fa-question"
|
NAVIGATION_TAB_ICON = "fas fa-question"
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""
|
"""Meta options for this mixin"""
|
||||||
Meta options for this mixin
|
|
||||||
"""
|
|
||||||
MIXIN_NAME = 'Navigation Links'
|
MIXIN_NAME = 'Navigation Links'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -312,9 +263,7 @@ class NavigationMixin:
|
|||||||
self.navigation = self.setup_navigation()
|
self.navigation = self.setup_navigation()
|
||||||
|
|
||||||
def setup_navigation(self):
|
def setup_navigation(self):
|
||||||
"""
|
"""Setup navigation links for this plugin"""
|
||||||
Setup navigation links for this plugin
|
|
||||||
"""
|
|
||||||
nav_links = getattr(self, 'NAVIGATION', None)
|
nav_links = getattr(self, 'NAVIGATION', None)
|
||||||
if nav_links:
|
if nav_links:
|
||||||
# check if needed values are configured
|
# check if needed values are configured
|
||||||
@ -325,16 +274,12 @@ class NavigationMixin:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_naviation(self):
|
def has_naviation(self):
|
||||||
"""
|
"""Does this plugin define navigation elements"""
|
||||||
Does this plugin define navigation elements
|
|
||||||
"""
|
|
||||||
return bool(self.navigation)
|
return bool(self.navigation)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def navigation_name(self):
|
def navigation_name(self):
|
||||||
"""
|
"""Name for navigation tab"""
|
||||||
Name for navigation tab
|
|
||||||
"""
|
|
||||||
name = getattr(self, 'NAVIGATION_TAB_NAME', None)
|
name = getattr(self, 'NAVIGATION_TAB_NAME', None)
|
||||||
if not name:
|
if not name:
|
||||||
name = self.human_name
|
name = self.human_name
|
||||||
@ -342,21 +287,16 @@ class NavigationMixin:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def navigation_icon(self):
|
def navigation_icon(self):
|
||||||
"""
|
"""Icon-name for navigation tab"""
|
||||||
Icon-name for navigation tab
|
|
||||||
"""
|
|
||||||
return getattr(self, 'NAVIGATION_TAB_ICON', "fas fa-question")
|
return getattr(self, 'NAVIGATION_TAB_ICON', "fas fa-question")
|
||||||
|
|
||||||
|
|
||||||
class AppMixin:
|
class AppMixin:
|
||||||
"""
|
"""Mixin that enables full django app functions for a plugin"""
|
||||||
Mixin that enables full django app functions for a plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""m
|
"""Meta options for this mixin"""
|
||||||
Mta options for this mixin
|
|
||||||
"""
|
|
||||||
MIXIN_NAME = 'App registration'
|
MIXIN_NAME = 'App registration'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -365,15 +305,12 @@ class AppMixin:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def has_app(self):
|
def has_app(self):
|
||||||
"""
|
"""This plugin is always an app with this plugin"""
|
||||||
This plugin is always an app with this plugin
|
|
||||||
"""
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class APICallMixin:
|
class APICallMixin:
|
||||||
"""
|
"""Mixin that enables easier API calls for a plugin
|
||||||
Mixin that enables easier API calls for a plugin
|
|
||||||
|
|
||||||
Steps to set up:
|
Steps to set up:
|
||||||
1. Add this mixin before (left of) SettingsMixin and PluginBase
|
1. Add this mixin before (left of) SettingsMixin and PluginBase
|
||||||
@ -424,7 +361,7 @@ class APICallMixin:
|
|||||||
API_TOKEN = 'Bearer'
|
API_TOKEN = 'Bearer'
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
"""meta options for this mixin"""
|
"""Meta options for this mixin"""
|
||||||
MIXIN_NAME = 'API calls'
|
MIXIN_NAME = 'API calls'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -487,8 +424,7 @@ class APICallMixin:
|
|||||||
|
|
||||||
|
|
||||||
class PanelMixin:
|
class PanelMixin:
|
||||||
"""
|
"""Mixin which allows integration of custom 'panels' into a particular page.
|
||||||
Mixin which allows integration of custom 'panels' into a particular page.
|
|
||||||
|
|
||||||
The mixin provides a number of key functionalities:
|
The mixin provides a number of key functionalities:
|
||||||
|
|
||||||
@ -540,17 +476,15 @@ class PanelMixin:
|
|||||||
self.add_mixin('panel', True, __class__)
|
self.add_mixin('panel', True, __class__)
|
||||||
|
|
||||||
def get_custom_panels(self, view, request):
|
def get_custom_panels(self, view, request):
|
||||||
""" This method *must* be implemented by the plugin class """
|
"""This method *must* be implemented by the plugin class"""
|
||||||
raise MixinNotImplementedError(f"{__class__} is missing the 'get_custom_panels' method")
|
raise MixinNotImplementedError(f"{__class__} is missing the 'get_custom_panels' method")
|
||||||
|
|
||||||
def get_panel_context(self, view, request, context):
|
def get_panel_context(self, view, request, context):
|
||||||
"""
|
"""Build the context data to be used for template rendering.
|
||||||
Build the context data to be used for template rendering.
|
|
||||||
Custom class can override this to provide any custom context data.
|
Custom class can override this to provide any custom context data.
|
||||||
|
|
||||||
(See the example in "custom_panel_sample.py")
|
(See the example in "custom_panel_sample.py")
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Provide some standard context items to the template for rendering
|
# Provide some standard context items to the template for rendering
|
||||||
context['plugin'] = self
|
context['plugin'] = self
|
||||||
context['request'] = request
|
context['request'] = request
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
""" Unit tests for base mixins for plugins """
|
"""Unit tests for base mixins for plugins"""
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
@ -170,9 +170,7 @@ class APICallMixinTest(BaseMixinDefinition, TestCase):
|
|||||||
API_TOKEN_SETTING = 'API_TOKEN'
|
API_TOKEN_SETTING = 'API_TOKEN'
|
||||||
|
|
||||||
def get_external_url(self, simple: bool = True):
|
def get_external_url(self, simple: bool = True):
|
||||||
'''
|
"""Returns data from the sample endpoint"""
|
||||||
returns data from the sample endpoint
|
|
||||||
'''
|
|
||||||
return self.api_call('api/users/2', simple_response=simple)
|
return self.api_call('api/users/2', simple_response=simple)
|
||||||
self.mixin = MixinCls()
|
self.mixin = MixinCls()
|
||||||
|
|
||||||
@ -263,7 +261,6 @@ class PanelMixinTests(InvenTreeTestCase):
|
|||||||
|
|
||||||
def test_installed(self):
|
def test_installed(self):
|
||||||
"""Test that the sample panel plugin is installed"""
|
"""Test that the sample panel plugin is installed"""
|
||||||
|
|
||||||
plugins = registry.with_mixin('panel')
|
plugins = registry.with_mixin('panel')
|
||||||
|
|
||||||
self.assertTrue(len(plugins) > 0)
|
self.assertTrue(len(plugins) > 0)
|
||||||
@ -276,7 +273,6 @@ class PanelMixinTests(InvenTreeTestCase):
|
|||||||
|
|
||||||
def test_disabled(self):
|
def test_disabled(self):
|
||||||
"""Test that the panels *do not load* if the plugin is not enabled"""
|
"""Test that the panels *do not load* if the plugin is not enabled"""
|
||||||
|
|
||||||
plugin = registry.get_plugin('samplepanel')
|
plugin = registry.get_plugin('samplepanel')
|
||||||
|
|
||||||
plugin.set_setting('ENABLE_HELLO_WORLD', True)
|
plugin.set_setting('ENABLE_HELLO_WORLD', True)
|
||||||
@ -308,7 +304,6 @@ class PanelMixinTests(InvenTreeTestCase):
|
|||||||
"""
|
"""
|
||||||
Test that the panels *do* load if the plugin is enabled
|
Test that the panels *do* load if the plugin is enabled
|
||||||
"""
|
"""
|
||||||
|
|
||||||
plugin = registry.get_plugin('samplepanel')
|
plugin = registry.get_plugin('samplepanel')
|
||||||
|
|
||||||
self.assertEqual(len(registry.with_mixin('panel', active=True)), 0)
|
self.assertEqual(len(registry.with_mixin('panel', active=True)), 0)
|
||||||
@ -383,7 +378,6 @@ class PanelMixinTests(InvenTreeTestCase):
|
|||||||
|
|
||||||
def test_mixin(self):
|
def test_mixin(self):
|
||||||
"""Test that ImplementationError is raised"""
|
"""Test that ImplementationError is raised"""
|
||||||
|
|
||||||
with self.assertRaises(MixinNotImplementedError):
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
class Wrong(PanelMixin, InvenTreePlugin):
|
class Wrong(PanelMixin, InvenTreePlugin):
|
||||||
pass
|
pass
|
||||||
|
@ -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
|
||||||
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
@ -10,8 +11,7 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
def print_label(plugin_slug, label_image, label_instance=None, user=None):
|
def print_label(plugin_slug, label_image, 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.
|
||||||
|
|
||||||
@ -21,7 +21,6 @@ def print_label(plugin_slug, label_image, label_instance=None, user=None):
|
|||||||
plugin_slug: The unique slug (key) of the plugin
|
plugin_slug: The unique slug (key) of the plugin
|
||||||
label_image: A PIL.Image image object to be printed
|
label_image: A PIL.Image image object to be printed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
logger.info(f"Plugin '{plugin_slug}' is printing a label")
|
logger.info(f"Plugin '{plugin_slug}' is printing a label")
|
||||||
|
|
||||||
plugin = registry.plugins.get(plugin_slug, None)
|
plugin = registry.plugins.get(plugin_slug, None)
|
||||||
|
Reference in New Issue
Block a user