mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Merge remote-tracking branch 'inventree/master' into order-parts-wizard
This commit is contained in:
commit
293294cce8
@ -6,6 +6,7 @@ Main JSON interface views
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
|
|
||||||
from django_filters.rest_framework import DjangoFilterBackend
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
@ -37,6 +38,7 @@ class InfoView(AjaxView):
|
|||||||
'instance': inventreeInstanceName(),
|
'instance': inventreeInstanceName(),
|
||||||
'apiVersion': inventreeApiVersion(),
|
'apiVersion': inventreeApiVersion(),
|
||||||
'worker_running': is_worker_running(),
|
'worker_running': is_worker_running(),
|
||||||
|
'plugins_enabled': settings.PLUGINS_ENABLED,
|
||||||
}
|
}
|
||||||
|
|
||||||
return JsonResponse(data)
|
return JsonResponse(data)
|
||||||
|
@ -12,11 +12,14 @@ import common.models
|
|||||||
INVENTREE_SW_VERSION = "0.7.0 dev"
|
INVENTREE_SW_VERSION = "0.7.0 dev"
|
||||||
|
|
||||||
# InvenTree API version
|
# 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
|
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
|
v32 -> 2022-03-19
|
||||||
- Adds "parameters" detail to Part API endpoint (use ¶meters=true)
|
- Adds "parameters" detail to Part API endpoint (use ¶meters=true)
|
||||||
- Adds ability to filter PartParameterTemplate API by Part instance
|
- Adds ability to filter PartParameterTemplate API by Part instance
|
||||||
|
@ -258,6 +258,19 @@
|
|||||||
</a></li>
|
</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Label Printing Actions -->
|
||||||
|
<div class='btn-group'>
|
||||||
|
<button id='output-print-options' class='btn btn-primary dropdown-toggle' type='button' data-bs-toggle='dropdown' title='{% trans "Printing Actions" %}'>
|
||||||
|
<span class='fas fa-print'></span> <span class='caret'></span>
|
||||||
|
</button>
|
||||||
|
<ul class='dropdown-menu'>
|
||||||
|
<li><a class='dropdown-item' href='#' id='incomplete-output-print-label' title='{% trans "Print labels" %}'>
|
||||||
|
<span class='fas fa-tags'></span> {% trans "Print labels" %}
|
||||||
|
</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
{% include "filter_list.html" with id='incompletebuilditems' %}
|
{% include "filter_list.html" with id='incompletebuilditems' %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% 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 %}
|
{% endif %}
|
||||||
|
|
||||||
{% if build.active and build.has_untracked_bom_items %}
|
{% if build.active and build.has_untracked_bom_items %}
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.conf.urls import url, include
|
from django.conf.urls import url, include
|
||||||
from django.core.exceptions import ValidationError, FieldError
|
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
|
from django_filters.rest_framework import DjangoFilterBackend
|
||||||
|
|
||||||
@ -12,8 +17,11 @@ from rest_framework import generics, filters
|
|||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
import InvenTree.helpers
|
import InvenTree.helpers
|
||||||
|
from InvenTree.tasks import offload_task
|
||||||
import common.models
|
import common.models
|
||||||
|
|
||||||
|
from plugin.registry import registry
|
||||||
|
|
||||||
from stock.models import StockItem, StockLocation
|
from stock.models import StockItem, StockLocation
|
||||||
from part.models import Part
|
from part.models import Part
|
||||||
|
|
||||||
@ -46,11 +54,43 @@ class LabelPrintMixin:
|
|||||||
Mixin for printing labels
|
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):
|
def print(self, request, items_to_print):
|
||||||
"""
|
"""
|
||||||
Print this label template against a number of pre-validated items
|
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:
|
if len(items_to_print) == 0:
|
||||||
# No valid items provided, return an error message
|
# No valid items provided, return an error message
|
||||||
data = {
|
data = {
|
||||||
@ -66,6 +106,8 @@ class LabelPrintMixin:
|
|||||||
|
|
||||||
label_name = "label.pdf"
|
label_name = "label.pdf"
|
||||||
|
|
||||||
|
label_names = []
|
||||||
|
|
||||||
# Merge one or more PDF files into a single download
|
# Merge one or more PDF files into a single download
|
||||||
for item in items_to_print:
|
for item in items_to_print:
|
||||||
label = self.get_object()
|
label = self.get_object()
|
||||||
@ -73,6 +115,8 @@ class LabelPrintMixin:
|
|||||||
|
|
||||||
label_name = label.generate_filename(request)
|
label_name = label.generate_filename(request)
|
||||||
|
|
||||||
|
label_names.append(label_name)
|
||||||
|
|
||||||
if debug_mode:
|
if debug_mode:
|
||||||
outputs.append(label.render_as_string(request))
|
outputs.append(label.render_as_string(request))
|
||||||
else:
|
else:
|
||||||
@ -81,7 +125,46 @@ class LabelPrintMixin:
|
|||||||
if not label_name.endswith(".pdf"):
|
if not label_name.endswith(".pdf"):
|
||||||
label_name += ".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,
|
Contatenate all rendered templates into a single HTML string,
|
||||||
and return the string as a HTML response.
|
and return the string as a HTML response.
|
||||||
@ -90,6 +173,7 @@ class LabelPrintMixin:
|
|||||||
html = "\n".join(outputs)
|
html = "\n".join(outputs)
|
||||||
|
|
||||||
return HttpResponse(html)
|
return HttpResponse(html)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
"""
|
"""
|
||||||
Concatenate all rendered pages into a single PDF object,
|
Concatenate all rendered pages into a single PDF object,
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
body {
|
body {
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
margin: 0mm;
|
margin: 0mm;
|
||||||
|
color: #000;
|
||||||
|
background-color: #FFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -8,6 +8,7 @@ over and above the built-in Django tags.
|
|||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
|
|
||||||
@ -31,6 +32,9 @@ from plugin.models import PluginSetting
|
|||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag()
|
@register.simple_tag()
|
||||||
def define(value, *args, **kwargs):
|
def define(value, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
@ -57,8 +61,19 @@ def render_date(context, date_object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
if type(date_object) == str:
|
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
|
# If a string is passed, first convert it to a datetime
|
||||||
|
try:
|
||||||
date_object = date.fromisoformat(date_object)
|
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!
|
# We may have already pre-cached the date format by calling this already!
|
||||||
user_date_format = context.get('user_date_format', None)
|
user_date_format = context.get('user_date_format', None)
|
||||||
|
@ -393,6 +393,38 @@ class AppMixin:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class LabelPrintingMixin:
|
||||||
|
"""
|
||||||
|
Mixin which enables direct printing of stock labels.
|
||||||
|
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
Meta options for this mixin
|
||||||
|
"""
|
||||||
|
MIXIN_NAME = 'Label printing'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.add_mixin('labels', True, __class__)
|
||||||
|
|
||||||
|
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:
|
class APICallMixin:
|
||||||
"""
|
"""
|
||||||
Mixin that enables easier API calls for a plugin
|
Mixin that enables easier API calls for a plugin
|
||||||
|
@ -95,7 +95,11 @@ def process_event(plugin_slug, event, *args, **kwargs):
|
|||||||
|
|
||||||
logger.info(f"Plugin '{plugin_slug}' is processing triggered event '{event}'")
|
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)
|
plugin.process_event(event, *args, **kwargs)
|
||||||
|
|
||||||
@ -186,3 +190,25 @@ def after_delete(sender, instance, **kwargs):
|
|||||||
model=sender.__name__,
|
model=sender.__name__,
|
||||||
table=table,
|
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)
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
Utility class to enable simpler imports
|
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.action.mixins import ActionMixin
|
||||||
from ..builtin.barcode.mixins import BarcodeMixin
|
from ..builtin.barcode.mixins import BarcodeMixin
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ __all__ = [
|
|||||||
'APICallMixin',
|
'APICallMixin',
|
||||||
'AppMixin',
|
'AppMixin',
|
||||||
'EventMixin',
|
'EventMixin',
|
||||||
|
'LabelPrintingMixin',
|
||||||
'NavigationMixin',
|
'NavigationMixin',
|
||||||
'ScheduleMixin',
|
'ScheduleMixin',
|
||||||
'SettingsMixin',
|
'SettingsMixin',
|
||||||
|
@ -10,12 +10,13 @@ import pathlib
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
from typing import OrderedDict
|
from typing import OrderedDict
|
||||||
from importlib import reload
|
from importlib import reload
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
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.conf.urls import url, include
|
||||||
from django.urls import clear_url_caches
|
from django.urls import clear_url_caches
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
@ -282,6 +283,8 @@ class PluginsRegistry:
|
|||||||
if not settings.PLUGIN_TESTING:
|
if not settings.PLUGIN_TESTING:
|
||||||
raise error # pragma: no cover
|
raise error # pragma: no cover
|
||||||
plugin_db_setting = None
|
plugin_db_setting = None
|
||||||
|
except (IntegrityError) as error:
|
||||||
|
logger.error(f"Error initializing plugin: {error}")
|
||||||
|
|
||||||
# Always activate if testing
|
# Always activate if testing
|
||||||
if settings.PLUGIN_TESTING or (plugin_db_setting and plugin_db_setting.active):
|
if settings.PLUGIN_TESTING or (plugin_db_setting and plugin_db_setting.active):
|
||||||
|
@ -213,7 +213,7 @@ function createBuildOutput(build_id, options) {
|
|||||||
success: function(data) {
|
success: function(data) {
|
||||||
if (data.next) {
|
if (data.next) {
|
||||||
fields.serial_numbers.placeholder = `{% trans "Next available serial number" %}: ${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}`;
|
fields.serial_numbers.placeholder = `{% trans "Latest serial number" %}: ${data.latest}`;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,11 +14,41 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* exported
|
/* exported
|
||||||
|
printLabels,
|
||||||
printPartLabels,
|
printPartLabels,
|
||||||
printStockItemLabels,
|
printStockItemLabels,
|
||||||
printStockLocationLabels,
|
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) {
|
function printStockItemLabels(items) {
|
||||||
/**
|
/**
|
||||||
* Print stock item labels for the given stock items
|
* Print stock item labels for the given stock items
|
||||||
@ -57,14 +87,17 @@ function printStockItemLabels(items) {
|
|||||||
response,
|
response,
|
||||||
items,
|
items,
|
||||||
{
|
{
|
||||||
success: function(pk) {
|
success: function(data) {
|
||||||
|
|
||||||
|
var pk = data.label;
|
||||||
|
|
||||||
var href = `/api/label/stock/${pk}/print/?`;
|
var href = `/api/label/stock/${pk}/print/?`;
|
||||||
|
|
||||||
items.forEach(function(item) {
|
items.forEach(function(item) {
|
||||||
href += `items[]=${item}&`;
|
href += `items[]=${item}&`;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = href;
|
printLabels(href, data.plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -73,6 +106,7 @@ function printStockItemLabels(items) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function printStockLocationLabels(locations) {
|
function printStockLocationLabels(locations) {
|
||||||
|
|
||||||
if (locations.length == 0) {
|
if (locations.length == 0) {
|
||||||
@ -107,14 +141,17 @@ function printStockLocationLabels(locations) {
|
|||||||
response,
|
response,
|
||||||
locations,
|
locations,
|
||||||
{
|
{
|
||||||
success: function(pk) {
|
success: function(data) {
|
||||||
|
|
||||||
|
var pk = data.label;
|
||||||
|
|
||||||
var href = `/api/label/location/${pk}/print/?`;
|
var href = `/api/label/location/${pk}/print/?`;
|
||||||
|
|
||||||
locations.forEach(function(location) {
|
locations.forEach(function(location) {
|
||||||
href += `locations[]=${location}&`;
|
href += `locations[]=${location}&`;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = href;
|
printLabels(href, data.plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -162,14 +199,17 @@ function printPartLabels(parts) {
|
|||||||
response,
|
response,
|
||||||
parts,
|
parts,
|
||||||
{
|
{
|
||||||
success: function(pk) {
|
success: function(data) {
|
||||||
var url = `/api/label/part/${pk}/print/?`;
|
|
||||||
|
var pk = data.label;
|
||||||
|
|
||||||
|
var href = `/api/label/part/${pk}/print/?`;
|
||||||
|
|
||||||
parts.forEach(function(part) {
|
parts.forEach(function(part) {
|
||||||
url += `parts[]=${part}&`;
|
href += `parts[]=${part}&`;
|
||||||
});
|
});
|
||||||
|
|
||||||
window.location.href = url;
|
printLabels(href, data.plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -188,17 +228,50 @@ function selectLabel(labels, items, options={}) {
|
|||||||
* (via AJAX) from the server.
|
* (via AJAX) from the server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// If only a single label template is provided,
|
// Array of available plugins for label printing
|
||||||
// just run with that!
|
var plugins = [];
|
||||||
|
|
||||||
if (labels.length == 1) {
|
// Request a list of available label printing plugins from the server
|
||||||
if (options.success) {
|
inventreeGet(
|
||||||
options.success(labels[0].pk);
|
`/api/plugin/`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
async: false,
|
||||||
|
success: function(response) {
|
||||||
|
response.forEach(function(plugin) {
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var plugin_selection = '';
|
||||||
|
|
||||||
|
if (plugins.length > 0) {
|
||||||
|
plugin_selection =`
|
||||||
|
<div class='form-group'>
|
||||||
|
<label class='control-label requiredField' for='id_plugin'>
|
||||||
|
{% trans "Select Printer" %}
|
||||||
|
</label>
|
||||||
|
<div class='controls'>
|
||||||
|
<select id='id_plugin' class='select form-control' name='plugin'>
|
||||||
|
<option value='' title='{% trans "Export to PDF" %}'>{% trans "Export to PDF" %}</option>
|
||||||
|
`;
|
||||||
|
|
||||||
|
plugins.forEach(function(plugin) {
|
||||||
|
plugin_selection += `<option value='${plugin.key}' title='${plugin.meta.human_name}'>${plugin.meta.description} - <small>${plugin.meta.human_name}</small></option>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
plugin_selection += `
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
var modal = options.modal || '#modal-form';
|
var modal = options.modal || '#modal-form';
|
||||||
|
|
||||||
@ -233,14 +306,15 @@ function selectLabel(labels, items, options={}) {
|
|||||||
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
|
<form method='post' action='' class='js-modal-form' enctype='multipart/form-data'>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label class='control-label requiredField' for='id_label'>
|
<label class='control-label requiredField' for='id_label'>
|
||||||
{% trans "Select Label" %}
|
{% trans "Select Label Template" %}
|
||||||
</label>
|
</label>
|
||||||
<div class='controls'>
|
<div class='controls'>
|
||||||
<select id='id_label' class='select form-control name='label'>
|
<select id='id_label' class='select form-control' name='label'>
|
||||||
${label_list}
|
${label_list}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
${plugin_selection}
|
||||||
</form>`;
|
</form>`;
|
||||||
|
|
||||||
openModal({
|
openModal({
|
||||||
@ -255,14 +329,17 @@ function selectLabel(labels, items, options={}) {
|
|||||||
|
|
||||||
modalSubmit(modal, function() {
|
modalSubmit(modal, function() {
|
||||||
|
|
||||||
var label = $(modal).find('#id_label');
|
var label = $(modal).find('#id_label').val();
|
||||||
|
var plugin = $(modal).find('#id_plugin').val();
|
||||||
var pk = label.val();
|
|
||||||
|
|
||||||
closeModal(modal);
|
closeModal(modal);
|
||||||
|
|
||||||
if (options.success) {
|
if (options.success) {
|
||||||
options.success(pk);
|
options.success({
|
||||||
|
// Return the selected label template and plugin
|
||||||
|
label: label,
|
||||||
|
plugin: plugin,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user