diff --git a/.gitignore b/.gitignore index 38dea68006..03507b3f37 100644 --- a/.gitignore +++ b/.gitignore @@ -40,9 +40,11 @@ inventree-demo-dataset/ inventree-data/ dummy_image.* _tmp.csv -inventree/label.pdf -inventree/label.png -inventree/my_special* +InvenTree/label.pdf +InvenTree/label.png +label.pdf +label.png +InvenTree/my_special* _tests*.txt # Local static and media file storage (only when running in development mode) diff --git a/InvenTree/InvenTree/api_version.py b/InvenTree/InvenTree/api_version.py index b3df418090..e344b81d34 100644 --- a/InvenTree/InvenTree/api_version.py +++ b/InvenTree/InvenTree/api_version.py @@ -2,11 +2,14 @@ # InvenTree API version -INVENTREE_API_VERSION = 129 +INVENTREE_API_VERSION = 130 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v130 -> 2023-07-14 : https://github.com/inventree/InvenTree/pull/5251 + - Refactor label printing interface + v129 -> 2023-07-06 : https://github.com/inventree/InvenTree/pull/5189 - Changes 'serial_lte' and 'serial_gte' stock filters to point to 'serial_int' field diff --git a/InvenTree/common/models.py b/InvenTree/common/models.py index b4c1dd8032..2addc8c8cc 100644 --- a/InvenTree/common/models.py +++ b/InvenTree/common/models.py @@ -1794,7 +1794,7 @@ class InvenTreeSetting(BaseInvenTreeSetting): def label_printer_options(): """Build a list of available label printer options.""" - printers = [('', _('No Printer (Export to PDF)'))] + printers = [] label_printer_plugins = registry.with_mixin('labels') if label_printer_plugins: printers.extend([(p.slug, p.name + ' - ' + p.human_name) for p in label_printer_plugins]) diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index 724c00e4d5..d833e64b71 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -2,7 +2,7 @@ from django.conf import settings from django.core.exceptions import FieldError, ValidationError -from django.http import HttpResponse, JsonResponse +from django.http import JsonResponse from django.urls import include, path, re_path from django.utils.decorators import method_decorator from django.views.decorators.cache import cache_page, never_cache @@ -18,9 +18,8 @@ import label.serializers from InvenTree.api import MetadataView from InvenTree.filters import InvenTreeSearchFilter from InvenTree.mixins import ListAPI, RetrieveAPI, RetrieveUpdateDestroyAPI -from InvenTree.tasks import offload_task from part.models import Part -from plugin.base.label import label as plugin_label +from plugin.builtin.labels.inventree_label import InvenTreeLabelPlugin from plugin.registry import registry from stock.models import StockItem, StockLocation @@ -167,9 +166,10 @@ class LabelPrintMixin(LabelFilterMixin): plugin_key = request.query_params.get('plugin', None) - # No plugin provided, and that's OK + # No plugin provided! if plugin_key is None: - return None + # Default to the builtin label printing plugin + plugin_key = InvenTreeLabelPlugin.NAME.lower() plugin = registry.get_plugin(plugin_key) @@ -189,96 +189,21 @@ class LabelPrintMixin(LabelFilterMixin): if len(items_to_print) == 0: # No valid items provided, return an error message - raise ValidationError('No valid objects provided to label template') - outputs = [] + # Label template + label = self.get_object() - # In debug mode, generate single HTML output, rather than PDF - debug_mode = common.models.InvenTreeSetting.get_setting('REPORT_DEBUG_MODE', cache=False) + # At this point, we offload the label(s) to the selected plugin. + # The plugin is responsible for handling the request and returning a response. - label_name = "label.pdf" - - label_names = [] - label_instances = [] - - # Merge one or more PDF files into a single download - for item in items_to_print: - label = self.get_object() - label.object_to_print = item - - label_name = label.generate_filename(request) - - label_names.append(label_name) - label_instances.append(label) - - if debug_mode and plugin is None: - # Note that debug mode is only supported when not using a plugin - outputs.append(label.render_as_string(request)) - else: - outputs.append(label.render(request)) - - if not label_name.endswith(".pdf"): - label_name += ".pdf" - - if plugin is not None: - """Label printing is to be handled by a plugin, rather than being exported to PDF. - - In this case, we do the following: - - - Individually generate each label, exporting as an image file - - Pass all the images through to the label printing plugin - - Return a JSON response indicating that the printing has been offloaded - """ - - for idx, output in enumerate(outputs): - """For each output, we generate a temporary image file, which will then get sent to the printer.""" - - # Generate PDF data for the label - pdf = output.get_document().write_pdf() - - # Offload a background task to print the provided label - offload_task( - plugin_label.print_label, - plugin.plugin_slug(), - pdf, - filename=label_names[idx], - label_instance=label_instances[idx], - user=request.user, - ) - - return JsonResponse({ - 'plugin': plugin.plugin_slug(), - 'labels': label_names, - }) - - elif debug_mode: - """Contatenate all rendered templates into a single HTML string, and return the string as a HTML response.""" - - html = "\n".join(outputs) - - return HttpResponse(html) + result = plugin.print_labels(label, items_to_print, request) + if isinstance(result, JsonResponse): + result['plugin'] = plugin.plugin_slug() + return result else: - """Concatenate all rendered pages into a single PDF object, and return the resulting document!""" - - pages = [] - - for output in outputs: - doc = output.get_document() - for page in doc.pages: - pages.append(page) - - pdf = outputs[0].get_document().copy(pages).write_pdf() - - inline = common.models.InvenTreeUserSetting.get_setting('LABEL_INLINE', user=request.user, cache=False) - - return InvenTree.helpers.DownloadFile( - pdf, - label_name, - content_type='application/pdf', - inline=inline - ) + raise ValidationError(f"Plugin '{plugin.plugin_slug()}' returned invalid response type '{type(result)}'") class StockItemLabelMixin: diff --git a/InvenTree/label/migrations/0012_labeloutput.py b/InvenTree/label/migrations/0012_labeloutput.py new file mode 100644 index 0000000000..3a69fb9a9b --- /dev/null +++ b/InvenTree/label/migrations/0012_labeloutput.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.20 on 2023-07-14 11:55 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import label.models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('label', '0011_auto_20230623_2158'), + ] + + operations = [ + migrations.CreateModel( + name='LabelOutput', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('label', models.FileField(unique=True, upload_to=label.models.rename_label_output)), + ('created', models.DateField(auto_now_add=True)), + ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/InvenTree/label/models.py b/InvenTree/label/models.py index 926fdf5e42..fef3395ed9 100644 --- a/InvenTree/label/models.py +++ b/InvenTree/label/models.py @@ -6,6 +6,7 @@ import os import sys from django.conf import settings +from django.contrib.auth.models import User from django.core.validators import FileExtensionValidator, MinValueValidator from django.db import models from django.template import Context, Template @@ -39,6 +40,13 @@ def rename_label(instance, filename): return os.path.join('label', 'template', instance.SUBDIR, filename) +def rename_label_output(instance, filename): + """Place the label output file into the correct subdirectory.""" + filename = os.path.basename(filename) + + return os.path.join('label', 'output', filename) + + def validate_stock_item_filters(filters): """Validate query filters for the StockItemLabel model""" filters = validateFilterString(filters, model=stock.models.StockItem) @@ -235,6 +243,36 @@ class LabelTemplate(MetadataMixin, models.Model): ) +class LabelOutput(models.Model): + """Class representing a label output file + + 'Printing' a label may generate a file object (such as PDF) + which is made available for download. + + Future work will offload this task to the background worker, + and provide a 'progress' bar for the user. + """ + + # File will be stored in a subdirectory + label = models.FileField( + upload_to=rename_label_output, + unique=True, blank=False, null=False, + ) + + # Creation date of label output + created = models.DateField( + auto_now_add=True, + editable=False, + ) + + # User who generated the label + user = models.ForeignKey( + User, + on_delete=models.SET_NULL, + blank=True, null=True, + ) + + class StockItemLabel(LabelTemplate): """Template for printing StockItem labels.""" diff --git a/InvenTree/label/tasks.py b/InvenTree/label/tasks.py new file mode 100644 index 0000000000..fc486cafab --- /dev/null +++ b/InvenTree/label/tasks.py @@ -0,0 +1,16 @@ +"""Background tasks for the label app""" + +from datetime import timedelta + +from django.utils import timezone + +from InvenTree.tasks import ScheduledTask, scheduled_task +from label.models import LabelOutput + + +@scheduled_task(ScheduledTask.DAILY) +def cleanup_old_label_outputs(): + """Remove old label outputs from the database""" + + # Remove any label outputs which are older than 30 days + LabelOutput.objects.filter(created__lte=timezone.now() - timedelta(days=5)).delete() diff --git a/InvenTree/label/tests.py b/InvenTree/label/tests.py index 6a94328605..447a0acd91 100644 --- a/InvenTree/label/tests.py +++ b/InvenTree/label/tests.py @@ -1,17 +1,21 @@ """Tests for labels""" import io +import json from django.apps import apps from django.conf import settings from django.core.exceptions import ValidationError from django.core.files.base import ContentFile +from django.http import JsonResponse from django.urls import reverse from common.models import InvenTreeSetting from InvenTree.helpers import validateFilterString from InvenTree.unit_test import InvenTreeAPITestCase +from label.models import LabelOutput from part.models import Part +from plugin.registry import registry from stock.models import StockItem from .models import PartLabel, StockItemLabel, StockLocationLabel @@ -77,7 +81,16 @@ class LabelTest(InvenTreeAPITestCase): for label in labels: url = reverse('api-part-label-print', kwargs={'pk': label.pk}) - self.get(f'{url}?parts={part.pk}', expected_code=200) + + # Check that label printing returns the correct response type + response = self.get(f'{url}?parts={part.pk}', expected_code=200) + self.assertIsInstance(response, JsonResponse) + data = json.loads(response.content) + + self.assertIn('message', data) + self.assertIn('file', data) + label_file = data['file'] + self.assertIn('/media/label/output/', label_file) def test_print_part_label(self): """Actually 'print' a label, and ensure that the correct information is contained.""" @@ -115,21 +128,33 @@ class LabelTest(InvenTreeAPITestCase): # Ensure we are in "debug" mode (so the report is generated as HTML) InvenTreeSetting.set_setting('REPORT_ENABLE', True, None) - InvenTreeSetting.set_setting('REPORT_DEBUG_MODE', True, None) - # Print via the API + # Set the 'debug' setting for the plugin + plugin = registry.get_plugin('inventreelabel') + plugin.set_setting('DEBUG', True) + + # Print via the API (Note: will default to the builtin plugin if no plugin supplied) url = reverse('api-part-label-print', kwargs={'pk': label.pk}) - response = self.get(f'{url}?parts=1', expected_code=200) + part_pk = Part.objects.first().pk - content = str(response.content) + response = self.get(f'{url}?parts={part_pk}', expected_code=200) + data = json.loads(response.content) + self.assertIn('file', data) + + # Find the generated file + output = LabelOutput.objects.last() + + # Open the file and read data + with open(output.label.path, 'r') as f: + content = f.read() # Test that each element has been rendered correctly self.assertIn("part: 1 - M2x4 LPHS", content) - self.assertIn('data: {"part": 1}', content) + self.assertIn(f'data: {{"part": {part_pk}}}', content) self.assertIn("http://testserver/part/1/", content) - self.assertIn("image: /static/img/blank_image.png", content) - self.assertIn("logo: /static/img/inventree.png", content) + self.assertIn("img/blank_image.png", content) + self.assertIn("img/inventree.png", content) def test_metadata(self): """Unit tests for the metadata field.""" diff --git a/InvenTree/plugin/base/label/label.py b/InvenTree/plugin/base/label/label.py index e8d791b87c..542cc60fa6 100644 --- a/InvenTree/plugin/base/label/label.py +++ b/InvenTree/plugin/base/label/label.py @@ -5,30 +5,26 @@ import logging from django.conf import settings from django.utils.translation import gettext_lazy as _ -import pdf2image - import common.notifications -from common.models import InvenTreeSetting from InvenTree.exceptions import log_error from plugin.registry import registry logger = logging.getLogger('inventree') -def print_label(plugin_slug: str, pdf_data, filename=None, label_instance=None, user=None): +def print_label(plugin_slug: str, **kwargs): """Print label with the provided plugin. This task is nominally handled by the background worker. If the printing fails (throws an exception) then the user is notified. - Args: + Arguments: plugin_slug (str): The unique slug (key) of the plugin. - pdf_data: Binary PDF data. - filename: The intended name of the printed label. Defaults to None. - label_instance (Union[LabelTemplate, None], optional): The template instance that should be printed. Defaults to None. - user (Union[User, None], optional): User that should be informed of errors. Defaults to None. + + kwargs: + passed through to the plugin.print_label() method """ - logger.info(f"Plugin '{plugin_slug}' is printing a label '{filename}'") + logger.info(f"Plugin '{plugin_slug}' is printing a label") plugin = registry.get_plugin(plugin_slug) @@ -36,43 +32,30 @@ def print_label(plugin_slug: str, pdf_data, filename=None, label_instance=None, logger.error(f"Could not find matching plugin for '{plugin_slug}'") return - # In addition to providing a .pdf image, we'll also provide a .png file - dpi = InvenTreeSetting.get_setting('LABEL_DPI', 300) - png_file = pdf2image.convert_from_bytes( - pdf_data, - dpi=dpi, - )[0] - try: - plugin.print_label( - pdf_data=pdf_data, - png_file=png_file, - filename=filename, - label_instance=label_instance, - width=label_instance.width, - height=label_instance.height, - user=user - ) + plugin.print_label(**kwargs) except Exception as e: # pragma: no cover # Plugin threw an error - notify the user who attempted to print - ctx = { 'name': _('Label printing failed'), 'message': str(e), } - # Log an error message to the database - log_error('plugin.print_label') - logger.error(f"Label printing failed: Sending notification to user '{user}'") # pragma: no cover + user = kwargs.get('user', None) - # Throw an error against the plugin instance - common.notifications.trigger_notification( - plugin.plugin_config(), - 'label.printing_failed', - targets=[user], - context=ctx, - delivery_methods={common.notifications.UIMessageNotification, }, - ) + if user: + # Log an error message to the database + log_error('plugin.print_label') + logger.error(f"Label printing failed: Sending notification to user '{user}'") # pragma: no cover + + # Throw an error against the plugin instance + common.notifications.trigger_notification( + plugin.plugin_config(), + 'label.printing_failed', + targets=[user], + context=ctx, + delivery_methods={common.notifications.UIMessageNotification, }, + ) if settings.TESTING: # If we are in testing mode, we want to know about this exception diff --git a/InvenTree/plugin/base/label/mixins.py b/InvenTree/plugin/base/label/mixins.py index b439d28c97..0186e69f57 100644 --- a/InvenTree/plugin/base/label/mixins.py +++ b/InvenTree/plugin/base/label/mixins.py @@ -1,5 +1,13 @@ """Plugin mixin classes for label plugins.""" +from django.http import JsonResponse + +import pdf2image + +from common.models import InvenTreeSetting +from InvenTree.tasks import offload_task +from label.models import LabelTemplate +from plugin.base.label import label as plugin_label from plugin.helpers import MixinNotImplementedError @@ -8,9 +16,16 @@ class LabelPrintingMixin: Each plugin must provide a NAME attribute, which is used to uniquely identify the printer. - The plugin must also implement the print_label() function + The plugin *must* also implement the print_label() function for rendering an individual label + + Note that the print_labels() function can also be overridden to provide custom behaviour. """ + # If True, the print_label() method will block until the label is printed + # If False, the offload_label() method will be called instead + # By default, this is False, which means that labels will be printed in the background + BLOCKING_PRINT = False + class MixinMeta: """Meta options for this mixin.""" MIXIN_NAME = 'Label printing' @@ -20,17 +35,124 @@ class LabelPrintingMixin: super().__init__() self.add_mixin('labels', True, __class__) + def render_to_pdf(self, label: LabelTemplate, request, **kwargs): + """Render this label to PDF format + + Arguments: + label: The LabelTemplate object to render + request: The HTTP request object which triggered this print job + """ + return label.render(request) + + def render_to_html(self, label: LabelTemplate, request, **kwargs): + """Render this label to HTML format + + Arguments: + label: The LabelTemplate object to render + request: The HTTP request object which triggered this print job + """ + return label.render_as_string(request) + + def render_to_png(self, label: LabelTemplate, request=None, **kwargs): + """Render this label to PNG format""" + + # Check if pdf data is provided + pdf_data = kwargs.get('pdf_data', None) + + if not pdf_data: + pdf_data = self.render_to_pdf(label, request, **kwargs).get_document().write_pdf() + + dpi = kwargs.get( + 'dpi', + InvenTreeSetting.get_setting('LABEL_DPI', 300) + ) + + # Convert to png data + png = pdf2image.convert_from_bytes(pdf_data, dpi=dpi)[0] + return png + + def print_labels(self, label: LabelTemplate, items: list, request, **kwargs): + """Print one or more labels with the provided template and items. + + Arguments: + label: The LabelTemplate object to use for printing + items: The list of database items to print (e.g. StockItem instances) + request: The HTTP request object which triggered this print job + + Returns: + A JSONResponse object which indicates outcome to the user + + The default implementation simply calls print_label() for each label, producing multiple single label output "jobs" + but this can be overridden by the particular plugin. + """ + + try: + user = request.user + except AttributeError: + user = None + + # Generate a label output for each provided item + for item in items: + label.object_to_print = item + filename = label.generate_filename(request) + pdf_file = self.render_to_pdf(label, request, **kwargs) + pdf_data = pdf_file.get_document().write_pdf() + png_file = self.render_to_png(label, request, pdf_data=pdf_data, **kwargs) + + print_args = { + 'pdf_file': pdf_file, + 'pdf_data': pdf_data, + 'png_file': png_file, + 'filename': filename, + 'label_instance': label, + 'item_instance': item, + 'user': user, + 'width': label.width, + 'height': label.height, + } + + if self.BLOCKING_PRINT: + # Blocking print job + self.print_label(**print_args) + else: + # Non-blocking print job + self.offload_label(**print_args) + + # Return a JSON response to the user + return JsonResponse({ + 'success': True, + 'message': f'{len(items)} labels printed', + }) + def print_label(self, **kwargs): - """Callback to print a single label. + """Print a single label (blocking) kwargs: + pdf_file: The PDF file object of the rendered label (WeasyTemplateResponse object) pdf_data: Raw PDF data of the rendered label - png_file: An in-memory PIL image file, rendered at 300dpi + filename: The filename of this PDF label label_instance: The instance of the label model which triggered the print_label() method + item_instance: The instance of the database model against which the label is printed + user: The user who triggered this print job width: The expected width of the label (in mm) height: The expected height of the label (in mm) - filename: The filename of this PDF label - user: The user who printed this label + + Note that the supplied kwargs may be different if the plugin overrides the print_labels() method. """ # Unimplemented (to be implemented by the particular plugin class) raise MixinNotImplementedError('This Plugin must implement a `print_label` method') + + def offload_label(self, **kwargs): + """Offload a single label (non-blocking) + + Instead of immediately printing the label (which is a blocking process), + this method should offload the label to a background worker process. + + Offloads a call to the 'print_label' method (of this plugin) to a background worker. + """ + + offload_task( + plugin_label.print_label, + self.plugin_slug(), + **kwargs + ) diff --git a/InvenTree/plugin/base/label/test_label_mixin.py b/InvenTree/plugin/base/label/test_label_mixin.py index 2c69473dbc..af8f34ea45 100644 --- a/InvenTree/plugin/base/label/test_label_mixin.py +++ b/InvenTree/plugin/base/label/test_label_mixin.py @@ -1,5 +1,6 @@ """Unit tests for the label printing mixin.""" +import json import os from django.apps import apps @@ -7,7 +8,6 @@ from django.urls import reverse from PIL import Image -from common.models import InvenTreeSetting from InvenTree.unit_test import InvenTreeAPITestCase from label.models import PartLabel, StockItemLabel, StockLocationLabel from part.models import Part @@ -77,11 +77,11 @@ class LabelMixinTests(InvenTreeAPITestCase): """Test that the sample printing plugin is installed.""" # Get all label plugins plugins = registry.with_mixin('labels') - self.assertEqual(len(plugins), 1) + self.assertEqual(len(plugins), 2) # But, it is not 'active' plugins = registry.with_mixin('labels', active=True) - self.assertEqual(len(plugins), 0) + self.assertEqual(len(plugins), 1) def test_api(self): """Test that we can filter the API endpoint by mixin.""" @@ -123,8 +123,8 @@ class LabelMixinTests(InvenTreeAPITestCase): } ) - self.assertEqual(len(response.data), 1) - data = response.data[0] + self.assertEqual(len(response.data), 2) + data = response.data[1] self.assertEqual(data['key'], 'samplelabel') def test_printing_process(self): @@ -160,9 +160,10 @@ class LabelMixinTests(InvenTreeAPITestCase): 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)) + + data = json.loads(response.content) + self.assertIn('file', data) # Print no part self.get(self.do_url(None, plugin_ref, label), expected_code=400) diff --git a/InvenTree/plugin/builtin/labels/__init__.py b/InvenTree/plugin/builtin/labels/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/InvenTree/plugin/builtin/labels/inventree_label.py b/InvenTree/plugin/builtin/labels/inventree_label.py new file mode 100644 index 0000000000..e90f0a5a67 --- /dev/null +++ b/InvenTree/plugin/builtin/labels/inventree_label.py @@ -0,0 +1,96 @@ +"""Default label printing plugin (supports PDF generation)""" + +from django.core.files.base import ContentFile +from django.http import JsonResponse +from django.utils.translation import gettext_lazy as _ + +from label.models import LabelOutput, LabelTemplate +from plugin import InvenTreePlugin +from plugin.mixins import LabelPrintingMixin, SettingsMixin + + +class InvenTreeLabelPlugin(LabelPrintingMixin, SettingsMixin, InvenTreePlugin): + """Builtin plugin for label printing. + + This plugin merges the selected labels into a single PDF file, + which is made available for download. + """ + + NAME = "InvenTreeLabel" + TITLE = _("InvenTree PDF label printer") + DESCRIPTION = _("Provides native support for printing PDF labels") + VERSION = "1.0.0" + AUTHOR = _("InvenTree contributors") + + BLOCKING_PRINT = True + + SETTINGS = { + 'DEBUG': { + 'name': _('Debug mode'), + 'description': _('Enable debug mode - returns raw HTML instead of PDF'), + 'validator': bool, + 'default': False, + }, + } + + def print_labels(self, label: LabelTemplate, items: list, request, **kwargs): + """Handle printing of multiple labels + + - Label outputs are concatenated together, and we return a single PDF file. + - If DEBUG mode is enabled, we return a single HTML file. + """ + + debug = self.get_setting('DEBUG') + + outputs = [] + output_file = None + + for item in items: + + label.object_to_print = item + + outputs.append(self.print_label(label, request, debug=debug, **kwargs)) + + if self.get_setting('DEBUG'): + html = '\n'.join(outputs) + + output_file = ContentFile(html, 'labels.html') + else: + pages = [] + + # Following process is required to stitch labels together into a single PDF + for output in outputs: + doc = output.get_document() + + for page in doc.pages: + pages.append(page) + + pdf = outputs[0].get_document().copy(pages).write_pdf() + + # Create label output file + output_file = ContentFile(pdf, 'labels.pdf') + + # Save the generated file to the database + output = LabelOutput.objects.create( + label=output_file, + user=request.user + ) + + return JsonResponse({ + 'file': output.label.url, + 'success': True, + 'message': f'{len(items)} labels generated' + }) + + def print_label(self, label: LabelTemplate, request, **kwargs): + """Handle printing of a single label. + + Returns either a PDF or HTML output, depending on the DEBUG setting. + """ + + debug = kwargs.get('debug', self.get_setting('DEBUG')) + + if debug: + return self.render_to_html(label, request, **kwargs) + else: + return self.render_to_pdf(label, request, **kwargs) diff --git a/InvenTree/plugin/samples/integration/label_sample.py b/InvenTree/plugin/samples/integration/label_sample.py index 61ec11b829..016026fc35 100644 --- a/InvenTree/plugin/samples/integration/label_sample.py +++ b/InvenTree/plugin/samples/integration/label_sample.py @@ -14,21 +14,22 @@ class SampleLabelPrinter(LabelPrintingMixin, InvenTreePlugin): SLUG = "samplelabel" TITLE = "Sample Label Printer" DESCRIPTION = "A sample plugin which provides a (fake) label printer interface" - VERSION = "0.2" + AUTHOR = "InvenTree contributors" + VERSION = "0.3.0" def print_label(self, **kwargs): """Sample printing step. Normally here the connection to the printer and transfer of the label would take place. """ + # Test that the expected kwargs are present print(f"Printing Label: {kwargs['filename']} (User: {kwargs['user']})") - print(f"Width: {kwargs['width']} x Height: {kwargs['height']}") pdf_data = kwargs['pdf_data'] - png_file = kwargs['png_file'] + png_file = self.render_to_png(label=None, pdf_data=pdf_data) - filename = kwargs['filename'] + filename = 'label.pdf' # Dump the PDF to a local file with open(filename, 'wb') as pdf_out: diff --git a/InvenTree/templates/js/translated/api.js b/InvenTree/templates/js/translated/api.js index c9ecaff915..f5b4bbeda4 100644 --- a/InvenTree/templates/js/translated/api.js +++ b/InvenTree/templates/js/translated/api.js @@ -62,12 +62,12 @@ function inventreeGet(url, filters={}, options={}) { url: url, type: 'GET', data: filters, - dataType: 'json', - contentType: 'application/json', + dataType: options.dataType || 'json', + contentType: options.contentType || 'application/json', async: (options.async == false) ? false : true, - success: function(response) { + success: function(response, status, xhr) { if (options.success) { - options.success(response); + options.success(response, status, xhr); } }, error: function(xhr, ajaxOptions, thrownError) { diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index cf3d4c90aa..b0b849f6ca 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -59,7 +59,6 @@ function selectLabel(labels, items, options={}) {