2
0
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:
Matthias
2022-05-28 02:43:33 +02:00
parent cb6fd63343
commit 61287dba2b
29 changed files with 134 additions and 361 deletions

View File

@ -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')

View File

@ -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()

View File

@ -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,

View File

@ -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

View File

@ -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():

View File

@ -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"

View File

@ -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)

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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)

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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')

View File

@ -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'

View File

@ -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

View File

@ -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"

View File

@ -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):

View File

@ -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}")

View File

@ -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']

View File

@ -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')

View File

@ -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
""" """

View File

@ -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.
""" """

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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.