From 346d7e0a700eefdcd706e0a14ce5066da8fa7a3e Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 23 Jul 2026 13:15:10 +1200 Subject: [PATCH] Improvements to `PrintingOptionsSerializer` (#12434) * Fixed issue with default `driver_options on `InvenTreeLabelPlugin` * Provide current `machine` to `LabelPrinterBaseDriver.get_printing_options_serializer` --- .../machine/machine_types/label_printer.py | 4 + src/backend/InvenTree/machine/tests.py | 102 ++++++++++++++++-- .../builtin/labels/inventree_machine.py | 5 +- .../plugin/testing/label_machines.py | 48 +++++++++ 4 files changed, 149 insertions(+), 10 deletions(-) diff --git a/src/backend/InvenTree/machine/machine_types/label_printer.py b/src/backend/InvenTree/machine/machine_types/label_printer.py index 47f108ffc7..e8b1e28129 100644 --- a/src/backend/InvenTree/machine/machine_types/label_printer.py +++ b/src/backend/InvenTree/machine/machine_types/label_printer.py @@ -207,6 +207,10 @@ class LabelPrinterBaseDriver(BaseDriver): ``` """ + def __init__(self, machine=None, *args, **kwargs): + """Initialize the printing options serializer.""" + super().__init__(*args, **kwargs) + copies = serializers.IntegerField( default=1, label=_('Copies'), diff --git a/src/backend/InvenTree/machine/tests.py b/src/backend/InvenTree/machine/tests.py index abd7f42211..2feeb6c12e 100755 --- a/src/backend/InvenTree/machine/tests.py +++ b/src/backend/InvenTree/machine/tests.py @@ -62,7 +62,7 @@ class TestDriverMachineInterface(TestMachineRegistryMixin, TestCase): # Check state now self.assertEqual(len(registry.get_machine_types()), 1) - self.assertEqual(len(registry.get_driver_types()), 3) + self.assertEqual(len(registry.get_driver_types()), 4) self.assertEqual(len(registry.get_machines()), 0) # Check for expected machine registry errors @@ -79,6 +79,7 @@ class TestDriverMachineInterface(TestMachineRegistryMixin, TestCase): for slug in [ 'sample-printer-driver', 'test-label-printer-api', + 'test-label-printer-options', 'test-label-printer-error', ]: instance = registry.get_driver_instance(slug) @@ -187,23 +188,22 @@ class TestLabelPrinterMachineType(InvenTreeAPITestCase): else: self.assertIsNone(machine) - def create_machine(self): + def create_machine(self, driver_key): """Create a new label printing machine.""" registry.initialize(main=True) PLG_KEY = 'label-printer-test-plugin' - DRIVER_KEY = 'test-label-printer-api' # Ensure that the driver is initialized plg_registry.set_plugin_state(PLG_KEY, True) - driver = registry.get_driver_instance(DRIVER_KEY) + driver = registry.get_driver_instance(driver_key) self.assertIsNotNone(driver) machine_config = MachineConfig.objects.create( name='Test Label Printer', machine_type='label-printer', - driver=DRIVER_KEY, + driver=driver_key, active=True, ) @@ -218,7 +218,7 @@ class TestLabelPrinterMachineType(InvenTreeAPITestCase): """Test arbitrary function calls against a machine.""" from machine.registry import call_machine_function - machine = self.create_machine() + machine = self.create_machine('test-label-printer-api') with self.assertRaises(AttributeError): call_machine_function(machine.pk, 'fake_function', custom_arg=123) @@ -231,7 +231,7 @@ class TestLabelPrinterMachineType(InvenTreeAPITestCase): """Test the print label method.""" plugin_ref = 'inventreelabelmachine' - machine = self.create_machine() + machine = self.create_machine('test-label-printer-api') # setup the label app apps.get_app_config('report').create_default_labels() @@ -297,9 +297,95 @@ class TestLabelPrinterMachineType(InvenTreeAPITestCase): self.assertIn('is not a valid choice', str(response.data['machine'])) + def test_printing_options_serializer(self): + """Test the printing options serializer.""" + plugin_ref = 'inventreelabelmachine' + + machine = self.create_machine('test-label-printer-options') + machine.set_setting('LABEL', 'D', '18') + + # setup the label app + apps.get_app_config('report').create_default_labels() + plg_registry.reload_plugins() + + config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config()) + config.active = True + config.save() + + parts = Part.objects.all()[:2] + template = LabelTemplate.objects.filter(enabled=True, model_type='part').first() + assert template + + url = reverse('api-label-print') + + # Test print using machine defaults (LABEL: 18) + with self.assertLogs('inventree', level='WARNING') as cm: + self.post( + url, + { + 'plugin': config.key, + 'items': [a.pk for a in parts], + 'template': template.pk, + 'machine': str(machine.pk), + }, + expected_code=201, + ) + + # Check for expected messages + messages = [ + 'Printing Label: TestingLabelPrinterDriverOptions', + f'machine: {machine.pk}', + f'label: {template.pk}', + f'item: {parts[0].pk}', + f'item: {parts[1].pk}', + 'options: label: 18', + ] + + for message in messages: + result = False + for item in cm.records: + if message in str(item): + result = True + break + + self.assertTrue(result, f'Message not found: {message}') + + # Test print using print options (label: 24) + with self.assertLogs('inventree', level='WARNING') as cm: + self.post( + url, + { + 'plugin': config.key, + 'items': [a.pk for a in parts], + 'template': template.pk, + 'machine': str(machine.pk), + 'driver_options': {'label': '24'}, + }, + expected_code=201, + ) + + # Check for expected messages + messages = [ + 'Printing Label: TestingLabelPrinterDriverOptions', + f'machine: {machine.pk}', + f'label: {template.pk}', + f'item: {parts[0].pk}', + f'item: {parts[1].pk}', + 'options: label: 24', + ] + + for message in messages: + result = False + for item in cm.records: + if message in str(item): + result = True + break + + self.assertTrue(result, f'Message not found: {message}') + def test_location_invalid_pk(self): """Test that location property returns None for an invalid PK without raising.""" - machine = self.create_machine() + machine = self.create_machine('test-label-printer-api') machine.set_setting('LOCATION', 'M', 999999) diff --git a/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py b/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py index 42657d617c..2d5510b2c8 100644 --- a/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py +++ b/src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py @@ -181,16 +181,17 @@ class InvenTreeLabelPlugin(LabelPrintingMixin, InvenTreePlugin): label=_('Options'), depends_on=['machine'], field_serializer='get_driver_options', + default={}, required=False, ) def get_driver_options(self, fields): """Returns the selected machines serializer.""" - _, driver = get_machine_and_driver(fields['machine']) + machine, driver = get_machine_and_driver(fields['machine']) if driver is None: return None return driver.get_printing_options_serializer( - self.context['request'], context=self.context + self.context['request'], context=self.context, machine=machine ) diff --git a/src/backend/InvenTree/plugin/testing/label_machines.py b/src/backend/InvenTree/plugin/testing/label_machines.py index 084e0519d0..54967d87a7 100644 --- a/src/backend/InvenTree/plugin/testing/label_machines.py +++ b/src/backend/InvenTree/plugin/testing/label_machines.py @@ -1,6 +1,7 @@ """Plugins for testing label machines.""" import structlog +from rest_framework import serializers from machine.machine_type import BaseDriver from plugin import InvenTreePlugin @@ -46,6 +47,52 @@ class TestingLabelPrinterDriver(LabelPrinterBaseDriver): return x * y +class TestingLabelPrinterDriverOptions(LabelPrinterBaseDriver): + """Test driver for label printing.""" + + SLUG = 'test-label-printer-options' + NAME = 'Test label printer options' + DESCRIPTION = 'This is a test label printer driver for testing.' + + MACHINE_SETTINGS = { + 'LABEL': { + 'name': 'Label Media', + 'description': 'Select label media type', + 'choices': [('12', '12mm'), ('18', '18mm'), ('24', '24mm')], + 'default': '12', + 'required': True, + } + } + + def print_label(self, machine, label, item, **kwargs) -> None: + """Override print_label.""" + # Simply output some warning messages, + # which we can check for in the unit test + logger.warn('Printing Label: TestingLabelPrinterDriverOptions') + logger.warn(f'machine: {machine.pk}') + logger.warn(f'label: {label.pk}') + logger.warn(f'item: {item.pk}') + + for k, v in kwargs['printing_options'].items(): + logger.warn(f'options: {k}: {v}') + + class PrintingOptionsSerializer(LabelPrinterBaseDriver.PrintingOptionsSerializer): + """Printing options serializer for print driver testing.""" + + def __init__(self, machine=None, *args, **kwargs): + """Initialize the printing options serializer.""" + super().__init__(*args, **kwargs) + + self.fields['label'].default = machine.get_setting('LABEL', 'D') + + label = serializers.ChoiceField( + label='Label Media', + help_text='Select label media type', + choices=[('12', '12mm'), ('18', '18mm'), ('24', '24mm')], + default='12', + ) + + class TestingLabelPrinterDriverError1(LabelPrinterBaseDriver): """Test driver for label printing.""" @@ -93,6 +140,7 @@ class LabelPrinterMachineTest(MachineDriverMixin, SettingsMixin, InvenTreePlugin """Return a list of drivers registered by this plugin.""" return [ TestingLabelPrinterDriver, + TestingLabelPrinterDriverOptions, TestingLabelPrinterDriverError1, TestingLabelPrinterDriverError2, TestingLabelPrinterDriverNotImplemented,