mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Label printing unit test (#3047)
* Adds a very simple sample plugin for label printing * Test mixin install status and API query * Better error reporting for label printing API * pep fixes * fix assertation * remove broken assertation * igonre for coverage * test the base process of printing * refactor tests * clean up basic test * refactor url * fix url creation * test printing multiples * test all printing endpoints * test all list options - move api tests * test for invalid filters * refactor * test with no part * these should not happen checks are in place upstream * fix assertation * do not cover continue parts * test for wrong implementation * ignore DB not ready * remove covage from default parts * fix url generation * test debug mode * fix url assertation * check that nothing was rendered Co-authored-by: Oliver Walters <oliver.henry.walters@gmail.com>
This commit is contained in:
parent
6247eecf69
commit
840ade25cd
@ -4,12 +4,11 @@ from django.conf import settings
|
||||
from django.core.exceptions import FieldError, ValidationError
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.urls import include, re_path
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from PIL import Image
|
||||
from rest_framework import filters, generics
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.exceptions import NotFound
|
||||
|
||||
import common.models
|
||||
import InvenTree.helpers
|
||||
@ -62,10 +61,14 @@ class LabelPrintMixin:
|
||||
"""
|
||||
|
||||
if not settings.PLUGINS_ENABLED:
|
||||
return None
|
||||
return None # pragma: no cover
|
||||
|
||||
plugin_key = request.query_params.get('plugin', None)
|
||||
|
||||
# No plugin provided, and that's OK
|
||||
if plugin_key is None:
|
||||
return None
|
||||
|
||||
plugin = registry.get_plugin(plugin_key)
|
||||
|
||||
if plugin:
|
||||
@ -74,9 +77,10 @@ class LabelPrintMixin:
|
||||
if config and config.active:
|
||||
# Only return the plugin if it is enabled!
|
||||
return plugin
|
||||
|
||||
# No matches found
|
||||
return None
|
||||
else:
|
||||
raise ValidationError(f"Plugin '{plugin_key}' is not enabled")
|
||||
else:
|
||||
raise NotFound(f"Plugin '{plugin_key}' not found")
|
||||
|
||||
def print(self, request, items_to_print):
|
||||
"""
|
||||
@ -85,13 +89,11 @@ class LabelPrintMixin:
|
||||
|
||||
# Check the request to determine if the user has selected a label printing plugin
|
||||
plugin = self.get_plugin(request)
|
||||
|
||||
if len(items_to_print) == 0:
|
||||
# No valid items provided, return an error message
|
||||
data = {
|
||||
'error': _('No valid objects provided to template'),
|
||||
}
|
||||
|
||||
return Response(data, status=400)
|
||||
raise ValidationError('No valid objects provided to label template')
|
||||
|
||||
outputs = []
|
||||
|
||||
@ -281,7 +283,7 @@ class StockItemLabelList(LabelListView, StockItemLabelMixin):
|
||||
# Filter string defined for the StockItemLabel object
|
||||
try:
|
||||
filters = InvenTree.helpers.validateFilterString(label.filters)
|
||||
except ValidationError:
|
||||
except ValidationError: # pragma: no cover
|
||||
continue
|
||||
|
||||
for item in items:
|
||||
@ -300,7 +302,7 @@ class StockItemLabelList(LabelListView, StockItemLabelMixin):
|
||||
if matches:
|
||||
valid_label_ids.add(label.pk)
|
||||
else:
|
||||
continue
|
||||
continue # pragma: no cover
|
||||
|
||||
# Reduce queryset to only valid matches
|
||||
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
||||
@ -412,7 +414,7 @@ class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
|
||||
# Filter string defined for the StockLocationLabel object
|
||||
try:
|
||||
filters = InvenTree.helpers.validateFilterString(label.filters)
|
||||
except:
|
||||
except: # pragma: no cover
|
||||
# Skip if there was an error validating the filters...
|
||||
continue
|
||||
|
||||
@ -432,7 +434,7 @@ class StockLocationLabelList(LabelListView, StockLocationLabelMixin):
|
||||
if matches:
|
||||
valid_label_ids.add(label.pk)
|
||||
else:
|
||||
continue
|
||||
continue # pragma: no cover
|
||||
|
||||
# Reduce queryset to only valid matches
|
||||
queryset = queryset.filter(pk__in=[pk for pk in valid_label_ids])
|
||||
@ -519,7 +521,7 @@ class PartLabelList(LabelListView, PartLabelMixin):
|
||||
|
||||
try:
|
||||
filters = InvenTree.helpers.validateFilterString(label.filters)
|
||||
except ValidationError:
|
||||
except ValidationError: # pragma: no cover
|
||||
continue
|
||||
|
||||
for part in parts:
|
||||
|
@ -46,7 +46,7 @@ class LabelConfig(AppConfig):
|
||||
try:
|
||||
from .models import StockLocationLabel
|
||||
assert bool(StockLocationLabel is not None)
|
||||
except AppRegistryNotReady:
|
||||
except AppRegistryNotReady: # pragma: no cover
|
||||
# Database might not yet be ready
|
||||
warnings.warn('Database was not ready for creating labels')
|
||||
return
|
||||
|
@ -171,7 +171,7 @@ class LabelTemplate(models.Model):
|
||||
Note: Override this in any subclass
|
||||
"""
|
||||
|
||||
return {}
|
||||
return {} # pragma: no cover
|
||||
|
||||
def generate_filename(self, request, **kwargs):
|
||||
"""
|
||||
@ -242,7 +242,7 @@ class StockItemLabel(LabelTemplate):
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-stockitem-label-list')
|
||||
return reverse('api-stockitem-label-list') # pragma: no cover
|
||||
|
||||
SUBDIR = "stockitem"
|
||||
|
||||
@ -302,7 +302,7 @@ class StockLocationLabel(LabelTemplate):
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-stocklocation-label-list')
|
||||
return reverse('api-stocklocation-label-list') # pragma: no cover
|
||||
|
||||
SUBDIR = "stocklocation"
|
||||
|
||||
@ -349,7 +349,7 @@ class PartLabel(LabelTemplate):
|
||||
|
||||
@staticmethod
|
||||
def get_api_url():
|
||||
return reverse('api-part-label-list')
|
||||
return reverse('api-part-label-list') # pragma: no cover
|
||||
|
||||
SUBDIR = 'part'
|
||||
|
||||
|
@ -63,39 +63,3 @@ class TestReportTests(InvenTreeAPITestCase):
|
||||
'items': [10, 11, 12],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TestLabels(InvenTreeAPITestCase):
|
||||
"""
|
||||
Tests for the label APIs
|
||||
"""
|
||||
|
||||
fixtures = [
|
||||
'category',
|
||||
'part',
|
||||
'location',
|
||||
'stock',
|
||||
]
|
||||
|
||||
roles = [
|
||||
'stock.view',
|
||||
'stock_location.view',
|
||||
]
|
||||
|
||||
def do_list(self, filters={}):
|
||||
|
||||
response = self.client.get(self.list_url, filters, format='json')
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
return response.data
|
||||
|
||||
def test_lists(self):
|
||||
self.list_url = reverse('api-stockitem-label-list')
|
||||
self.do_list()
|
||||
|
||||
self.list_url = reverse('api-stocklocation-label-list')
|
||||
self.do_list()
|
||||
|
||||
self.list_url = reverse('api-part-label-list')
|
||||
self.do_list()
|
||||
|
@ -26,13 +26,13 @@ def print_label(plugin_slug, label_image, label_instance=None, user=None):
|
||||
|
||||
plugin = registry.plugins.get(plugin_slug, None)
|
||||
|
||||
if plugin is None:
|
||||
if plugin is None: # pragma: no cover
|
||||
logger.error(f"Could not find matching plugin for '{plugin_slug}'")
|
||||
return
|
||||
|
||||
try:
|
||||
plugin.print_label(label_image, width=label_instance.width, height=label_instance.height)
|
||||
except Exception as e:
|
||||
except Exception as e: # pragma: no cover
|
||||
# Plugin threw an error - notify the user who attempted to print
|
||||
|
||||
ctx = {
|
||||
|
209
InvenTree/plugin/base/label/test_label_mixin.py
Normal file
209
InvenTree/plugin/base/label/test_label_mixin.py
Normal file
@ -0,0 +1,209 @@
|
||||
"""Unit tests for the label printing mixin"""
|
||||
|
||||
from django.apps import apps
|
||||
from django.urls import reverse
|
||||
|
||||
from common.models import InvenTreeSetting
|
||||
from InvenTree.api_tester import InvenTreeAPITestCase
|
||||
from label.models import PartLabel, StockItemLabel, StockLocationLabel
|
||||
from part.models import Part
|
||||
from plugin.base.label.mixins import LabelPrintingMixin
|
||||
from plugin.helpers import MixinNotImplementedError
|
||||
from plugin.plugin import InvenTreePlugin
|
||||
from plugin.registry import registry
|
||||
from stock.models import StockItem, StockLocation
|
||||
|
||||
|
||||
class LabelMixinTests(InvenTreeAPITestCase):
|
||||
"""Test that the Label mixin operates correctly"""
|
||||
|
||||
fixtures = [
|
||||
'category',
|
||||
'part',
|
||||
'location',
|
||||
'stock',
|
||||
]
|
||||
|
||||
roles = 'all'
|
||||
|
||||
def do_activate_plugin(self):
|
||||
"""Activate the 'samplelabel' plugin"""
|
||||
|
||||
config = registry.get_plugin('samplelabel').plugin_config()
|
||||
config.active = True
|
||||
config.save()
|
||||
|
||||
def do_url(self, parts, plugin_ref, label, url_name: str = 'api-part-label-print', url_single: str = 'part', invalid: bool = False):
|
||||
"""Generate an URL to print a label"""
|
||||
# Construct URL
|
||||
kwargs = {}
|
||||
if label:
|
||||
kwargs["pk"] = label.pk
|
||||
|
||||
url = reverse(url_name, kwargs=kwargs)
|
||||
|
||||
# Append part filters
|
||||
if not parts:
|
||||
pass
|
||||
elif len(parts) == 1:
|
||||
url += f'?{url_single}={parts[0].pk}'
|
||||
elif len(parts) > 1:
|
||||
url += '?' + '&'.join([f'{url_single}s={item.pk}' for item in parts])
|
||||
|
||||
# Append an invalid item
|
||||
if invalid:
|
||||
url += f'&{url_single}{"s" if len(parts) > 1 else ""}=abc'
|
||||
|
||||
# Append plugin reference
|
||||
if plugin_ref:
|
||||
url += f'&plugin={plugin_ref}'
|
||||
|
||||
return url
|
||||
|
||||
def test_wrong_implementation(self):
|
||||
"""Test that a wrong implementation raises an error"""
|
||||
|
||||
class WrongPlugin(LabelPrintingMixin, InvenTreePlugin):
|
||||
pass
|
||||
|
||||
with self.assertRaises(MixinNotImplementedError):
|
||||
plugin = WrongPlugin()
|
||||
plugin.print_label('test')
|
||||
|
||||
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)
|
||||
|
||||
# But, it is not 'active'
|
||||
plugins = registry.with_mixin('labels', active=True)
|
||||
self.assertEqual(len(plugins), 0)
|
||||
|
||||
def test_api(self):
|
||||
"""Test that we can filter the API endpoint by mixin"""
|
||||
|
||||
url = reverse('api-plugin-list')
|
||||
|
||||
# Try POST (disallowed)
|
||||
response = self.client.post(url, {})
|
||||
self.assertEqual(response.status_code, 405)
|
||||
|
||||
response = self.client.get(
|
||||
url,
|
||||
{
|
||||
'mixin': 'labels',
|
||||
'active': True,
|
||||
}
|
||||
)
|
||||
|
||||
# No results matching this query!
|
||||
self.assertEqual(len(response.data), 0)
|
||||
|
||||
# What about inactive?
|
||||
response = self.client.get(
|
||||
url,
|
||||
{
|
||||
'mixin': 'labels',
|
||||
'active': False,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(len(response.data), 0)
|
||||
|
||||
self.do_activate_plugin()
|
||||
# Should be available via the API now
|
||||
response = self.client.get(
|
||||
url,
|
||||
{
|
||||
'mixin': 'labels',
|
||||
'active': True,
|
||||
}
|
||||
)
|
||||
|
||||
self.assertEqual(len(response.data), 1)
|
||||
data = response.data[0]
|
||||
self.assertEqual(data['key'], 'samplelabel')
|
||||
|
||||
def test_printing_process(self):
|
||||
"""Test that a label can be printed"""
|
||||
|
||||
# Ensure the labels were created
|
||||
apps.get_app_config('label').create_labels()
|
||||
|
||||
# Lookup references
|
||||
part = Part.objects.first()
|
||||
plugin_ref = 'samplelabel'
|
||||
label = PartLabel.objects.first()
|
||||
|
||||
url = self.do_url([part], plugin_ref, label)
|
||||
|
||||
# Non-exsisting plugin
|
||||
response = self.get(f'{url}123', expected_code=404)
|
||||
self.assertIn(f'Plugin \'{plugin_ref}123\' not found', str(response.content, 'utf8'))
|
||||
|
||||
# Inactive plugin
|
||||
response = self.get(url, expected_code=400)
|
||||
self.assertIn(f'Plugin \'{plugin_ref}\' is not enabled', str(response.content, 'utf8'))
|
||||
|
||||
# Active plugin
|
||||
self.do_activate_plugin()
|
||||
|
||||
# Print one part
|
||||
self.get(url, expected_code=200)
|
||||
|
||||
# Print multiple parts
|
||||
self.get(self.do_url(Part.objects.all()[:2], plugin_ref, label), expected_code=200)
|
||||
|
||||
# Print multiple parts without a plugin
|
||||
self.get(self.do_url(Part.objects.all()[:2], None, label), expected_code=200)
|
||||
|
||||
# Print multiple parts without a plugin in debug mode
|
||||
InvenTreeSetting.set_setting('REPORT_DEBUG_MODE', True, None)
|
||||
response = self.get(self.do_url(Part.objects.all()[:2], None, label), expected_code=200)
|
||||
self.assertIn('@page', str(response.content))
|
||||
|
||||
# Print no part
|
||||
self.get(self.do_url(None, plugin_ref, label), expected_code=400)
|
||||
|
||||
def test_printing_endpoints(self):
|
||||
"""Cover the endpoints not covered by `test_printing_process`"""
|
||||
plugin_ref = 'samplelabel'
|
||||
|
||||
# Activate the label components
|
||||
apps.get_app_config('label').create_labels()
|
||||
self.do_activate_plugin()
|
||||
|
||||
def run_print_test(label, qs, url_name, url_single):
|
||||
"""Run tests on single and multiple page printing
|
||||
|
||||
Args:
|
||||
label (_type_): class of the label
|
||||
qs (_type_): class of the base queryset
|
||||
url_name (_type_): url for endpoints
|
||||
url_single (_type_): item lookup reference
|
||||
"""
|
||||
label = label.objects.first()
|
||||
qs = qs.objects.all()
|
||||
|
||||
# List endpoint
|
||||
self.get(self.do_url(None, None, None, f'{url_name}-list', url_single), expected_code=200)
|
||||
|
||||
# List endpoint with filter
|
||||
self.get(self.do_url(qs[:2], None, None, f'{url_name}-list', url_single, invalid=True), expected_code=200)
|
||||
|
||||
# Single page printing
|
||||
self.get(self.do_url(qs[:1], plugin_ref, label, f'{url_name}-print', url_single), expected_code=200)
|
||||
|
||||
# Multi page printing
|
||||
self.get(self.do_url(qs[:2], plugin_ref, label, f'{url_name}-print', url_single), expected_code=200)
|
||||
|
||||
# Test StockItemLabels
|
||||
run_print_test(StockItemLabel, StockItem, 'api-stockitem-label', 'item')
|
||||
|
||||
# Test StockLocationLabels
|
||||
run_print_test(StockLocationLabel, StockLocation, 'api-stocklocation-label', 'location')
|
||||
|
||||
# Test PartLabels
|
||||
run_print_test(PartLabel, Part, 'api-part-label', 'part')
|
@ -120,7 +120,7 @@ class CustomPanelSample(PanelMixin, SettingsMixin, InvenTreePlugin):
|
||||
'icon': 'fa-user',
|
||||
'content_template': 'panel_demo/childless.html', # Note that the panel content is rendered using a template file!
|
||||
})
|
||||
except:
|
||||
except: # pragma: no cover
|
||||
pass
|
||||
|
||||
return panels
|
||||
|
18
InvenTree/plugin/samples/integration/label_sample.py
Normal file
18
InvenTree/plugin/samples/integration/label_sample.py
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
from plugin import InvenTreePlugin
|
||||
from plugin.mixins import LabelPrintingMixin
|
||||
|
||||
|
||||
class SampleLabelPrinter(LabelPrintingMixin, InvenTreePlugin):
|
||||
"""
|
||||
Sample plugin which provides a 'fake' label printer endpoint
|
||||
"""
|
||||
|
||||
NAME = "Label Printer"
|
||||
SLUG = "samplelabel"
|
||||
TITLE = "Sample Label Printer"
|
||||
DESCRIPTION = "A sample plugin which provides a (fake) label printer interface"
|
||||
VERSION = "0.1"
|
||||
|
||||
def print_label(self, label, **kwargs):
|
||||
print("OK PRINTING")
|
Loading…
Reference in New Issue
Block a user