mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-14 11:05:41 +00:00
fix docstrings 4
This commit is contained in:
@ -4,8 +4,7 @@ from plugin.helpers import MixinNotImplementedError
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@ -13,9 +12,8 @@ class LabelPrintingMixin:
|
||||
"""
|
||||
|
||||
class MixinMeta:
|
||||
"""
|
||||
Meta options for this mixin
|
||||
"""
|
||||
"""Meta options for this mixin"""
|
||||
|
||||
MIXIN_NAME = 'Label printing'
|
||||
|
||||
def __init__(self): # pragma: no cover
|
||||
@ -23,8 +21,7 @@ class LabelPrintingMixin:
|
||||
self.add_mixin('labels', True, __class__)
|
||||
|
||||
def print_label(self, label, **kwargs):
|
||||
"""
|
||||
Callback to print a single label
|
||||
"""Callback to print a single label
|
||||
|
||||
Arguments:
|
||||
label: A black-and-white pillow Image object
|
||||
@ -32,8 +29,6 @@ class LabelPrintingMixin:
|
||||
kwargs:
|
||||
length: The length of the label (in mm)
|
||||
width: The width of the label (in mm)
|
||||
|
||||
"""
|
||||
|
||||
# Unimplemented (to be implemented by the particular plugin class)
|
||||
raise MixinNotImplementedError('This Plugin must implement a `print_label` method')
|
||||
|
@ -28,7 +28,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
||||
|
||||
def do_activate_plugin(self):
|
||||
"""Activate the 'samplelabel' plugin"""
|
||||
|
||||
config = registry.get_plugin('samplelabel').plugin_config()
|
||||
config.active = True
|
||||
config.save()
|
||||
@ -62,7 +61,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
||||
|
||||
def test_wrong_implementation(self):
|
||||
"""Test that a wrong implementation raises an error"""
|
||||
|
||||
class WrongPlugin(LabelPrintingMixin, InvenTreePlugin):
|
||||
pass
|
||||
|
||||
@ -72,7 +70,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
||||
|
||||
def test_installed(self):
|
||||
"""Test that the sample printing plugin is installed"""
|
||||
|
||||
# Get all label plugins
|
||||
plugins = registry.with_mixin('labels')
|
||||
self.assertEqual(len(plugins), 1)
|
||||
@ -83,7 +80,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
||||
|
||||
def test_api(self):
|
||||
"""Test that we can filter the API endpoint by mixin"""
|
||||
|
||||
url = reverse('api-plugin-list')
|
||||
|
||||
# Try POST (disallowed)
|
||||
@ -128,7 +124,6 @@ class LabelMixinTests(InvenTreeAPITestCase):
|
||||
|
||||
def test_printing_process(self):
|
||||
"""Test that a label can be printed"""
|
||||
|
||||
# Ensure the labels were created
|
||||
apps.get_app_config('label').create_labels()
|
||||
|
||||
|
@ -11,9 +11,7 @@ from stock.models import StockItem, StockLocation
|
||||
|
||||
|
||||
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 = [
|
||||
permissions.IsAuthenticated,
|
||||
|
@ -8,8 +8,7 @@ logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Refer to the default method implementations below for more information!
|
||||
|
||||
"""
|
||||
|
||||
class MixinMeta:
|
||||
@ -34,8 +32,7 @@ class LocateMixin:
|
||||
self.add_mixin('locate', True, __class__)
|
||||
|
||||
def locate_stock_item(self, item_pk):
|
||||
"""
|
||||
Attempt to locate a particular StockItem
|
||||
"""Attempt to locate a particular StockItem
|
||||
|
||||
Arguments:
|
||||
item_pk: The PK (primary key) of the StockItem to be located
|
||||
@ -63,8 +60,7 @@ class LocateMixin:
|
||||
pass
|
||||
|
||||
def locate_stock_location(self, location_pk):
|
||||
"""
|
||||
Attempt to location a particular StockLocation
|
||||
"""Attempt to location a particular StockLocation
|
||||
|
||||
Arguments:
|
||||
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
|
||||
|
||||
@ -21,7 +19,6 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
||||
|
||||
def test_installed(self):
|
||||
"""Test that a locate plugin is actually installed"""
|
||||
|
||||
plugins = registry.with_mixin('locate')
|
||||
|
||||
self.assertTrue(len(plugins) > 0)
|
||||
@ -30,7 +27,6 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
||||
|
||||
def test_locate_fail(self):
|
||||
"""Test various API failure modes"""
|
||||
|
||||
url = reverse('api-locate-plugin')
|
||||
|
||||
# Post without a plugin
|
||||
@ -90,13 +86,11 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
||||
self.assertIn(f"StockLocation matching PK '{pk}' not found", str(response.data))
|
||||
|
||||
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,
|
||||
the sample 'locate' function will be called 'inline'
|
||||
"""
|
||||
|
||||
url = reverse('api-locate-plugin')
|
||||
|
||||
item = StockItem.objects.get(pk=1)
|
||||
@ -121,10 +115,7 @@ class LocatePluginTests(InvenTreeAPITestCase):
|
||||
self.assertTrue(item.metadata['located'])
|
||||
|
||||
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')
|
||||
|
||||
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.mixins import ActionMixin
|
||||
|
||||
|
||||
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"
|
||||
ACTION_NAME = "simple"
|
||||
|
@ -1,11 +1,11 @@
|
||||
""" Unit tests for action plugins """
|
||||
"""Unit tests for action plugins"""
|
||||
|
||||
from InvenTree.helpers import InvenTreeTestCase
|
||||
from plugin.builtin.action.simpleactionplugin import SimpleActionPlugin
|
||||
|
||||
|
||||
class SimpleActionPluginTests(InvenTreeTestCase):
|
||||
""" Tests for SampleIntegrationPlugin """
|
||||
"""Tests for SampleIntegrationPlugin"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@ -13,12 +13,12 @@ class SimpleActionPluginTests(InvenTreeTestCase):
|
||||
self.plugin = SimpleActionPlugin()
|
||||
|
||||
def test_name(self):
|
||||
"""check plugn names """
|
||||
"""Check plugn names"""
|
||||
self.assertEqual(self.plugin.plugin_name(), "SimpleActionPlugin")
|
||||
self.assertEqual(self.plugin.action_name(), "simple")
|
||||
|
||||
def test_function(self):
|
||||
"""check if functions work """
|
||||
"""Check if functions work"""
|
||||
# test functions
|
||||
response = self.client.post('/api/action/', data={'action': "simple", 'data': {'foo': "bar", }})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Unit tests for InvenTreeBarcodePlugin"""
|
||||
|
||||
from django.urls import reverse
|
||||
@ -18,9 +17,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
]
|
||||
|
||||
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):
|
||||
response = self.client.post(
|
||||
@ -46,10 +43,7 @@ class TestInvenTreeBarcode(InvenTreeAPITestCase):
|
||||
test_assert_error('{"blbla": 10004}')
|
||||
|
||||
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'})
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
self.assertIn('success', response.data)
|
||||
|
@ -1,5 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Core set of Notifications as a Plugin"""
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@ -16,9 +16,7 @@ class PlgMixin:
|
||||
|
||||
|
||||
class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
||||
"""
|
||||
Core notification methods for InvenTree
|
||||
"""
|
||||
"""Core notification methods for InvenTree"""
|
||||
|
||||
NAME = "CoreNotificationsPlugin"
|
||||
AUTHOR = _('InvenTree contributors')
|
||||
@ -50,11 +48,7 @@ class CoreNotificationsPlugin(SettingsMixin, InvenTreePlugin):
|
||||
}
|
||||
|
||||
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 = []
|
||||
|
||||
for user in self.targets:
|
||||
|
@ -8,10 +8,7 @@ from plugin.models import NotificationUserSetting
|
||||
class CoreNotificationTestTests(BaseNotificationIntegrationTest):
|
||||
|
||||
def test_email(self):
|
||||
"""
|
||||
Ensure that the email notifications run
|
||||
"""
|
||||
|
||||
"""Ensure that the email notifications run"""
|
||||
# enable plugin and set mail setting to true
|
||||
plugin = registry.plugins.get('corenotificationsplugin')
|
||||
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,
|
||||
SingleNotificationMethod)
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Sample plugin which responds to events
|
||||
"""
|
||||
"""Sample plugin which responds to events"""
|
||||
|
||||
import warnings
|
||||
|
||||
@ -11,16 +9,14 @@ from plugin.mixins import EventMixin
|
||||
|
||||
|
||||
class EventPluginSample(EventMixin, InvenTreePlugin):
|
||||
"""
|
||||
A sample plugin which provides supports for triggered events
|
||||
"""
|
||||
"""A sample plugin which provides supports for triggered events"""
|
||||
|
||||
NAME = "EventPlugin"
|
||||
SLUG = "sampleevent"
|
||||
TITLE = "Triggered Events"
|
||||
|
||||
def process_event(self, event, *args, **kwargs):
|
||||
""" Custom event processing """
|
||||
"""Custom event processing"""
|
||||
|
||||
print(f"Processing triggered event: '{event}'")
|
||||
print("args:", str(args))
|
||||
|
@ -31,7 +31,6 @@ class EventPluginSampleTests(TestCase):
|
||||
|
||||
def test_mixin(self):
|
||||
"""Test that MixinNotImplementedError is raised"""
|
||||
|
||||
with self.assertRaises(MixinNotImplementedError):
|
||||
class Wrong(EventMixin, InvenTreePlugin):
|
||||
pass
|
||||
|
@ -1,19 +1,15 @@
|
||||
"""sample implementation for IntegrationPlugin"""
|
||||
"""Sample implementation for IntegrationPlugin"""
|
||||
from plugin import InvenTreePlugin
|
||||
from plugin.mixins import UrlsMixin
|
||||
|
||||
|
||||
class NoIntegrationPlugin(InvenTreePlugin):
|
||||
"""
|
||||
An basic plugin
|
||||
"""
|
||||
"""A basic plugin"""
|
||||
|
||||
NAME = "NoIntegrationPlugin"
|
||||
|
||||
|
||||
class WrongIntegrationPlugin(UrlsMixin, InvenTreePlugin):
|
||||
"""
|
||||
An basic wron plugin with urls
|
||||
"""
|
||||
"""A basic wron plugin with urls"""
|
||||
|
||||
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.mixins import APICallMixin, SettingsMixin
|
||||
|
||||
|
||||
class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
|
||||
"""
|
||||
A small api call sample
|
||||
"""
|
||||
"""A small api call sample"""
|
||||
|
||||
NAME = "Sample API Caller"
|
||||
|
||||
SETTINGS = {
|
||||
@ -26,7 +23,5 @@ class SampleApiCallerPlugin(APICallMixin, SettingsMixin, InvenTreePlugin):
|
||||
API_TOKEN_SETTING = 'API_TOKEN'
|
||||
|
||||
def get_external_url(self):
|
||||
"""
|
||||
returns data from the sample endpoint
|
||||
"""
|
||||
"""Returns data from the sample endpoint"""
|
||||
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
|
||||
|
||||
|
||||
class BrokenIntegrationPlugin(InvenTreePlugin):
|
||||
"""
|
||||
An very broken plugin
|
||||
"""
|
||||
"""A very broken plugin"""
|
||||
|
||||
NAME = 'Test'
|
||||
TITLE = 'Broken Plugin'
|
||||
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 plugin import InvenTreePlugin
|
||||
@ -9,9 +7,7 @@ from stock.views import StockLocationDetail
|
||||
|
||||
|
||||
class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
|
||||
"""
|
||||
A sample plugin which renders some custom panels.
|
||||
"""
|
||||
"""A sample plugin which renders some custom panels."""
|
||||
|
||||
NAME = "CustomPanelExample"
|
||||
SLUG = "samplepanel"
|
||||
@ -45,9 +41,7 @@ class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
|
||||
return ctx
|
||||
|
||||
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
|
||||
- Only on a single page or set of pages
|
||||
|
@ -4,9 +4,7 @@ from plugin.mixins import LabelPrintingMixin
|
||||
|
||||
|
||||
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"
|
||||
SLUG = "samplelabel"
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Sample implementations for IntegrationPlugin
|
||||
"""
|
||||
"""Sample implementations for IntegrationPlugin"""
|
||||
|
||||
from django.http import HttpResponse
|
||||
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):
|
||||
"""
|
||||
A full plugin example
|
||||
"""
|
||||
"""A full plugin example"""
|
||||
|
||||
NAME = "SampleIntegrationPlugin"
|
||||
SLUG = "sample"
|
||||
@ -23,7 +19,7 @@ class SampleIntegrationPlugin(AppMixin, SettingsMixin, UrlsMixin, NavigationMixi
|
||||
NAVIGATION_TAB_ICON = 'fas fa-plus'
|
||||
|
||||
def view_test(self, request):
|
||||
"""very basic view"""
|
||||
"""Very basic view"""
|
||||
return HttpResponse(f'Hi there {request.user.username} this works')
|
||||
|
||||
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.mixins import ScheduleMixin, SettingsMixin
|
||||
@ -16,9 +14,7 @@ def print_world():
|
||||
|
||||
|
||||
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"
|
||||
SLUG = "schedule"
|
||||
@ -51,10 +47,7 @@ class ScheduledTaskPlugin(ScheduleMixin, SettingsMixin, InvenTreePlugin):
|
||||
}
|
||||
|
||||
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')
|
||||
|
||||
print(f"Called member_func - value is {t_or_f}")
|
||||
|
@ -1,4 +1,4 @@
|
||||
""" Unit tests for action caller sample"""
|
||||
"""Unit tests for action caller sample"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
@ -6,10 +6,10 @@ from plugin import registry
|
||||
|
||||
|
||||
class SampleApiCallerPluginTests(TestCase):
|
||||
""" Tests for SampleApiCallerPluginTests """
|
||||
"""Tests for SampleApiCallerPluginTests """
|
||||
|
||||
def test_return(self):
|
||||
"""check if the external api call works"""
|
||||
"""Check if the external api call works"""
|
||||
# The plugin should be defined
|
||||
self.assertIn('sample-api-caller', registry.plugins)
|
||||
plg = registry.plugins['sample-api-caller']
|
||||
|
@ -1,13 +1,13 @@
|
||||
""" Unit tests for action plugins """
|
||||
"""Unit tests for action plugins"""
|
||||
|
||||
from InvenTree.helpers import InvenTreeTestCase
|
||||
|
||||
|
||||
class SampleIntegrationPluginTests(InvenTreeTestCase):
|
||||
""" Tests for SampleIntegrationPlugin """
|
||||
"""Tests for SampleIntegrationPlugin"""
|
||||
|
||||
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/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.content, b'Hi there testuser this works')
|
||||
|
@ -1,4 +1,4 @@
|
||||
""" Unit tests for scheduled tasks"""
|
||||
"""Unit tests for scheduled tasks"""
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
@ -9,10 +9,10 @@ from plugin.registry import call_function
|
||||
|
||||
|
||||
class ExampleScheduledTaskPluginTests(TestCase):
|
||||
""" Tests for provided ScheduledTaskPlugin """
|
||||
"""Tests for provided ScheduledTaskPlugin"""
|
||||
|
||||
def test_function(self):
|
||||
"""check if the scheduling works"""
|
||||
"""Check if the scheduling works"""
|
||||
# The plugin should be defined
|
||||
self.assertIn('schedule', registry.plugins)
|
||||
plg = registry.plugins['schedule']
|
||||
@ -44,7 +44,7 @@ class ExampleScheduledTaskPluginTests(TestCase):
|
||||
self.assertEqual(len(scheduled_plugin_tasks), 0)
|
||||
|
||||
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
|
||||
self.assertEqual(call_function('schedule', 'member_func'), False)
|
||||
|
||||
@ -53,7 +53,7 @@ class ExampleScheduledTaskPluginTests(TestCase):
|
||||
|
||||
|
||||
class ScheduledTaskPluginTests(TestCase):
|
||||
""" Tests for ScheduledTaskPluginTests mixin base """
|
||||
"""Tests for ScheduledTaskPluginTests mixin base"""
|
||||
|
||||
def test_init(self):
|
||||
"""Check that all MixinImplementationErrors raise"""
|
||||
@ -68,8 +68,7 @@ class ScheduledTaskPluginTests(TestCase):
|
||||
NoSchedules()
|
||||
|
||||
class WrongFuncSchedules(Base):
|
||||
"""
|
||||
Plugin with broken functions
|
||||
"""Plugin with broken functions
|
||||
|
||||
This plugin is missing a func
|
||||
"""
|
||||
@ -88,8 +87,7 @@ class ScheduledTaskPluginTests(TestCase):
|
||||
WrongFuncSchedules()
|
||||
|
||||
class WrongFuncSchedules1(WrongFuncSchedules):
|
||||
"""
|
||||
Plugin with broken functions
|
||||
"""Plugin with broken functions
|
||||
|
||||
This plugin is missing a schedule
|
||||
"""
|
||||
@ -105,8 +103,7 @@ class ScheduledTaskPluginTests(TestCase):
|
||||
WrongFuncSchedules1()
|
||||
|
||||
class WrongFuncSchedules2(WrongFuncSchedules):
|
||||
"""
|
||||
Plugin with broken functions
|
||||
"""Plugin with broken functions
|
||||
|
||||
This plugin is missing a schedule
|
||||
"""
|
||||
@ -122,8 +119,7 @@ class ScheduledTaskPluginTests(TestCase):
|
||||
WrongFuncSchedules2()
|
||||
|
||||
class WrongFuncSchedules3(WrongFuncSchedules):
|
||||
"""
|
||||
Plugin with broken functions
|
||||
"""Plugin with broken functions
|
||||
|
||||
This plugin has a broken schedule
|
||||
"""
|
||||
@ -140,8 +136,7 @@ class ScheduledTaskPluginTests(TestCase):
|
||||
WrongFuncSchedules3()
|
||||
|
||||
class WrongFuncSchedules4(WrongFuncSchedules):
|
||||
"""
|
||||
Plugin with broken functions
|
||||
"""Plugin with broken functions
|
||||
|
||||
This plugin is missing a minute marker for its schedule
|
||||
"""
|
||||
|
@ -13,8 +13,8 @@ logger = logging.getLogger('inventree')
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
|
@ -51,7 +51,6 @@ class SampleLocatePlugintests(InvenTreeAPITestCase):
|
||||
|
||||
def test_mixin(self):
|
||||
"""Test that MixinNotImplementedError is raised"""
|
||||
|
||||
with self.assertRaises(MixinNotImplementedError):
|
||||
class Wrong(LocateMixin, InvenTreePlugin):
|
||||
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.conf import settings as djangosettings
|
||||
from django.urls import reverse
|
||||
@ -15,49 +13,37 @@ register = template.Library()
|
||||
|
||||
@register.simple_tag()
|
||||
def plugin_list(*args, **kwargs):
|
||||
"""
|
||||
List of all installed plugins
|
||||
"""
|
||||
"""List of all installed plugins"""
|
||||
return registry.plugins
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def inactive_plugin_list(*args, **kwargs):
|
||||
"""
|
||||
List of all inactive plugins
|
||||
"""
|
||||
"""List of all inactive plugins"""
|
||||
return registry.plugins_inactive
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
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)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
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)
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
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
|
||||
|
||||
|
||||
@register.simple_tag()
|
||||
def navigation_enabled(*args, **kwargs):
|
||||
"""
|
||||
Is plugin navigation enabled?
|
||||
"""
|
||||
"""Is plugin navigation enabled?"""
|
||||
if djangosettings.PLUGIN_TESTING:
|
||||
return True
|
||||
return InvenTreeSetting.get_setting('ENABLE_PLUGINS_NAVIGATION') # pragma: no cover
|
||||
@ -65,8 +51,8 @@ def navigation_enabled(*args, **kwargs):
|
||||
|
||||
@register.simple_tag()
|
||||
def safe_url(view_name, *args, **kwargs):
|
||||
"""
|
||||
Safe lookup fnc for URLs
|
||||
"""Safe lookup fnc for URLs
|
||||
|
||||
Returns None if not found
|
||||
"""
|
||||
try:
|
||||
@ -77,15 +63,11 @@ def safe_url(view_name, *args, **kwargs):
|
||||
|
||||
@register.simple_tag()
|
||||
def plugin_errors(*args, **kwargs):
|
||||
"""
|
||||
All plugin errors in the current session
|
||||
"""
|
||||
"""All plugin errors in the current session"""
|
||||
return registry.errors
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
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))
|
||||
|
@ -24,9 +24,7 @@ from .serializers import (BOMReportSerializer, BuildReportSerializer,
|
||||
|
||||
|
||||
class ReportListView(generics.ListAPIView):
|
||||
"""
|
||||
Generic API class for report templates
|
||||
"""
|
||||
"""Generic API class for report templates"""
|
||||
|
||||
filter_backends = [
|
||||
DjangoFilterBackend,
|
||||
@ -44,15 +42,10 @@ class ReportListView(generics.ListAPIView):
|
||||
|
||||
|
||||
class StockItemReportMixin:
|
||||
"""
|
||||
Mixin for extracting stock items from query params
|
||||
"""
|
||||
"""Mixin for extracting stock items from query params"""
|
||||
|
||||
def get_items(self):
|
||||
"""
|
||||
Return a list of requested stock items
|
||||
"""
|
||||
|
||||
"""Return a list of requested stock items"""
|
||||
items = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -77,15 +70,10 @@ class StockItemReportMixin:
|
||||
|
||||
|
||||
class BuildReportMixin:
|
||||
"""
|
||||
Mixin for extracting Build items from query params
|
||||
"""
|
||||
"""Mixin for extracting Build items from query params"""
|
||||
|
||||
def get_builds(self):
|
||||
"""
|
||||
Return a list of requested Build objects
|
||||
"""
|
||||
|
||||
"""Return a list of requested Build objects"""
|
||||
builds = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -109,17 +97,13 @@ class BuildReportMixin:
|
||||
|
||||
|
||||
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!
|
||||
"""
|
||||
|
||||
def get_orders(self):
|
||||
"""
|
||||
Return a list of order objects
|
||||
"""
|
||||
|
||||
"""Return a list of order objects"""
|
||||
orders = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -143,15 +127,10 @@ class OrderReportMixin:
|
||||
|
||||
|
||||
class PartReportMixin:
|
||||
"""
|
||||
Mixin for extracting part items from query params
|
||||
"""
|
||||
"""Mixin for extracting part items from query params"""
|
||||
|
||||
def get_parts(self):
|
||||
"""
|
||||
Return a list of requested part objects
|
||||
"""
|
||||
|
||||
"""Return a list of requested part objects"""
|
||||
parts = []
|
||||
|
||||
params = self.request.query_params
|
||||
@ -176,15 +155,10 @@ class PartReportMixin:
|
||||
|
||||
|
||||
class ReportPrintMixin:
|
||||
"""
|
||||
Mixin for printing reports
|
||||
"""
|
||||
"""Mixin for printing reports"""
|
||||
|
||||
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:
|
||||
# No valid items provided, return an error message
|
||||
data = {
|
||||
@ -283,8 +257,7 @@ class ReportPrintMixin:
|
||||
|
||||
|
||||
class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
||||
"""
|
||||
API endpoint for viewing list of TestReport objects.
|
||||
"""API endpoint for viewing list of TestReport objects.
|
||||
|
||||
Filterable by:
|
||||
|
||||
@ -347,35 +320,27 @@ class StockItemTestReportList(ReportListView, StockItemReportMixin):
|
||||
|
||||
|
||||
class StockItemTestReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single TestReport object
|
||||
"""
|
||||
"""API endpoint for a single TestReport object"""
|
||||
|
||||
queryset = TestReport.objects.all()
|
||||
serializer_class = TestReportSerializer
|
||||
|
||||
|
||||
class StockItemTestReportPrint(generics.RetrieveAPIView, StockItemReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a TestReport object
|
||||
"""
|
||||
"""API endpoint for printing a TestReport object"""
|
||||
|
||||
queryset = TestReport.objects.all()
|
||||
serializer_class = TestReportSerializer
|
||||
|
||||
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()
|
||||
|
||||
return self.print(request, items)
|
||||
|
||||
|
||||
class BOMReportList(ReportListView, PartReportMixin):
|
||||
"""
|
||||
API endpoint for viewing a list of BillOfMaterialReport objects.
|
||||
"""API endpoint for viewing a list of BillOfMaterialReport objects.
|
||||
|
||||
Filterably by:
|
||||
|
||||
@ -436,35 +401,27 @@ class BOMReportList(ReportListView, PartReportMixin):
|
||||
|
||||
|
||||
class BOMReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single BillOfMaterialReport object
|
||||
"""
|
||||
"""API endpoint for a single BillOfMaterialReport object"""
|
||||
|
||||
queryset = BillOfMaterialsReport.objects.all()
|
||||
serializer_class = BOMReportSerializer
|
||||
|
||||
|
||||
class BOMReportPrint(generics.RetrieveAPIView, PartReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a BillOfMaterialReport object
|
||||
"""
|
||||
"""API endpoint for printing a BillOfMaterialReport object"""
|
||||
|
||||
queryset = BillOfMaterialsReport.objects.all()
|
||||
serializer_class = BOMReportSerializer
|
||||
|
||||
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()
|
||||
|
||||
return self.print(request, parts)
|
||||
|
||||
|
||||
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:
|
||||
|
||||
@ -526,18 +483,14 @@ class BuildReportList(ReportListView, BuildReportMixin):
|
||||
|
||||
|
||||
class BuildReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single BuildReport object
|
||||
"""
|
||||
"""API endpoint for a single BuildReport object"""
|
||||
|
||||
queryset = BuildReport.objects.all()
|
||||
serializer_class = BuildReportSerializer
|
||||
|
||||
|
||||
class BuildReportPrint(generics.RetrieveAPIView, BuildReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a BuildReport
|
||||
"""
|
||||
"""API endpoint for printing a BuildReport"""
|
||||
|
||||
queryset = BuildReport.objects.all()
|
||||
serializer_class = BuildReportSerializer
|
||||
@ -607,18 +560,14 @@ class PurchaseOrderReportList(ReportListView, OrderReportMixin):
|
||||
|
||||
|
||||
class PurchaseOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single PurchaseOrderReport object
|
||||
"""
|
||||
"""API endpoint for a single PurchaseOrderReport object"""
|
||||
|
||||
queryset = PurchaseOrderReport.objects.all()
|
||||
serializer_class = PurchaseOrderReportSerializer
|
||||
|
||||
|
||||
class PurchaseOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a PurchaseOrderReport object
|
||||
"""
|
||||
"""API endpoint for printing a PurchaseOrderReport object"""
|
||||
|
||||
OrderModel = order.models.PurchaseOrder
|
||||
|
||||
@ -690,18 +639,14 @@ class SalesOrderReportList(ReportListView, OrderReportMixin):
|
||||
|
||||
|
||||
class SalesOrderReportDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""
|
||||
API endpoint for a single SalesOrderReport object
|
||||
"""
|
||||
"""API endpoint for a single SalesOrderReport object"""
|
||||
|
||||
queryset = SalesOrderReport.objects.all()
|
||||
serializer_class = SalesOrderReportSerializer
|
||||
|
||||
|
||||
class SalesOrderReportPrint(generics.RetrieveAPIView, OrderReportMixin, ReportPrintMixin):
|
||||
"""
|
||||
API endpoint for printing a PurchaseOrderReport object
|
||||
"""
|
||||
"""API endpoint for printing a PurchaseOrderReport object"""
|
||||
|
||||
OrderModel = order.models.SalesOrder
|
||||
|
||||
|
@ -14,19 +14,13 @@ class ReportConfig(AppConfig):
|
||||
name = 'report'
|
||||
|
||||
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):
|
||||
self.create_default_test_reports()
|
||||
self.create_default_build_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
|
||||
src_dir = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)),
|
||||
@ -82,11 +76,7 @@ class ReportConfig(AppConfig):
|
||||
pass
|
||||
|
||||
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:
|
||||
from .models import TestReport
|
||||
except: # pragma: no cover
|
||||
@ -105,11 +95,7 @@ class ReportConfig(AppConfig):
|
||||
self.create_default_reports(TestReport, reports)
|
||||
|
||||
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:
|
||||
from .models import BuildReport
|
||||
except: # pragma: no cover
|
||||
|
@ -1,6 +1,4 @@
|
||||
"""
|
||||
Report template model definitions
|
||||
"""
|
||||
"""Report template model definitions"""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
@ -36,59 +34,41 @@ logger = logging.getLogger("inventree")
|
||||
|
||||
|
||||
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,
|
||||
to ensure that files are uploaded to the correct directory.
|
||||
"""
|
||||
|
||||
return instance.rename_file(filename)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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_attachment = True
|
||||
@ -101,9 +81,7 @@ class WeasyprintReportMixin(WeasyTemplateResponseMixin):
|
||||
|
||||
|
||||
class ReportBase(models.Model):
|
||||
"""
|
||||
Base class for uploading html templates
|
||||
"""
|
||||
"""Base class for uploading html templates"""
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
@ -151,11 +129,10 @@ class ReportBase(models.Model):
|
||||
|
||||
@property
|
||||
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
|
||||
"""
|
||||
|
||||
template = self.template.name
|
||||
template = template.replace('/', os.path.sep)
|
||||
template = template.replace('\\', os.path.sep)
|
||||
@ -192,28 +169,20 @@ class ReportBase(models.Model):
|
||||
|
||||
|
||||
class ReportTemplateBase(ReportBase):
|
||||
"""
|
||||
Reporting template model.
|
||||
"""Reporting template model.
|
||||
|
||||
Able to be passed context data
|
||||
|
||||
"""
|
||||
|
||||
# Pass a single top-level object to the report template
|
||||
object_to_print = None
|
||||
|
||||
def get_context_data(self, request):
|
||||
"""
|
||||
Supply context data to the template for rendering
|
||||
"""
|
||||
|
||||
"""Supply context data to the template for rendering"""
|
||||
return {}
|
||||
|
||||
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
|
||||
context = self.get_context_data(request)
|
||||
|
||||
@ -230,10 +199,7 @@ class ReportTemplateBase(ReportBase):
|
||||
return context
|
||||
|
||||
def generate_filename(self, request, **kwargs):
|
||||
"""
|
||||
Generate a filename for this report
|
||||
"""
|
||||
|
||||
"""Generate a filename for this report"""
|
||||
template_string = Template(self.filename_pattern)
|
||||
|
||||
ctx = self.context(request)
|
||||
@ -243,21 +209,17 @@ class ReportTemplateBase(ReportBase):
|
||||
return template_string.render(context)
|
||||
|
||||
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)
|
||||
"""
|
||||
|
||||
return render_to_string(self.template_name, self.context(request), request)
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
# TODO: Support custom filename generation!
|
||||
# filename = kwargs.get('filename', 'report.pdf')
|
||||
|
||||
@ -292,9 +254,7 @@ class ReportTemplateBase(ReportBase):
|
||||
|
||||
|
||||
class TestReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a TestReport against a StockItem object.
|
||||
"""
|
||||
"""Render a TestReport against a StockItem object."""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
@ -321,10 +281,7 @@ class TestReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
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:
|
||||
filters = validateFilterString(self.filters)
|
||||
items = stock.models.StockItem.objects.filter(**filters)
|
||||
@ -352,9 +309,7 @@ class TestReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class BuildReport(ReportTemplateBase):
|
||||
"""
|
||||
Build order / work order report
|
||||
"""
|
||||
"""Build order / work order report"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
@ -375,10 +330,7 @@ class BuildReport(ReportTemplateBase):
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
if type(my_build) != build.models.Build:
|
||||
@ -395,9 +347,7 @@ class BuildReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class BillOfMaterialsReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a Bill of Materials against a Part object
|
||||
"""
|
||||
"""Render a Bill of Materials against a Part object"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
@ -429,9 +379,7 @@ class BillOfMaterialsReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class PurchaseOrderReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a report against a PurchaseOrder object
|
||||
"""
|
||||
"""Render a report against a PurchaseOrder object"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
@ -468,9 +416,7 @@ class PurchaseOrderReport(ReportTemplateBase):
|
||||
|
||||
|
||||
class SalesOrderReport(ReportTemplateBase):
|
||||
"""
|
||||
Render a report against a SalesOrder object
|
||||
"""
|
||||
"""Render a report against a SalesOrder object"""
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
@ -530,9 +476,7 @@ def rename_snippet(instance, filename):
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
@ -568,6 +512,7 @@ def rename_asset(instance, filename):
|
||||
|
||||
class ReportAsset(models.Model):
|
||||
"""Asset file for use in report templates.
|
||||
|
||||
For example, an image to use in a header file.
|
||||
Uploaded asset files appear in MEDIA_ROOT/report/assets,
|
||||
and can be loaded in a template using the {% report_asset <filename> %} tag.
|
||||
|
Reference in New Issue
Block a user