From f92aa07f82c1dddcb0095f9cfdcc1f031d3377b6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 23 Mar 2022 12:01:50 +1100 Subject: [PATCH 01/19] Fix null values in build order serial number field --- InvenTree/templates/js/translated/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/build.js b/InvenTree/templates/js/translated/build.js index 5b416b4d22..46f7f32e42 100644 --- a/InvenTree/templates/js/translated/build.js +++ b/InvenTree/templates/js/translated/build.js @@ -213,7 +213,7 @@ function createBuildOutput(build_id, options) { success: function(data) { if (data.next) { fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${data.next}`; - } else { + } else if (data.latest) { fields.serial_numbers.placeholder = `{% trans "Latest serial number" %}: ${data.latest}`; } }, From b720c2e4318a2a70d71e8ad19613b075c4be321d Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 11:49:34 +1100 Subject: [PATCH 02/19] Check for empty string when rendering dates --- InvenTree/part/templatetags/inventree_extras.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index 3deab1ecd1..dd20d49e39 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -57,6 +57,13 @@ def render_date(context, date_object): return None if type(date_object) == str: + + date_object = date_object.strip() + + # Check for empty string + if len(date_object) == 0: + return None + # If a string is passed, first convert it to a datetime date_object = date.fromisoformat(date_object) From 31b71fe29fd9f0179eb733ec9f8bac3a5c60d080 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 11:56:39 +1100 Subject: [PATCH 03/19] Catch error when incorrect date format string is passed --- InvenTree/part/templatetags/inventree_extras.py | 10 +++++++++- InvenTree/plugin/events.py | 9 +++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/InvenTree/part/templatetags/inventree_extras.py b/InvenTree/part/templatetags/inventree_extras.py index dd20d49e39..dc93e00efa 100644 --- a/InvenTree/part/templatetags/inventree_extras.py +++ b/InvenTree/part/templatetags/inventree_extras.py @@ -8,6 +8,7 @@ over and above the built-in Django tags. from datetime import date, datetime import os import sys +import logging from django.utils.html import format_html @@ -31,6 +32,9 @@ from plugin.models import PluginSetting register = template.Library() +logger = logging.getLogger('inventree') + + @register.simple_tag() def define(value, *args, **kwargs): """ @@ -65,7 +69,11 @@ def render_date(context, date_object): return None # If a string is passed, first convert it to a datetime - date_object = date.fromisoformat(date_object) + try: + date_object = date.fromisoformat(date_object) + except ValueError: + logger.warning(f"Tried to convert invalid date string: {date_object}") + return None # We may have already pre-cached the date format by calling this already! user_date_format = context.get('user_date_format', None) diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index 049c8626c5..b75f5f92bb 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -59,15 +59,24 @@ def register_event(event, *args, **kwargs): logger.debug(f"Registering triggered event: '{event}'") + print("register_event") + # Determine if there are any plugins which are interested in responding if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS'): + print("checking plugins") + with transaction.atomic(): for slug, plugin in registry.plugins.items(): + print("slug:", slug) + print("plugin:", plugin) + if plugin.mixin_enabled('events'): + print("events are enabled for this plugin!") + config = plugin.plugin_config() if config and config.active: From 66487817dd06c72eb73fc4c5848cef6550ed1136 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 12:03:16 +1100 Subject: [PATCH 04/19] Remove debug messages --- InvenTree/plugin/events.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index b75f5f92bb..f0e2458810 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -70,13 +70,8 @@ def register_event(event, *args, **kwargs): for slug, plugin in registry.plugins.items(): - print("slug:", slug) - print("plugin:", plugin) - if plugin.mixin_enabled('events'): - print("events are enabled for this plugin!") - config = plugin.plugin_config() if config and config.active: From bd0bf4368c4adc9bacc8a637247f13217cd732ee Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 12:08:56 +1100 Subject: [PATCH 05/19] Remove more debug messages --- InvenTree/plugin/events.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index f0e2458810..049c8626c5 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -59,13 +59,9 @@ def register_event(event, *args, **kwargs): logger.debug(f"Registering triggered event: '{event}'") - print("register_event") - # Determine if there are any plugins which are interested in responding if settings.PLUGIN_TESTING or InvenTreeSetting.get_setting('ENABLE_PLUGINS_EVENTS'): - print("checking plugins") - with transaction.atomic(): for slug, plugin in registry.plugins.items(): From 69e9d1625a1c01fa5e77d4190362a379dcca8c09 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 12:51:27 +1100 Subject: [PATCH 06/19] Adds a LabelPrintingMixin plugin class - Enables the implementation of custom label printing plugins - Will be available directly from the "print labels" dialog box --- .../plugin/builtin/integration/mixins.py | 53 +++++++++++++++++++ InvenTree/plugin/mixins/__init__.py | 4 +- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 1e6b7e38b7..e379f974f1 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -393,6 +393,59 @@ class AppMixin: return True +class LabelPrintingMixin: + """ + Mixin which enables direct printing of stock labels. + + Each plugin should provide a PRINTER_NAME attribute, + and also implement the print_label() function + """ + + class MixinMeta: + """ + Meta options for this mixin + """ + MIXIN_NAME = 'Label printing' + + def __init__(self): + super().__init__() + self.add_mixin('labels', 'has_label_printing', __class__) + + @property + def has_label_printing(self): + + if not bool(self.PRINTER_NAME): + raise ValueError("PRINTER_NAME must be defined") + + return True + + PRINTER_NAME = "LabelPrinter" + + def get_printer_name(self): + return self.PRINTER_NAME + + def print_labels(self, labels, **kwargs): + """ + Print multiple labels. + Default implementation is to call print_label() for each label, + but it can be overridden if desired. + """ + for label in labels: + self.print_label(label, **kwargs) + + def print_label(self, label, **kwargs): + """ + Callback to print a single label + + Arguments: + label: A black-and-white pillow Image object + + """ + + # Unimplemented (to be implemented by the particular plugin class) + ... + + class APICallMixin: """ Mixin that enables easier API calls for a plugin diff --git a/InvenTree/plugin/mixins/__init__.py b/InvenTree/plugin/mixins/__init__.py index 8097b0b459..86e5e92f37 100644 --- a/InvenTree/plugin/mixins/__init__.py +++ b/InvenTree/plugin/mixins/__init__.py @@ -2,7 +2,8 @@ Utility class to enable simpler imports """ -from ..builtin.integration.mixins import APICallMixin, AppMixin, SettingsMixin, EventMixin, ScheduleMixin, UrlsMixin, NavigationMixin +from ..builtin.integration.mixins import APICallMixin, AppMixin, LabelPrintingMixin, SettingsMixin, EventMixin, ScheduleMixin, UrlsMixin, NavigationMixin + from ..builtin.action.mixins import ActionMixin from ..builtin.barcode.mixins import BarcodeMixin @@ -10,6 +11,7 @@ __all__ = [ 'APICallMixin', 'AppMixin', 'EventMixin', + 'LabelPrintingMixin', 'NavigationMixin', 'ScheduleMixin', 'SettingsMixin', From 4e041e97b93e1ece316a77bbbe7c9896195aada0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 13:00:03 +1100 Subject: [PATCH 07/19] Catch potential IntegrityError when importing plugins --- InvenTree/plugin/registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/InvenTree/plugin/registry.py b/InvenTree/plugin/registry.py index 2db1beb805..3276c82f45 100644 --- a/InvenTree/plugin/registry.py +++ b/InvenTree/plugin/registry.py @@ -10,12 +10,13 @@ import pathlib import logging import os import subprocess + from typing import OrderedDict from importlib import reload from django.apps import apps from django.conf import settings -from django.db.utils import OperationalError, ProgrammingError +from django.db.utils import OperationalError, ProgrammingError, IntegrityError from django.conf.urls import url, include from django.urls import clear_url_caches from django.contrib import admin @@ -282,6 +283,8 @@ class PluginsRegistry: if not settings.PLUGIN_TESTING: raise error # pragma: no cover plugin_db_setting = None + except (IntegrityError) as error: + logger.error(f"Error initializing plugin: {error}") # Always activate if testing if settings.PLUGIN_TESTING or (plugin_db_setting and plugin_db_setting.active): From 86b5655c5f6f3b0913275fe485fc90b0f0954531 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 13:15:57 +1100 Subject: [PATCH 08/19] Simplify new plugin class --- InvenTree/plugin/builtin/integration/mixins.py | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index e379f974f1..2d56e61929 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -397,8 +397,9 @@ class LabelPrintingMixin: """ Mixin which enables direct printing of stock labels. - Each plugin should provide a PRINTER_NAME attribute, - and also implement the print_label() function + Each plugin must provide a PLUGIN_NAME attribute, which is used to uniquely identify the printer. + + The plugin must also implement the print_label() function """ class MixinMeta: @@ -409,17 +410,7 @@ class LabelPrintingMixin: def __init__(self): super().__init__() - self.add_mixin('labels', 'has_label_printing', __class__) - - @property - def has_label_printing(self): - - if not bool(self.PRINTER_NAME): - raise ValueError("PRINTER_NAME must be defined") - - return True - - PRINTER_NAME = "LabelPrinter" + self.add_mixin('labels', True, __class__) def get_printer_name(self): return self.PRINTER_NAME From e62b6063bb6863992db82199208dbfc38b2a2fbe Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 13:20:26 +1100 Subject: [PATCH 09/19] If printing plugins are available, let the user select them when printing --- InvenTree/templates/js/translated/label.js | 92 ++++++++++++++++++---- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index 1c843917e6..2e1a477387 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -57,13 +57,20 @@ function printStockItemLabels(items) { response, items, { - success: function(pk) { + success: function(data) { + + var pk = data.label; + var href = `/api/label/stock/${pk}/print/?`; items.forEach(function(item) { href += `items[]=${item}&`; }); + if (data.plugin) { + href += `plugin=${data.plugin}`; + } + window.location.href = href; } } @@ -107,13 +114,20 @@ function printStockLocationLabels(locations) { response, locations, { - success: function(pk) { + success: function(data) { + + var pk = data.label; + var href = `/api/label/location/${pk}/print/?`; locations.forEach(function(location) { href += `locations[]=${location}&`; }); + if (data.plugin) { + href += `plugin=${data.plugin}`; + } + window.location.href = href; } } @@ -162,13 +176,20 @@ function printPartLabels(parts) { response, parts, { - success: function(pk) { + success: function(data) { + + var pk = data.label; + var url = `/api/label/part/${pk}/print/?`; parts.forEach(function(part) { url += `parts[]=${part}&`; }); + if (data.plugin) { + href += `plugin=${data.plugin}`; + } + window.location.href = url; } } @@ -188,18 +209,53 @@ function selectLabel(labels, items, options={}) { * (via AJAX) from the server. */ - // If only a single label template is provided, - // just run with that! + // Array of available plugins for label printing + var plugins = []; - if (labels.length == 1) { - if (options.success) { - options.success(labels[0].pk); + // Request a list of available label printing plugins from the server + inventreeGet( + '{% url "api-plugin-list" %}', + { + + }, + { + async: false, + success: function(response) { + response.forEach(function(plugin) { + if (plugin.mixins && plugin.mixins.labels) { + // This plugin supports label printing + plugins.push(plugin); + } + }); + } } + ); - return; + var plugin_selection = ''; + + + if (plugins.length > 0) { + plugin_selection =` +
+ +
+ +
+
+ `; } - var modal = options.modal || '#modal-form'; var label_list = makeOptionsList( @@ -233,14 +289,15 @@ function selectLabel(labels, items, options={}) {
- ${label_list}
+ ${plugin_selection}
`; openModal({ @@ -255,14 +312,17 @@ function selectLabel(labels, items, options={}) { modalSubmit(modal, function() { - var label = $(modal).find('#id_label'); - - var pk = label.val(); + var label = $(modal).find('#id_label').val(); + var plugin = $(modal).find('#id_plugin').val(); closeModal(modal); if (options.success) { - options.success(pk); + options.success({ + // Return the selected label template and plugin + label: label, + plugin: plugin, + }); } }); } From f1f07a1977f85378b86164077a641423593502c6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 13:31:01 +1100 Subject: [PATCH 10/19] Extract plugin information from label printing request --- InvenTree/label/api.py | 36 ++++++++++++++++++++++ InvenTree/templates/js/translated/label.js | 3 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index 475d4f8fea..99e51b48b4 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ + +from django.conf import settings from django.conf.urls import url, include from django.core.exceptions import ValidationError, FieldError from django.http import HttpResponse @@ -14,6 +16,8 @@ from rest_framework.response import Response import InvenTree.helpers import common.models +from plugin.registry import registry + from stock.models import StockItem, StockLocation from part.models import Part @@ -46,11 +50,43 @@ class LabelPrintMixin: Mixin for printing labels """ + def get_plugin(self, request): + """ + Return the label printing plugin associated with this request. + This is provided in the url, e.g. ?plugin=myprinter + + Requires: + - settings.PLUGINS_ENABLED is True + - matching plugin can be found + - matching plugin implements the 'labels' mixin + - matching plugin is enabled + """ + + if not settings.PLUGINS_ENABLED: + return None + + plugin_key = request.query_params.get('plugin', None) + + for slug, plugin in registry.plugins.items(): + + if slug == plugin_key and plugin.mixin_enabled('labels'): + + config = plugin.plugin_config() + + if config and config.active: + # Only return the plugin if it is enabled! + return plugin + + # No matches found + return None + def print(self, request, items_to_print): """ Print this label template against a number of pre-validated items """ + # 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 = { diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index 2e1a477387..f91d9c2431 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -222,7 +222,8 @@ function selectLabel(labels, items, options={}) { async: false, success: function(response) { response.forEach(function(plugin) { - if (plugin.mixins && plugin.mixins.labels) { + // Look for active plugins which implement the 'labels' mixin class + if (plugin.active && plugin.mixins && plugin.mixins.labels) { // This plugin supports label printing plugins.push(plugin); } From 6c25a5805dc008fead599fa1e32b5285bad002f8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 14:57:01 +1100 Subject: [PATCH 11/19] Allow offloading of label printing to the configured plugin --- InvenTree/label/api.py | 52 ++++++++++++++++++- .../plugin/builtin/integration/mixins.py | 9 ---- InvenTree/plugin/events.py | 28 +++++++++- InvenTree/templates/js/translated/label.js | 50 ++++++++++++------ 4 files changed, 111 insertions(+), 28 deletions(-) diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index 99e51b48b4..0a06e76b9e 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +from io import BytesIO +from PIL import Image + from django.utils.translation import ugettext_lazy as _ from django.conf import settings from django.conf.urls import url, include from django.core.exceptions import ValidationError, FieldError -from django.http import HttpResponse +from django.http import HttpResponse, JsonResponse from django_filters.rest_framework import DjangoFilterBackend @@ -14,6 +17,7 @@ from rest_framework import generics, filters from rest_framework.response import Response import InvenTree.helpers +from InvenTree.tasks import offload_task import common.models from plugin.registry import registry @@ -102,6 +106,8 @@ class LabelPrintMixin: label_name = "label.pdf" + label_names = [] + # Merge one or more PDF files into a single download for item in items_to_print: label = self.get_object() @@ -109,6 +115,8 @@ class LabelPrintMixin: label_name = label.generate_filename(request) + label_names.append(label_name) + if debug_mode: outputs.append(label.render_as_string(request)) else: @@ -117,7 +125,46 @@ class LabelPrintMixin: if not label_name.endswith(".pdf"): label_name += ".pdf" - if debug_mode: + 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 output in outputs: + """ + For each output, we generate a temporary image file, + which will then get sent to the printer + """ + + # Generate a png image at 300dpi + (img_data, w, h) = output.get_document().write_png(resolution=300) + + # Construct a BytesIO object, which can be read by pillow + img_bytes = BytesIO(img_data) + + image = Image.open(img_bytes) + + # Offload a background task to print the provided label + offload_task( + 'plugin.events.print_label', + plugin.plugin_slug(), + image + ) + + 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. @@ -126,6 +173,7 @@ class LabelPrintMixin: html = "\n".join(outputs) return HttpResponse(html) + else: """ Concatenate all rendered pages into a single PDF object, diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 2d56e61929..2f0cfb02ce 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -415,15 +415,6 @@ class LabelPrintingMixin: def get_printer_name(self): return self.PRINTER_NAME - def print_labels(self, labels, **kwargs): - """ - Print multiple labels. - Default implementation is to call print_label() for each label, - but it can be overridden if desired. - """ - for label in labels: - self.print_label(label, **kwargs) - def print_label(self, label, **kwargs): """ Callback to print a single label diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index 049c8626c5..25a693a4f8 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -95,7 +95,11 @@ def process_event(plugin_slug, event, *args, **kwargs): logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'") - plugin = registry.plugins[plugin_slug] + plugin = registry.plugins.get(plugin_slug, None) + + if plugin is None: + logger.error(f"Could not find matching plugin for '{plugin_slug}'") + return plugin.process_event(event, *args, **kwargs) @@ -186,3 +190,25 @@ def after_delete(sender, instance, **kwargs): model=sender.__name__, table=table, ) + + +def print_label(plugin_slug, label_image, **kwargs): + """ + Print label with the provided plugin. + + This task is nominally handled by the background worker. + + Arguments: + plugin_slug: The unique slug (key) of the plugin + label_image: A PIL.Image image object to be printed + """ + + logger.info(f"Plugin '{plugin_slug}' is printing a label") + + plugin = registry.plugins.get(plugin_slug, None) + + if plugin is None: + logger.error(f"Could not find matching plugin for '{plugin_slug}'") + return + + plugin.print_label(label_image) diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index f91d9c2431..93484d48d2 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -14,11 +14,41 @@ */ /* exported + printLabels, printPartLabels, printStockItemLabels, printStockLocationLabels, */ + +/* + * Perform the "print" action. + */ +function printLabels(url, plugin=null) { + + if (plugin) { + // If a plugin is provided, do not redirect the browser. + // Instead, perform an API request and display a message + + url = url + `plugin=${plugin}`; + + inventreeGet(url, {}, { + success: function(response) { + showMessage( + '{% trans "Labels sent to printer" %}', + { + style: 'success', + } + ); + } + }); + } else { + window.location.href = url; + } + +} + + function printStockItemLabels(items) { /** * Print stock item labels for the given stock items @@ -67,11 +97,7 @@ function printStockItemLabels(items) { href += `items[]=${item}&`; }); - if (data.plugin) { - href += `plugin=${data.plugin}`; - } - - window.location.href = href; + printLabels(href, data.plugin); } } ); @@ -80,6 +106,7 @@ function printStockItemLabels(items) { ); } + function printStockLocationLabels(locations) { if (locations.length == 0) { @@ -124,11 +151,7 @@ function printStockLocationLabels(locations) { href += `locations[]=${location}&`; }); - if (data.plugin) { - href += `plugin=${data.plugin}`; - } - - window.location.href = href; + printLabels(href, data.plugin); } } ); @@ -186,11 +209,7 @@ function printPartLabels(parts) { url += `parts[]=${part}&`; }); - if (data.plugin) { - href += `plugin=${data.plugin}`; - } - - window.location.href = url; + printLabels(href, data.plugin); } } ); @@ -234,7 +253,6 @@ function selectLabel(labels, items, options={}) { var plugin_selection = ''; - if (plugins.length > 0) { plugin_selection =`
From f50dab22a656dc289d695e5a9d7066959d671e74 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:17:04 +1100 Subject: [PATCH 12/19] fixes for label template --- InvenTree/label/templates/label/label_base.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/InvenTree/label/templates/label/label_base.html b/InvenTree/label/templates/label/label_base.html index 2986c8a439..363a7d3144 100644 --- a/InvenTree/label/templates/label/label_base.html +++ b/InvenTree/label/templates/label/label_base.html @@ -13,6 +13,8 @@ body { font-family: Arial, Helvetica, sans-serif; margin: 0mm; + color: #000; + background-color: #FFF; } img { From efc6af5f4cebcdf97646bedade27983128116a6f Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:18:08 +1100 Subject: [PATCH 13/19] PEP fixes --- InvenTree/label/api.py | 4 ++-- InvenTree/plugin/events.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/InvenTree/label/api.py b/InvenTree/label/api.py index 0a06e76b9e..1a679d5ac5 100644 --- a/InvenTree/label/api.py +++ b/InvenTree/label/api.py @@ -131,11 +131,11 @@ class LabelPrintMixin: 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 output in outputs: diff --git a/InvenTree/plugin/events.py b/InvenTree/plugin/events.py index 25a693a4f8..829aeaa935 100644 --- a/InvenTree/plugin/events.py +++ b/InvenTree/plugin/events.py @@ -96,7 +96,7 @@ def process_event(plugin_slug, event, *args, **kwargs): logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'") plugin = registry.plugins.get(plugin_slug, None) - + if plugin is None: logger.error(f"Could not find matching plugin for '{plugin_slug}'") return @@ -206,7 +206,7 @@ def print_label(plugin_slug, label_image, **kwargs): logger.info(f"Plugin '{plugin_slug}' is printing a label") plugin = registry.plugins.get(plugin_slug, None) - + if plugin is None: logger.error(f"Could not find matching plugin for '{plugin_slug}'") return From fcdd1eb55b6528c977e2cdb90c518ae0d57a1d87 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:18:50 +1100 Subject: [PATCH 14/19] Remove old function call --- InvenTree/plugin/builtin/integration/mixins.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/InvenTree/plugin/builtin/integration/mixins.py b/InvenTree/plugin/builtin/integration/mixins.py index 2f0cfb02ce..451ddaf40f 100644 --- a/InvenTree/plugin/builtin/integration/mixins.py +++ b/InvenTree/plugin/builtin/integration/mixins.py @@ -412,9 +412,6 @@ class LabelPrintingMixin: super().__init__() self.add_mixin('labels', True, __class__) - def get_printer_name(self): - return self.PRINTER_NAME - def print_label(self, label, **kwargs): """ Callback to print a single label From 2fc522ad1569802d849af8206f830b6f76da4d9f Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:34:31 +1100 Subject: [PATCH 15/19] Hard code url - it is not always available --- InvenTree/templates/js/translated/label.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index 93484d48d2..b54905f3e6 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -233,10 +233,8 @@ function selectLabel(labels, items, options={}) { // Request a list of available label printing plugins from the server inventreeGet( - '{% url "api-plugin-list" %}', - { - - }, + `/api/plugins/`, + {}, { async: false, success: function(response) { From f24aa37f8fafcb6d0b7dc1ca5af92d18a4740d03 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:34:47 +1100 Subject: [PATCH 16/19] Allow labels to be printed from the "incomplete build output" table --- InvenTree/build/templates/build/detail.html | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/InvenTree/build/templates/build/detail.html b/InvenTree/build/templates/build/detail.html index 21bddfd6ca..0ed3c01f7e 100644 --- a/InvenTree/build/templates/build/detail.html +++ b/InvenTree/build/templates/build/detail.html @@ -258,6 +258,19 @@
+ + + + {% include "filter_list.html" with id='incompletebuilditems' %} {% endif %} @@ -468,6 +481,23 @@ inventreeGet( ) }); + $('#incomplete-output-print-label').click(function() { + var outputs = $('#build-output-table').bootstrapTable('getSelections'); + + if (outputs.length == 0) { + outputs = $('#build-output-table').bootstrapTable('getData'); + } + + var stock_id_values = []; + + outputs.forEach(function(output) { + stock_id_values.push(output.pk); + }); + + printStockItemLabels(stock_id_values); + + }); + {% endif %} {% if build.active and build.has_untracked_bom_items %} From b2b9ceec46823b535fb9b2b4b58e4ab311a5c129 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:38:01 +1100 Subject: [PATCH 17/19] javascript fixes --- InvenTree/templates/js/translated/label.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index b54905f3e6..bb662e0b67 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -203,10 +203,10 @@ function printPartLabels(parts) { var pk = data.label; - var url = `/api/label/part/${pk}/print/?`; + var href = `/api/label/part/${pk}/print/?`; parts.forEach(function(part) { - url += `parts[]=${part}&`; + href += `parts[]=${part}&`; }); printLabels(href, data.plugin); From 708ba566cbb34f18cbb7cd484113c94f3754e772 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 24 Mar 2022 16:44:46 +1100 Subject: [PATCH 18/19] URL fix --- InvenTree/templates/js/translated/label.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/InvenTree/templates/js/translated/label.js b/InvenTree/templates/js/translated/label.js index bb662e0b67..5215ce9d28 100644 --- a/InvenTree/templates/js/translated/label.js +++ b/InvenTree/templates/js/translated/label.js @@ -233,7 +233,7 @@ function selectLabel(labels, items, options={}) { // Request a list of available label printing plugins from the server inventreeGet( - `/api/plugins/`, + `/api/plugin/`, {}, { async: false, From acd796f12796f4dc971c7193878d553ac4b275f3 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 24 Mar 2022 20:11:17 +1100 Subject: [PATCH 19/19] Add 'plugins_enabled' information to the root API endpoint --- InvenTree/InvenTree/api.py | 2 ++ InvenTree/InvenTree/version.py | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/InvenTree/InvenTree/api.py b/InvenTree/InvenTree/api.py index 7c8f71ea9a..09ec02f158 100644 --- a/InvenTree/InvenTree/api.py +++ b/InvenTree/InvenTree/api.py @@ -6,6 +6,7 @@ Main JSON interface views from __future__ import unicode_literals from django.utils.translation import ugettext_lazy as _ +from django.conf import settings from django.http import JsonResponse from django_filters.rest_framework import DjangoFilterBackend @@ -37,6 +38,7 @@ class InfoView(AjaxView): 'instance': inventreeInstanceName(), 'apiVersion': inventreeApiVersion(), 'worker_running': is_worker_running(), + 'plugins_enabled': settings.PLUGINS_ENABLED, } return JsonResponse(data) diff --git a/InvenTree/InvenTree/version.py b/InvenTree/InvenTree/version.py index 6d4848f436..bb36b28ff8 100644 --- a/InvenTree/InvenTree/version.py +++ b/InvenTree/InvenTree/version.py @@ -12,11 +12,14 @@ import common.models INVENTREE_SW_VERSION = "0.7.0 dev" # InvenTree API version -INVENTREE_API_VERSION = 32 +INVENTREE_API_VERSION = 33 """ Increment this API version number whenever there is a significant change to the API that any clients need to know about +v33 -> 2022-03-24 + - Adds "plugins_enabled" information to root API endpoint + v32 -> 2022-03-19 - Adds "parameters" detail to Part API endpoint (use ¶meters=true) - Adds ability to filter PartParameterTemplate API by Part instance