mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 20:15:44 +00:00
fix docstrings 4
This commit is contained in:
@ -4,8 +4,7 @@ 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,8 @@ 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
|
||||||
@ -23,8 +21,7 @@ class LabelPrintingMixin:
|
|||||||
self.add_mixin('labels', True, __class__)
|
self.add_mixin('labels', True, __class__)
|
||||||
|
|
||||||
def print_label(self, label, **kwargs):
|
def print_label(self, label, **kwargs):
|
||||||
"""
|
"""Callback to print a single label
|
||||||
Callback to print a single label
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
label: A black-and-white pillow Image object
|
label: A black-and-white pillow Image object
|
||||||
@ -32,8 +29,6 @@ class LabelPrintingMixin:
|
|||||||
kwargs:
|
kwargs:
|
||||||
length: The length of the label (in mm)
|
length: The length of the label (in mm)
|
||||||
width: The width of the label (in mm)
|
width: The width of the label (in mm)
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 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')
|
||||||
|
@ -28,7 +28,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
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()
|
||||||
@ -62,7 +61,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
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
|
||||||
|
|
||||||
@ -72,7 +70,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
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)
|
||||||
@ -83,7 +80,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
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)
|
||||||
@ -128,7 +124,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
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()
|
||||||
|
|
||||||
|
@ -11,9 +11,7 @@ from stock.models import StockItem, StockLocation
|
|||||||
|
|
||||||
|
|
||||||
class LocatePluginView(APIView):
|
class LocatePluginView(APIView):
|
||||||
"""
|
"""Endpoint for using a custom plugin to identify or 'locate' a stock item or location"""
|
||||||
Endpoint for using a custom plugin to identify or 'locate' a stock item or location
|
|
||||||
"""
|
|
||||||
|
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
permissions.IsAuthenticated,
|
permissions.IsAuthenticated,
|
||||||
|
@ -8,8 +8,7 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
class LocateMixin:
|
class LocateMixin:
|
||||||
"""
|
"""Mixin class which provides support for 'locating' inventory items,
|
||||||
Mixin class which provides support for 'locating' inventory items,
|
|
||||||
for example identifying the location of a particular StockLocation.
|
for example identifying the location of a particular StockLocation.
|
||||||
|
|
||||||
Plugins could implement audible or visual cues to direct attention to the location,
|
Plugins could implement audible or visual cues to direct attention to the location,
|
||||||
@ -23,7 +22,6 @@ class LocateMixin:
|
|||||||
- locate_stock_location : Used to locate / identify a particular stock location
|
- locate_stock_location : Used to locate / identify a particular stock location
|
||||||
|
|
||||||
Refer to the default method implementations below for more information!
|
Refer to the default method implementations below for more information!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class MixinMeta:
|
class MixinMeta:
|
||||||
@ -34,8 +32,7 @@ class LocateMixin:
|
|||||||
self.add_mixin('locate', True, __class__)
|
self.add_mixin('locate', True, __class__)
|
||||||
|
|
||||||
def locate_stock_item(self, item_pk):
|
def locate_stock_item(self, item_pk):
|
||||||
"""
|
"""Attempt to locate a particular StockItem
|
||||||
Attempt to locate a particular StockItem
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
item_pk: The PK (primary key) of the StockItem to be located
|
item_pk: The PK (primary key) of the StockItem to be located
|
||||||
@ -63,8 +60,7 @@ class LocateMixin:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def locate_stock_location(self, location_pk):
|
def locate_stock_location(self, location_pk):
|
||||||
"""
|
"""Attempt to location a particular StockLocation
|
||||||
Attempt to location a particular StockLocation
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
location_pk: The PK (primary key) of the StockLocation to be located
|
location_pk: The PK (primary key) of the StockLocation to be located
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Unit tests for the 'locate' plugin mixin class"""
|
||||||
Unit tests for the 'locate' plugin mixin class
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
@ -21,7 +19,6 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def test_installed(self):
|
def test_installed(self):
|
||||||
"""Test that a locate plugin is actually installed"""
|
"""Test that a locate plugin is actually installed"""
|
||||||
|
|
||||||
plugins = registry.with_mixin('locate')
|
plugins = registry.with_mixin('locate')
|
||||||
|
|
||||||
self.assertTrue(len(plugins) > 0)
|
self.assertTrue(len(plugins) > 0)
|
||||||
@ -30,7 +27,6 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def test_locate_fail(self):
|
def test_locate_fail(self):
|
||||||
"""Test various API failure modes"""
|
"""Test various API failure modes"""
|
||||||
|
|
||||||
url = reverse('api-locate-plugin')
|
url = reverse('api-locate-plugin')
|
||||||
|
|
||||||
# Post without a plugin
|
# Post without a plugin
|
||||||
@ -90,13 +86,11 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
|||||||
self.assertIn(f"StockLocation matching PK '{pk}' not found", str(response.data))
|
self.assertIn(f"StockLocation matching PK '{pk}' not found", str(response.data))
|
||||||
|
|
||||||
def test_locate_item(self):
|
def test_locate_item(self):
|
||||||
"""
|
"""Test that the plugin correctly 'locates' a StockItem
|
||||||
Test that the plugin correctly 'locates' a StockItem
|
|
||||||
|
|
||||||
As the background worker is not running during unit testing,
|
As the background worker is not running during unit testing,
|
||||||
the sample 'locate' function will be called 'inline'
|
the sample 'locate' function will be called 'inline'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url = reverse('api-locate-plugin')
|
url = reverse('api-locate-plugin')
|
||||||
|
|
||||||
item = StockItem.objects.get(pk=1)
|
item = StockItem.objects.get(pk=1)
|
||||||
@ -121,10 +115,7 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
|||||||
self.assertTrue(item.metadata['located'])
|
self.assertTrue(item.metadata['located'])
|
||||||
|
|
||||||
def test_locate_location(self):
|
def test_locate_location(self):
|
||||||
"""
|
"""Test that the plugin correctly 'locates' a StockLocation"""
|
||||||
Test that the plugin correctly 'locates' a StockLocation
|
|
||||||
"""
|
|
||||||
|
|
||||||
url = reverse('api-locate-plugin')
|
url = reverse('api-locate-plugin')
|
||||||
|
|
||||||
for location in StockLocation.objects.all():
|
for location in StockLocation.objects.all():
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
"""Sample implementation for ActionMixin"""
|
||||||
"""sample implementation for ActionMixin"""
|
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.mixins import ActionMixin
|
from plugin.mixins import ActionMixin
|
||||||
|
|
||||||
|
|
||||||
class SimpleActionPlugin(ActionMixin, InvenTreePlugin):
|
class SimpleActionPlugin(ActionMixin, InvenTreePlugin):
|
||||||
"""
|
"""An EXTREMELY simple action plugin which demonstrates the capability of the ActionMixin class"""
|
||||||
An EXTREMELY simple action plugin which demonstrates
|
|
||||||
the capability of the ActionMixin class
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "SimpleActionPlugin"
|
NAME = "SimpleActionPlugin"
|
||||||
ACTION_NAME = "simple"
|
ACTION_NAME = "simple"
|
||||||
|
@ -13,12 +13,12 @@ class SimpleActionPluginTests(InvenTreeTestCase):
|
|||||||
self.plugin = SimpleActionPlugin()
|
self.plugin = SimpleActionPlugin()
|
||||||
|
|
||||||
def test_name(self):
|
def test_name(self):
|
||||||
"""check plugn names """
|
"""Check plugn names"""
|
||||||
self.assertEqual(self.plugin.plugin_name(), "SimpleActionPlugin")
|
self.assertEqual(self.plugin.plugin_name(), "SimpleActionPlugin")
|
||||||
self.assertEqual(self.plugin.action_name(), "simple")
|
self.assertEqual(self.plugin.action_name(), "simple")
|
||||||
|
|
||||||
def test_function(self):
|
def test_function(self):
|
||||||
"""check if functions work """
|
"""Check if functions work"""
|
||||||
# test functions
|
# test functions
|
||||||
response = self.client.post('/api/action/', data={'action': "simple", 'data': {'foo': "bar", }})
|
response = self.client.post('/api/action/', data={'action': "simple", 'data': {'foo': "bar", }})
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Unit tests for InvenTreeBarcodePlugin"""
|
"""Unit tests for InvenTreeBarcodePlugin"""
|
||||||
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -18,9 +17,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def test_errors(self):
|
def test_errors(self):
|
||||||
"""
|
"""Test all possible error cases for assigment action"""
|
||||||
Test all possible error cases for assigment action
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_assert_error(barcode_data):
|
def test_assert_error(barcode_data):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -46,10 +43,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
|||||||
test_assert_error('{"blbla": 10004}')
|
test_assert_error('{"blbla": 10004}')
|
||||||
|
|
||||||
def test_scan(self):
|
def test_scan(self):
|
||||||
"""
|
"""Test that a barcode can be scanned"""
|
||||||
Test that a barcode can be scanned
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = self.client.post(reverse('api-barcode-scan'), format='json', data={'barcode': 'blbla=10004'})
|
response = self.client.post(reverse('api-barcode-scan'), format='json', data={'barcode': 'blbla=10004'})
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertIn('success', response.data)
|
self.assertIn('success', response.data)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""Core set of Notifications as a Plugin"""
|
"""Core set of Notifications as a Plugin"""
|
||||||
|
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
@ -16,9 +16,7 @@ class PlgMixin:
|
|||||||
|
|
||||||
|
|
||||||
class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
||||||
"""
|
"""Core notification methods for InvenTree"""
|
||||||
Core notification methods for InvenTree
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "CoreNotificationsPlugin"
|
NAME = "CoreNotificationsPlugin"
|
||||||
AUTHOR = _('InvenTree contributors')
|
AUTHOR = _('InvenTree contributors')
|
||||||
@ -50,11 +48,7 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def get_targets(self):
|
def get_targets(self):
|
||||||
"""
|
"""Return a list of target email addresses, only for users which allow email notifications"""
|
||||||
Return a list of target email addresses,
|
|
||||||
only for users which allow email notifications
|
|
||||||
"""
|
|
||||||
|
|
||||||
allowed_users = []
|
allowed_users = []
|
||||||
|
|
||||||
for user in self.targets:
|
for user in self.targets:
|
||||||
|
@ -8,10 +8,7 @@ from plugin.models import NotificationUserSetting
|
|||||||
class CoreNotificationTestTests(BaseNotificationIntegrationTest):
|
class CoreNotificationTestTests(BaseNotificationIntegrationTest):
|
||||||
|
|
||||||
def test_email(self):
|
def test_email(self):
|
||||||
"""
|
"""Ensure that the email notifications run"""
|
||||||
Ensure that the email notifications run
|
|
||||||
"""
|
|
||||||
|
|
||||||
# enable plugin and set mail setting to true
|
# enable plugin and set mail setting to true
|
||||||
plugin = registry.plugins.get('corenotificationsplugin')
|
plugin = registry.plugins.get('corenotificationsplugin')
|
||||||
plugin.set_setting('ENABLE_NOTIFICATION_EMAILS', True)
|
plugin.set_setting('ENABLE_NOTIFICATION_EMAILS', True)
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Utility class to enable simpler imports"""
|
||||||
Utility class to enable simpler imports
|
|
||||||
"""
|
|
||||||
|
|
||||||
from common.notifications import (BulkNotificationMethod,
|
from common.notifications import (BulkNotificationMethod,
|
||||||
SingleNotificationMethod)
|
SingleNotificationMethod)
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Sample plugin which responds to events"""
|
||||||
Sample plugin which responds to events
|
|
||||||
"""
|
|
||||||
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
@ -11,9 +9,7 @@ from plugin.mixins import EventMixin
|
|||||||
|
|
||||||
|
|
||||||
class EventPluginSample(EventMixin, InvenTreePlugin):
|
class EventPluginSample(EventMixin, InvenTreePlugin):
|
||||||
"""
|
"""A sample plugin which provides supports for triggered events"""
|
||||||
A sample plugin which provides supports for triggered events
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "EventPlugin"
|
NAME = "EventPlugin"
|
||||||
SLUG = "sampleevent"
|
SLUG = "sampleevent"
|
||||||
|
@ -31,7 +31,6 @@ class EventPluginSampleTests(TestCase):
|
|||||||
|
|
||||||
def test_mixin(self):
|
def test_mixin(self):
|
||||||
"""Test that MixinNotImplementedError is raised"""
|
"""Test that MixinNotImplementedError is raised"""
|
||||||
|
|
||||||
with self.assertRaises(MixinNotImplementedError):
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
class Wrong(EventMixin, InvenTreePlugin):
|
class Wrong(EventMixin, InvenTreePlugin):
|
||||||
pass
|
pass
|
||||||
|
@ -1,19 +1,15 @@
|
|||||||
"""sample implementation for IntegrationPlugin"""
|
"""Sample implementation for IntegrationPlugin"""
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.mixins import UrlsMixin
|
from plugin.mixins import UrlsMixin
|
||||||
|
|
||||||
|
|
||||||
class NoIntegrationPlugin(InvenTreePlugin):
|
class NoIntegrationPlugin(InvenTreePlugin):
|
||||||
"""
|
"""A basic plugin"""
|
||||||
An basic plugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "NoIntegrationPlugin"
|
NAME = "NoIntegrationPlugin"
|
||||||
|
|
||||||
|
|
||||||
class WrongIntegrationPlugin(UrlsMixin, InvenTreePlugin):
|
class WrongIntegrationPlugin(UrlsMixin, InvenTreePlugin):
|
||||||
"""
|
"""A basic wron plugin with urls"""
|
||||||
An basic wron plugin with urls
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "WrongIntegrationPlugin"
|
NAME = "WrongIntegrationPlugin"
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
"""
|
"""Sample plugin for calling an external API"""
|
||||||
Sample plugin for calling an external API
|
|
||||||
"""
|
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.mixins import APICallMixin, SettingsMixin
|
from plugin.mixins import APICallMixin, SettingsMixin
|
||||||
|
|
||||||
|
|
||||||
class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
|
class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
|
||||||
"""
|
"""A small api call sample"""
|
||||||
A small api call sample
|
|
||||||
"""
|
|
||||||
NAME = "Sample API Caller"
|
NAME = "Sample API Caller"
|
||||||
|
|
||||||
SETTINGS = {
|
SETTINGS = {
|
||||||
@ -26,7 +23,5 @@ class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
API_TOKEN_SETTING = 'API_TOKEN'
|
API_TOKEN_SETTING = 'API_TOKEN'
|
||||||
|
|
||||||
def get_external_url(self):
|
def get_external_url(self):
|
||||||
"""
|
"""Returns data from the sample endpoint"""
|
||||||
returns data from the sample endpoint
|
|
||||||
"""
|
|
||||||
return self.api_call('api/users/2')
|
return self.api_call('api/users/2')
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
"""sample of a broken plugin"""
|
"""Sample of a broken plugin"""
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
|
|
||||||
|
|
||||||
class BrokenIntegrationPlugin(InvenTreePlugin):
|
class BrokenIntegrationPlugin(InvenTreePlugin):
|
||||||
"""
|
"""A very broken plugin"""
|
||||||
An very broken plugin
|
|
||||||
"""
|
|
||||||
NAME = 'Test'
|
NAME = 'Test'
|
||||||
TITLE = 'Broken Plugin'
|
TITLE = 'Broken Plugin'
|
||||||
SLUG = 'broken'
|
SLUG = 'broken'
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Sample plugin which renders custom panels on certain pages"""
|
||||||
Sample plugin which renders custom panels on certain pages
|
|
||||||
"""
|
|
||||||
|
|
||||||
from part.views import PartDetail
|
from part.views import PartDetail
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
@ -9,9 +7,7 @@ from stock.views import StockLocationDetail
|
|||||||
|
|
||||||
|
|
||||||
class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
|
class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
|
||||||
"""
|
"""A sample plugin which renders some custom panels."""
|
||||||
A sample plugin which renders some custom panels.
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "CustomPanelExample"
|
NAME = "CustomPanelExample"
|
||||||
SLUG = "samplepanel"
|
SLUG = "samplepanel"
|
||||||
@ -45,9 +41,7 @@ class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def get_custom_panels(self, view, request):
|
def get_custom_panels(self, view, request):
|
||||||
|
"""You can decide at run-time which custom panels you want to display!
|
||||||
"""
|
|
||||||
You can decide at run-time which custom panels you want to display!
|
|
||||||
|
|
||||||
- Display on every page
|
- Display on every page
|
||||||
- Only on a single page or set of pages
|
- Only on a single page or set of pages
|
||||||
|
@ -4,9 +4,7 @@ from plugin.mixins import LabelPrintingMixin
|
|||||||
|
|
||||||
|
|
||||||
class SampleLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
|
class SampleLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
|
||||||
"""
|
"""Sample plugin which provides a 'fake' label printer endpoint"""
|
||||||
Sample plugin which provides a 'fake' label printer endpoint
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "Label Printer"
|
NAME = "Label Printer"
|
||||||
SLUG = "samplelabel"
|
SLUG = "samplelabel"
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Sample implementations for IntegrationPlugin"""
|
||||||
Sample implementations for IntegrationPlugin
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.urls import include, re_path
|
from django.urls import include, re_path
|
||||||
@ -11,9 +9,7 @@ from plugin.mixins import AppMixin, NavigationMixin, SettingsMixin, UrlsMixin
|
|||||||
|
|
||||||
|
|
||||||
class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixin, InvenTreePlugin):
|
class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixin, InvenTreePlugin):
|
||||||
"""
|
"""A full plugin example"""
|
||||||
A full plugin example
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "SampleIntegrationPlugin"
|
NAME = "SampleIntegrationPlugin"
|
||||||
SLUG = "sample"
|
SLUG = "sample"
|
||||||
@ -23,7 +19,7 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi
|
|||||||
NAVIGATION_TAB_ICON = 'fas fa-plus'
|
NAVIGATION_TAB_ICON = 'fas fa-plus'
|
||||||
|
|
||||||
def view_test(self, request):
|
def view_test(self, request):
|
||||||
"""very basic view"""
|
"""Very basic view"""
|
||||||
return HttpResponse(f'Hi there {request.user.username} this works')
|
return HttpResponse(f'Hi there {request.user.username} this works')
|
||||||
|
|
||||||
def setup_urls(self):
|
def setup_urls(self):
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Sample plugin which supports task scheduling"""
|
||||||
Sample plugin which supports task scheduling
|
|
||||||
"""
|
|
||||||
|
|
||||||
from plugin import InvenTreePlugin
|
from plugin import InvenTreePlugin
|
||||||
from plugin.mixins import ScheduleMixin, SettingsMixin
|
from plugin.mixins import ScheduleMixin, SettingsMixin
|
||||||
@ -16,9 +14,7 @@ def print_world():
|
|||||||
|
|
||||||
|
|
||||||
class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
|
class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
|
||||||
"""
|
"""A sample plugin which provides support for scheduled tasks"""
|
||||||
A sample plugin which provides support for scheduled tasks
|
|
||||||
"""
|
|
||||||
|
|
||||||
NAME = "ScheduledTasksPlugin"
|
NAME = "ScheduledTasksPlugin"
|
||||||
SLUG = "schedule"
|
SLUG = "schedule"
|
||||||
@ -51,10 +47,7 @@ class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def member_func(self, *args, **kwargs):
|
def member_func(self, *args, **kwargs):
|
||||||
"""
|
"""A simple member function to demonstrate functionality"""
|
||||||
A simple member function to demonstrate functionality
|
|
||||||
"""
|
|
||||||
|
|
||||||
t_or_f = self.get_setting('T_OR_F')
|
t_or_f = self.get_setting('T_OR_F')
|
||||||
|
|
||||||
print(f"Called member_func - value is {t_or_f}")
|
print(f"Called member_func - value is {t_or_f}")
|
||||||
|
@ -9,7 +9,7 @@ class SampleApiCallerPluginTests(TestCase):
|
|||||||
"""Tests for SampleApiCallerPluginTests """
|
"""Tests for SampleApiCallerPluginTests """
|
||||||
|
|
||||||
def test_return(self):
|
def test_return(self):
|
||||||
"""check if the external api call works"""
|
"""Check if the external api call works"""
|
||||||
# The plugin should be defined
|
# The plugin should be defined
|
||||||
self.assertIn('sample-api-caller', registry.plugins)
|
self.assertIn('sample-api-caller', registry.plugins)
|
||||||
plg = registry.plugins['sample-api-caller']
|
plg = registry.plugins['sample-api-caller']
|
||||||
|
@ -7,7 +7,7 @@ class SampleIntegrationPluginTests(InvenTreeTestCase):
|
|||||||
"""Tests for SampleIntegrationPlugin"""
|
"""Tests for SampleIntegrationPlugin"""
|
||||||
|
|
||||||
def test_view(self):
|
def test_view(self):
|
||||||
"""check the function of the custom sample plugin """
|
"""Check the function of the custom sample plugin"""
|
||||||
response = self.client.get('/plugin/sample/ho/he/')
|
response = self.client.get('/plugin/sample/ho/he/')
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertEqual(response.content, b'Hi there testuser this works')
|
self.assertEqual(response.content, b'Hi there testuser this works')
|
||||||
|
@ -12,7 +12,7 @@ class ExampleScheduledTaskPluginTests(TestCase):
|
|||||||
"""Tests for provided ScheduledTaskPlugin"""
|
"""Tests for provided ScheduledTaskPlugin"""
|
||||||
|
|
||||||
def test_function(self):
|
def test_function(self):
|
||||||
"""check if the scheduling works"""
|
"""Check if the scheduling works"""
|
||||||
# The plugin should be defined
|
# The plugin should be defined
|
||||||
self.assertIn('schedule', registry.plugins)
|
self.assertIn('schedule', registry.plugins)
|
||||||
plg = registry.plugins['schedule']
|
plg = registry.plugins['schedule']
|
||||||
@ -44,7 +44,7 @@ class ExampleScheduledTaskPluginTests(TestCase):
|
|||||||
self.assertEqual(len(scheduled_plugin_tasks), 0)
|
self.assertEqual(len(scheduled_plugin_tasks), 0)
|
||||||
|
|
||||||
def test_calling(self):
|
def test_calling(self):
|
||||||
"""check if a function can be called without errors"""
|
"""Check if a function can be called without errors"""
|
||||||
# Check with right parameters
|
# Check with right parameters
|
||||||
self.assertEqual(call_function('schedule', 'member_func'), False)
|
self.assertEqual(call_function('schedule', 'member_func'), False)
|
||||||
|
|
||||||
@ -68,8 +68,7 @@ class ScheduledTaskPluginTests(TestCase):
|
|||||||
NoSchedules()
|
NoSchedules()
|
||||||
|
|
||||||
class WrongFuncSchedules(Base):
|
class WrongFuncSchedules(Base):
|
||||||
"""
|
"""Plugin with broken functions
|
||||||
Plugin with broken functions
|
|
||||||
|
|
||||||
This plugin is missing a func
|
This plugin is missing a func
|
||||||
"""
|
"""
|
||||||
@ -88,8 +87,7 @@ class ScheduledTaskPluginTests(TestCase):
|
|||||||
WrongFuncSchedules()
|
WrongFuncSchedules()
|
||||||
|
|
||||||
class WrongFuncSchedules1(WrongFuncSchedules):
|
class WrongFuncSchedules1(WrongFuncSchedules):
|
||||||
"""
|
"""Plugin with broken functions
|
||||||
Plugin with broken functions
|
|
||||||
|
|
||||||
This plugin is missing a schedule
|
This plugin is missing a schedule
|
||||||
"""
|
"""
|
||||||
@ -105,8 +103,7 @@ class ScheduledTaskPluginTests(TestCase):
|
|||||||
WrongFuncSchedules1()
|
WrongFuncSchedules1()
|
||||||
|
|
||||||
class WrongFuncSchedules2(WrongFuncSchedules):
|
class WrongFuncSchedules2(WrongFuncSchedules):
|
||||||
"""
|
"""Plugin with broken functions
|
||||||
Plugin with broken functions
|
|
||||||
|
|
||||||
This plugin is missing a schedule
|
This plugin is missing a schedule
|
||||||
"""
|
"""
|
||||||
@ -122,8 +119,7 @@ class ScheduledTaskPluginTests(TestCase):
|
|||||||
WrongFuncSchedules2()
|
WrongFuncSchedules2()
|
||||||
|
|
||||||
class WrongFuncSchedules3(WrongFuncSchedules):
|
class WrongFuncSchedules3(WrongFuncSchedules):
|
||||||
"""
|
"""Plugin with broken functions
|
||||||
Plugin with broken functions
|
|
||||||
|
|
||||||
This plugin has a broken schedule
|
This plugin has a broken schedule
|
||||||
"""
|
"""
|
||||||
@ -140,8 +136,7 @@ class ScheduledTaskPluginTests(TestCase):
|
|||||||
WrongFuncSchedules3()
|
WrongFuncSchedules3()
|
||||||
|
|
||||||
class WrongFuncSchedules4(WrongFuncSchedules):
|
class WrongFuncSchedules4(WrongFuncSchedules):
|
||||||
"""
|
"""Plugin with broken functions
|
||||||
Plugin with broken functions
|
|
||||||
|
|
||||||
This plugin is missing a minute marker for its schedule
|
This plugin is missing a minute marker for its schedule
|
||||||
"""
|
"""
|
||||||
|
@ -13,8 +13,8 @@ logger = logging.getLogger('inventree')
|
|||||||
|
|
||||||
|
|
||||||
class SampleLocatePlugin(LocateMixin, InvenTreePlugin):
|
class SampleLocatePlugin(LocateMixin, InvenTreePlugin):
|
||||||
"""
|
"""A very simple example of the 'locate' plugin.
|
||||||
A very simple example of the 'locate' plugin.
|
|
||||||
This plugin class simply prints location information to the logger.
|
This plugin class simply prints location information to the logger.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -51,7 +51,6 @@ class SampleLocatePlugintests(InvenTreeAPITestCase):
|
|||||||
|
|
||||||
def test_mixin(self):
|
def test_mixin(self):
|
||||||
"""Test that MixinNotImplementedError is raised"""
|
"""Test that MixinNotImplementedError is raised"""
|
||||||
|
|
||||||
with self.assertRaises(MixinNotImplementedError):
|
with self.assertRaises(MixinNotImplementedError):
|
||||||
class Wrong(LocateMixin, InvenTreePlugin):
|
class Wrong(LocateMixin, InvenTreePlugin):
|
||||||
pass
|
pass
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
"""This module provides template tags for handeling plugins"""
|
||||||
|
|
||||||
""" This module provides template tags for handeling plugins
|
|
||||||
"""
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.conf import settings as djangosettings
|
from django.conf import settings as djangosettings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
@ -15,49 +13,37 @@ register = template.Library()
|
|||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def plugin_list(*args, **kwargs):
|
def plugin_list(*args, **kwargs):
|
||||||
"""
|
"""List of all installed plugins"""
|
||||||
List of all installed plugins
|
|
||||||
"""
|
|
||||||
return registry.plugins
|
return registry.plugins
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def inactive_plugin_list(*args, **kwargs):
|
def inactive_plugin_list(*args, **kwargs):
|
||||||
"""
|
"""List of all inactive plugins"""
|
||||||
List of all inactive plugins
|
|
||||||
"""
|
|
||||||
return registry.plugins_inactive
|
return registry.plugins_inactive
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def plugin_settings(plugin, *args, **kwargs):
|
def plugin_settings(plugin, *args, **kwargs):
|
||||||
"""
|
"""List of all settings for the plugin"""
|
||||||
List of all settings for the plugin
|
|
||||||
"""
|
|
||||||
return registry.mixins_settings.get(plugin)
|
return registry.mixins_settings.get(plugin)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def mixin_enabled(plugin, key, *args, **kwargs):
|
def mixin_enabled(plugin, key, *args, **kwargs):
|
||||||
"""
|
"""Is the mixin registerd and configured in the plugin?"""
|
||||||
Is the mixin registerd and configured in the plugin?
|
|
||||||
"""
|
|
||||||
return plugin.mixin_enabled(key)
|
return plugin.mixin_enabled(key)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def mixin_available(mixin, *args, **kwargs):
|
def mixin_available(mixin, *args, **kwargs):
|
||||||
"""
|
"""Returns True if there is at least one active plugin which supports the provided mixin"""
|
||||||
Returns True if there is at least one active plugin which supports the provided mixin
|
|
||||||
"""
|
|
||||||
return len(registry.with_mixin(mixin)) > 0
|
return len(registry.with_mixin(mixin)) > 0
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def navigation_enabled(*args, **kwargs):
|
def navigation_enabled(*args, **kwargs):
|
||||||
"""
|
"""Is plugin navigation enabled?"""
|
||||||
Is plugin navigation enabled?
|
|
||||||
"""
|
|
||||||
if djangosettings.PLUGIN_TESTING:
|
if djangosettings.PLUGIN_TESTING:
|
||||||
return True
|
return True
|
||||||
return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
|
return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
|
||||||
@ -65,8 +51,8 @@ def navigation_enabled(*args, **kwargs):
|
|||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def safe_url(view_name, *args, **kwargs):
|
def safe_url(view_name, *args, **kwargs):
|
||||||
"""
|
"""Safe lookup fnc for URLs
|
||||||
Safe lookup fnc for URLs
|
|
||||||
Returns None if not found
|
Returns None if not found
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
@ -77,15 +63,11 @@ def safe_url(view_name, *args, **kwargs):
|
|||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def plugin_errors(*args, **kwargs):
|
def plugin_errors(*args, **kwargs):
|
||||||
"""
|
"""All plugin errors in the current session"""
|
||||||
All plugin errors in the current session
|
|
||||||
"""
|
|
||||||
return registry.errors
|
return registry.errors
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag(takes_context=True)
|
@register.simple_tag(takes_context=True)
|
||||||
def notification_settings_list(context, *args, **kwargs):
|
def notification_settings_list(context, *args, **kwargs):
|
||||||
"""
|
"""List of all user notification settings"""
|
||||||
List of all user notification settings
|
|
||||||
"""
|
|
||||||
return storage.get_usersettings(user=context.get('user', None))
|
return storage.get_usersettings(user=context.get('user', None))
|
||||||
|
@ -24,9 +24,7 @@ from .serializers import (BOMReportSerializer, BuildReportSerializer,
|
|||||||
|
|
||||||
|
|
||||||
class ReportListView(generics.ListAPIView):
|
class ReportListView(generics.ListAPIView):
|
||||||
"""
|
"""Generic API class for report templates"""
|
||||||
Generic API class for report templates
|
|
||||||
"""
|
|
||||||
|
|
||||||
filter_backends = [
|
filter_backends = [
|
||||||
DjangoFilterBackend,
|
DjangoFilterBackend,
|
||||||
@ -44,15 +42,10 @@ class ReportListView(generics.ListAPIView):
|
|||||||
|
|
||||||
|
|
||||||
class StockItemReportMixin:
|
class StockItemReportMixin:
|
||||||
"""
|
"""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
|
||||||
@ -77,15 +70,10 @@ class StockItemReportMixin:
|
|||||||
|
|
||||||
|
|
||||||
class BuildReportMixin:
|
class BuildReportMixin:
|
||||||
"""
|
"""Mixin for extracting Build items from query params"""
|
||||||
Mixin for extracting Build items from query params
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_builds(self):
|
def get_builds(self):
|
||||||
"""
|
"""Return a list of requested Build objects"""
|
||||||
Return a list of requested Build objects
|
|
||||||
"""
|
|
||||||
|
|
||||||
builds = []
|
builds = []
|
||||||
|
|
||||||
params = self.request.query_params
|
params = self.request.query_params
|
||||||
@ -109,17 +97,13 @@ class BuildReportMixin:
|
|||||||
|
|
||||||
|
|
||||||
class OrderReportMixin:
|
class OrderReportMixin:
|
||||||
"""
|
"""Mixin for extracting order items from query params
|
||||||
Mixin for extracting order items from query params
|
|
||||||
|
|
||||||
requires the OrderModel class attribute to be set!
|
requires the OrderModel class attribute to be set!
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_orders(self):
|
def get_orders(self):
|
||||||
"""
|
"""Return a list of order objects"""
|
||||||
Return a list of order objects
|
|
||||||
"""
|
|
||||||
|
|
||||||
orders = []
|
orders = []
|
||||||
|
|
||||||
params = self.request.query_params
|
params = self.request.query_params
|
||||||
@ -143,15 +127,10 @@ class OrderReportMixin:
|
|||||||
|
|
||||||
|
|
||||||
class PartReportMixin:
|
class PartReportMixin:
|
||||||
"""
|
"""Mixin for extracting part items from query params"""
|
||||||
Mixin for extracting part items from query params
|
|
||||||
"""
|
|
||||||
|
|
||||||
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
|
||||||
@ -176,15 +155,10 @@ class PartReportMixin:
|
|||||||
|
|
||||||
|
|
||||||
class ReportPrintMixin:
|
class ReportPrintMixin:
|
||||||
"""
|
"""Mixin for printing reports"""
|
||||||
Mixin for printing reports
|
|
||||||
"""
|
|
||||||
|
|
||||||
def print(self, request, items_to_print):
|
def print(self, request, items_to_print):
|
||||||
"""
|
"""Print this report template against a number of pre-validated items."""
|
||||||
Print this report template against a number of pre-validated items.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if len(items_to_print) == 0:
|
if len(items_to_print) == 0:
|
||||||
# No valid items provided, return an error message
|
# No valid items provided, return an error message
|
||||||
data = {
|
data = {
|
||||||
@ -283,8 +257,7 @@ class ReportPrintMixin:
|
|||||||
|
|
||||||
|
|
||||||
class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
||||||
"""
|
"""API endpoint for viewing list of TestReport objects.
|
||||||
API endpoint for viewing list of TestReport objects.
|
|
||||||
|
|
||||||
Filterable by:
|
Filterable by:
|
||||||
|
|
||||||
@ -347,35 +320,27 @@ class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
|||||||
|
|
||||||
|
|
||||||
class StockItemTestReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class StockItemTestReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""API endpoint for a single TestReport object"""
|
||||||
API endpoint for a single TestReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = TestReport.objects.all()
|
queryset = TestReport.objects.all()
|
||||||
serializer_class = TestReportSerializer
|
serializer_class = TestReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class StockItemTestReportPrint(generics.RetrieveAPIView, StockItemReportMixin, ReportPrintMixin):
|
class StockItemTestReportPrint(generics.RetrieveAPIView, StockItemReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""API endpoint for printing a TestReport object"""
|
||||||
API endpoint for printing a TestReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = TestReport.objects.all()
|
queryset = TestReport.objects.all()
|
||||||
serializer_class = TestReportSerializer
|
serializer_class = TestReportSerializer
|
||||||
|
|
||||||
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 BOMReportList(ReportListView, PartReportMixin):
|
class BOMReportList(ReportListView, PartReportMixin):
|
||||||
"""
|
"""API endpoint for viewing a list of BillOfMaterialReport objects.
|
||||||
API endpoint for viewing a list of BillOfMaterialReport objects.
|
|
||||||
|
|
||||||
Filterably by:
|
Filterably by:
|
||||||
|
|
||||||
@ -436,35 +401,27 @@ class BOMReportList(ReportListView, PartReportMixin):
|
|||||||
|
|
||||||
|
|
||||||
class BOMReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class BOMReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""API endpoint for a single BillOfMaterialReport object"""
|
||||||
API endpoint for a single BillOfMaterialReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = BillOfMaterialsReport.objects.all()
|
queryset = BillOfMaterialsReport.objects.all()
|
||||||
serializer_class = BOMReportSerializer
|
serializer_class = BOMReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class BOMReportPrint(generics.RetrieveAPIView, PartReportMixin, ReportPrintMixin):
|
class BOMReportPrint(generics.RetrieveAPIView, PartReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""API endpoint for printing a BillOfMaterialReport object"""
|
||||||
API endpoint for printing a BillOfMaterialReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = BillOfMaterialsReport.objects.all()
|
queryset = BillOfMaterialsReport.objects.all()
|
||||||
serializer_class = BOMReportSerializer
|
serializer_class = BOMReportSerializer
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""
|
"""Check if valid part item(s) have been provided"""
|
||||||
Check if valid part item(s) have been provided
|
|
||||||
"""
|
|
||||||
|
|
||||||
parts = self.get_parts()
|
parts = self.get_parts()
|
||||||
|
|
||||||
return self.print(request, parts)
|
return self.print(request, parts)
|
||||||
|
|
||||||
|
|
||||||
class BuildReportList(ReportListView, BuildReportMixin):
|
class BuildReportList(ReportListView, BuildReportMixin):
|
||||||
"""
|
"""API endpoint for viewing a list of BuildReport objects.
|
||||||
API endpoint for viewing a list of BuildReport objects.
|
|
||||||
|
|
||||||
Can be filtered by:
|
Can be filtered by:
|
||||||
|
|
||||||
@ -526,18 +483,14 @@ class BuildReportList(ReportListView, BuildReportMixin):
|
|||||||
|
|
||||||
|
|
||||||
class BuildReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class BuildReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""API endpoint for a single BuildReport object"""
|
||||||
API endpoint for a single BuildReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = BuildReport.objects.all()
|
queryset = BuildReport.objects.all()
|
||||||
serializer_class = BuildReportSerializer
|
serializer_class = BuildReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMixin):
|
class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""API endpoint for printing a BuildReport"""
|
||||||
API endpoint for printing a BuildReport
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = BuildReport.objects.all()
|
queryset = BuildReport.objects.all()
|
||||||
serializer_class = BuildReportSerializer
|
serializer_class = BuildReportSerializer
|
||||||
@ -607,18 +560,14 @@ class PurchaseOrderReportList(ReportListView, OrderReportMixin):
|
|||||||
|
|
||||||
|
|
||||||
class PurchaseOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class PurchaseOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""API endpoint for a single PurchaseOrderReport object"""
|
||||||
API endpoint for a single PurchaseOrderReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = PurchaseOrderReport.objects.all()
|
queryset = PurchaseOrderReport.objects.all()
|
||||||
serializer_class = PurchaseOrderReportSerializer
|
serializer_class = PurchaseOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class PurchaseOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
class PurchaseOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""API endpoint for printing a PurchaseOrderReport object"""
|
||||||
API endpoint for printing a PurchaseOrderReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
OrderModel = order.models.PurchaseOrder
|
OrderModel = order.models.PurchaseOrder
|
||||||
|
|
||||||
@ -690,18 +639,14 @@ class SalesOrderReportList(ReportListView, OrderReportMixin):
|
|||||||
|
|
||||||
|
|
||||||
class SalesOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
class SalesOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||||
"""
|
"""API endpoint for a single SalesOrderReport object"""
|
||||||
API endpoint for a single SalesOrderReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
queryset = SalesOrderReport.objects.all()
|
queryset = SalesOrderReport.objects.all()
|
||||||
serializer_class = SalesOrderReportSerializer
|
serializer_class = SalesOrderReportSerializer
|
||||||
|
|
||||||
|
|
||||||
class SalesOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
class SalesOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||||
"""
|
"""API endpoint for printing a PurchaseOrderReport object"""
|
||||||
API endpoint for printing a PurchaseOrderReport object
|
|
||||||
"""
|
|
||||||
|
|
||||||
OrderModel = order.models.SalesOrder
|
OrderModel = order.models.SalesOrder
|
||||||
|
|
||||||
|
@ -14,19 +14,13 @@ class ReportConfig(AppConfig):
|
|||||||
name = 'report'
|
name = 'report'
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
"""
|
"""This function is called whenever the report app is loaded"""
|
||||||
This function is called whenever the report app is loaded
|
|
||||||
"""
|
|
||||||
|
|
||||||
if canAppAccessDatabase(allow_test=True):
|
if canAppAccessDatabase(allow_test=True):
|
||||||
self.create_default_test_reports()
|
self.create_default_test_reports()
|
||||||
self.create_default_build_reports()
|
self.create_default_build_reports()
|
||||||
|
|
||||||
def create_default_reports(self, model, reports):
|
def create_default_reports(self, model, reports):
|
||||||
"""
|
"""Copy defualt report files across to the media directory."""
|
||||||
Copy defualt report files across to the media directory.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Source directory for report templates
|
# Source directory for report templates
|
||||||
src_dir = os.path.join(
|
src_dir = os.path.join(
|
||||||
os.path.dirname(os.path.realpath(__file__)),
|
os.path.dirname(os.path.realpath(__file__)),
|
||||||
@ -82,11 +76,7 @@ class ReportConfig(AppConfig):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def create_default_test_reports(self):
|
def create_default_test_reports(self):
|
||||||
"""
|
"""Create database entries for the default TestReport templates, if they do not already exist"""
|
||||||
Create database entries for the default TestReport templates,
|
|
||||||
if they do not already exist
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .models import TestReport
|
from .models import TestReport
|
||||||
except: # pragma: no cover
|
except: # pragma: no cover
|
||||||
@ -105,11 +95,7 @@ class ReportConfig(AppConfig):
|
|||||||
self.create_default_reports(TestReport, reports)
|
self.create_default_reports(TestReport, reports)
|
||||||
|
|
||||||
def create_default_build_reports(self):
|
def create_default_build_reports(self):
|
||||||
"""
|
"""Create database entries for the default BuildReport templates (if they do not already exist)"""
|
||||||
Create database entries for the default BuildReport templates
|
|
||||||
(if they do not already exist)
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from .models import BuildReport
|
from .models import BuildReport
|
||||||
except: # pragma: no cover
|
except: # pragma: no cover
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
"""
|
"""Report template model definitions"""
|
||||||
Report template model definitions
|
|
||||||
"""
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
@ -36,59 +34,41 @@ logger = logging.getLogger("inventree")
|
|||||||
|
|
||||||
|
|
||||||
def rename_template(instance, filename):
|
def rename_template(instance, filename):
|
||||||
"""
|
"""Helper function for 'renaming' uploaded report files.
|
||||||
Helper function for 'renaming' uploaded report files.
|
|
||||||
Pass responsibility back to the calling class,
|
Pass responsibility back to the calling class,
|
||||||
to ensure that files are uploaded to the correct directory.
|
to ensure that files are uploaded to the correct directory.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return instance.rename_file(filename)
|
return instance.rename_file(filename)
|
||||||
|
|
||||||
|
|
||||||
def validate_stock_item_report_filters(filters):
|
def validate_stock_item_report_filters(filters):
|
||||||
"""
|
"""Validate filter string against StockItem model"""
|
||||||
Validate filter string against StockItem model
|
|
||||||
"""
|
|
||||||
|
|
||||||
return validateFilterString(filters, model=stock.models.StockItem)
|
return validateFilterString(filters, model=stock.models.StockItem)
|
||||||
|
|
||||||
|
|
||||||
def validate_part_report_filters(filters):
|
def validate_part_report_filters(filters):
|
||||||
"""
|
"""Validate filter string against Part model"""
|
||||||
Validate filter string against Part model
|
|
||||||
"""
|
|
||||||
|
|
||||||
return validateFilterString(filters, model=part.models.Part)
|
return validateFilterString(filters, model=part.models.Part)
|
||||||
|
|
||||||
|
|
||||||
def validate_build_report_filters(filters):
|
def validate_build_report_filters(filters):
|
||||||
"""
|
"""Validate filter string against Build model"""
|
||||||
Validate filter string against Build model
|
|
||||||
"""
|
|
||||||
|
|
||||||
return validateFilterString(filters, model=build.models.Build)
|
return validateFilterString(filters, model=build.models.Build)
|
||||||
|
|
||||||
|
|
||||||
def validate_purchase_order_filters(filters):
|
def validate_purchase_order_filters(filters):
|
||||||
"""
|
"""Validate filter string against PurchaseOrder model"""
|
||||||
Validate filter string against PurchaseOrder model
|
|
||||||
"""
|
|
||||||
|
|
||||||
return validateFilterString(filters, model=order.models.PurchaseOrder)
|
return validateFilterString(filters, model=order.models.PurchaseOrder)
|
||||||
|
|
||||||
|
|
||||||
def validate_sales_order_filters(filters):
|
def validate_sales_order_filters(filters):
|
||||||
"""
|
"""Validate filter string against SalesOrder model"""
|
||||||
Validate filter string against SalesOrder model
|
|
||||||
"""
|
|
||||||
|
|
||||||
return validateFilterString(filters, model=order.models.SalesOrder)
|
return validateFilterString(filters, model=order.models.SalesOrder)
|
||||||
|
|
||||||
|
|
||||||
class WeasyprintReportMixin(WeasyTemplateResponseMixin):
|
class WeasyprintReportMixin(WeasyTemplateResponseMixin):
|
||||||
"""
|
"""Class for rendering a HTML template to a PDF."""
|
||||||
Class for rendering a HTML template to a PDF.
|
|
||||||
"""
|
|
||||||
|
|
||||||
pdf_filename = 'report.pdf'
|
pdf_filename = 'report.pdf'
|
||||||
pdf_attachment = True
|
pdf_attachment = True
|
||||||
@ -101,9 +81,7 @@ class WeasyprintReportMixin(WeasyTemplateResponseMixin):
|
|||||||
|
|
||||||
|
|
||||||
class ReportBase(models.Model):
|
class ReportBase(models.Model):
|
||||||
"""
|
"""Base class for uploading html templates"""
|
||||||
Base class for uploading html templates
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
@ -151,11 +129,10 @@ class ReportBase(models.Model):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def template_name(self):
|
def template_name(self):
|
||||||
"""
|
"""Returns the file system path to the template file.
|
||||||
Returns the file system path to the template file.
|
|
||||||
Required for passing the file to an external process
|
Required for passing the file to an external process
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template = self.template.name
|
template = self.template.name
|
||||||
template = template.replace('/', os.path.sep)
|
template = template.replace('/', os.path.sep)
|
||||||
template = template.replace('\\', os.path.sep)
|
template = template.replace('\\', os.path.sep)
|
||||||
@ -192,28 +169,20 @@ class ReportBase(models.Model):
|
|||||||
|
|
||||||
|
|
||||||
class ReportTemplateBase(ReportBase):
|
class ReportTemplateBase(ReportBase):
|
||||||
"""
|
"""Reporting template model.
|
||||||
Reporting template model.
|
|
||||||
|
|
||||||
Able to be passed context data
|
Able to be passed context data
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Pass a single top-level object to the report template
|
# Pass a single top-level object to the report template
|
||||||
object_to_print = None
|
object_to_print = None
|
||||||
|
|
||||||
def get_context_data(self, request):
|
def get_context_data(self, request):
|
||||||
"""
|
"""Supply context data to the template for rendering"""
|
||||||
Supply context data to the template for rendering
|
|
||||||
"""
|
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def context(self, request):
|
def context(self, request):
|
||||||
"""
|
"""All context to be passed to the renderer."""
|
||||||
All context to be passed to the renderer.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Generate custom context data based on the particular report subclass
|
# Generate custom context data based on the particular report subclass
|
||||||
context = self.get_context_data(request)
|
context = self.get_context_data(request)
|
||||||
|
|
||||||
@ -230,10 +199,7 @@ class ReportTemplateBase(ReportBase):
|
|||||||
return context
|
return context
|
||||||
|
|
||||||
def generate_filename(self, request, **kwargs):
|
def generate_filename(self, request, **kwargs):
|
||||||
"""
|
"""Generate a filename for this report"""
|
||||||
Generate a filename for this report
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_string = Template(self.filename_pattern)
|
template_string = Template(self.filename_pattern)
|
||||||
|
|
||||||
ctx = self.context(request)
|
ctx = self.context(request)
|
||||||
@ -243,21 +209,17 @@ class ReportTemplateBase(ReportBase):
|
|||||||
return template_string.render(context)
|
return template_string.render(context)
|
||||||
|
|
||||||
def render_as_string(self, request, **kwargs):
|
def render_as_string(self, request, **kwargs):
|
||||||
"""
|
"""Render the report to a HTML string.
|
||||||
Render the report to a HTML string.
|
|
||||||
|
|
||||||
Useful for debug mode (viewing generated code)
|
Useful for debug mode (viewing generated code)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return render_to_string(self.template_name, self.context(request), request)
|
return render_to_string(self.template_name, self.context(request), request)
|
||||||
|
|
||||||
def render(self, request, **kwargs):
|
def render(self, request, **kwargs):
|
||||||
"""
|
"""Render the template to a PDF file.
|
||||||
Render the template to a PDF file.
|
|
||||||
|
|
||||||
Uses django-weasyprint plugin to render HTML template against Weasyprint
|
Uses django-weasyprint plugin to render HTML template against Weasyprint
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO: Support custom filename generation!
|
# TODO: Support custom filename generation!
|
||||||
# filename = kwargs.get('filename', 'report.pdf')
|
# filename = kwargs.get('filename', 'report.pdf')
|
||||||
|
|
||||||
@ -292,9 +254,7 @@ class ReportTemplateBase(ReportBase):
|
|||||||
|
|
||||||
|
|
||||||
class TestReport(ReportTemplateBase):
|
class TestReport(ReportTemplateBase):
|
||||||
"""
|
"""Render a TestReport against a StockItem object."""
|
||||||
Render a TestReport against a StockItem object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
@ -321,10 +281,7 @@ class TestReport(ReportTemplateBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def matches_stock_item(self, item):
|
def matches_stock_item(self, item):
|
||||||
"""
|
"""Test if this report template matches a given StockItem objects"""
|
||||||
Test if this report template matches a given StockItem objects
|
|
||||||
"""
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
filters = validateFilterString(self.filters)
|
filters = validateFilterString(self.filters)
|
||||||
items = stock.models.StockItem.objects.filter(**filters)
|
items = stock.models.StockItem.objects.filter(**filters)
|
||||||
@ -352,9 +309,7 @@ class TestReport(ReportTemplateBase):
|
|||||||
|
|
||||||
|
|
||||||
class BuildReport(ReportTemplateBase):
|
class BuildReport(ReportTemplateBase):
|
||||||
"""
|
"""Build order / work order report"""
|
||||||
Build order / work order report
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
@ -375,10 +330,7 @@ class BuildReport(ReportTemplateBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, request):
|
def get_context_data(self, request):
|
||||||
"""
|
"""Custom context data for the build report"""
|
||||||
Custom context data for the build report
|
|
||||||
"""
|
|
||||||
|
|
||||||
my_build = self.object_to_print
|
my_build = self.object_to_print
|
||||||
|
|
||||||
if type(my_build) != build.models.Build:
|
if type(my_build) != build.models.Build:
|
||||||
@ -395,9 +347,7 @@ class BuildReport(ReportTemplateBase):
|
|||||||
|
|
||||||
|
|
||||||
class BillOfMaterialsReport(ReportTemplateBase):
|
class BillOfMaterialsReport(ReportTemplateBase):
|
||||||
"""
|
"""Render a Bill of Materials against a Part object"""
|
||||||
Render a Bill of Materials against a Part object
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
@ -429,9 +379,7 @@ class BillOfMaterialsReport(ReportTemplateBase):
|
|||||||
|
|
||||||
|
|
||||||
class PurchaseOrderReport(ReportTemplateBase):
|
class PurchaseOrderReport(ReportTemplateBase):
|
||||||
"""
|
"""Render a report against a PurchaseOrder object"""
|
||||||
Render a report against a PurchaseOrder object
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
@ -468,9 +416,7 @@ class PurchaseOrderReport(ReportTemplateBase):
|
|||||||
|
|
||||||
|
|
||||||
class SalesOrderReport(ReportTemplateBase):
|
class SalesOrderReport(ReportTemplateBase):
|
||||||
"""
|
"""Render a report against a SalesOrder object"""
|
||||||
Render a report against a SalesOrder object
|
|
||||||
"""
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_api_url():
|
def get_api_url():
|
||||||
@ -530,9 +476,7 @@ def rename_snippet(instance, filename):
|
|||||||
|
|
||||||
|
|
||||||
class ReportSnippet(models.Model):
|
class ReportSnippet(models.Model):
|
||||||
"""
|
"""Report template 'snippet' which can be used to make templates that can then be included in other reports.
|
||||||
Report template 'snippet' which can be used to make templates
|
|
||||||
that can then be included in other reports.
|
|
||||||
|
|
||||||
Useful for 'common' template actions, sub-templates, etc
|
Useful for 'common' template actions, sub-templates, etc
|
||||||
"""
|
"""
|
||||||
@ -568,6 +512,7 @@ def rename_asset(instance, filename):
|
|||||||
|
|
||||||
class ReportAsset(models.Model):
|
class ReportAsset(models.Model):
|
||||||
"""Asset file for use in report templates.
|
"""Asset file for use in report templates.
|
||||||
|
|
||||||
For example, an image to use in a header file.
|
For example, an image to use in a header file.
|
||||||
Uploaded asset files appear in MEDIA_ROOT/report/assets,
|
Uploaded asset files appear in MEDIA_ROOT/report/assets,
|
||||||
and can be loaded in a template using the {% report_asset <filename> %} tag.
|
and can be loaded in a template using the {% report_asset <filename> %} tag.
|
||||||
|
Reference in New Issue
Block a user