InvenTree/InvenTree/plugin/base/label/mixins.py

209 lines
7.7 KiB
Python

"""Plugin mixin classes for label plugins."""
from typing import Union
from django.core.exceptions import ValidationError
from django.http import JsonResponse
from django.utils.translation import gettext_lazy as _
import pdf2image
from rest_framework import serializers
from rest_framework.request import Request
from common.models import InvenTreeSetting
from InvenTree.exceptions import log_error
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
class LabelPrintingMixin:
"""Mixin which enables direct printing of stock labels.
Each plugin must provide a NAME attribute, which is used to uniquely identify the printer.
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 behavior.
"""
# 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'
def __init__(self): # pragma: no cover
"""Register mixin."""
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
"""
try:
return label.render(request)
except Exception as e:
log_error('label.render_to_pdf')
raise ValidationError(_('Error rendering label to PDF'))
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
"""
try:
return label.render_as_string(request)
except Exception as e:
log_error('label.render_to_html')
raise ValidationError(_('Error rendering label to HTML'))
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
try:
return pdf2image.convert_from_bytes(pdf_data, dpi=dpi)[0]
except Exception as e:
log_error('label.render_to_png')
raise ValidationError(_('Error rendering label to PNG'))
def print_labels(
self,
label: LabelTemplate,
items: list,
request: Request,
printing_options: dict,
**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
Keyword Arguments:
printing_options: The printing options set for this print job defined in the PrintingOptionsSerializer
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,
'printing_options': printing_options,
}
if self.BLOCKING_PRINT:
# Blocking print job
self.print_label(**print_args)
else:
# Non-blocking print job
# Offload the print job to a background worker
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):
"""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
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)
printing_options: The printing options set for this print job defined in the PrintingOptionsSerializer
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.
"""
# Exclude the 'pdf_file' object - cannot be pickled
kwargs.pop('pdf_file', None)
offload_task(plugin_label.print_label, self.plugin_slug(), **kwargs)
def get_printing_options_serializer(
self, request: Request, *args, **kwargs
) -> Union[serializers.Serializer, None]:
"""Return a serializer class instance with dynamic printing options.
Arguments:
request: The request made to print a label or interfering the available serializer fields via an OPTIONS request
*args, **kwargs: need to be passed to the serializer instance
Returns:
A class instance of a DRF serializer class, by default this an instance of
self.PrintingOptionsSerializer using the *args, **kwargs if existing for this plugin
"""
serializer = getattr(self, 'PrintingOptionsSerializer', None)
if not serializer:
return None
return serializer(*args, **kwargs)