262 lines
9.8 KiB
Python
262 lines
9.8 KiB
Python
"""Plugin mixin classes for label plugins."""
|
|
|
|
from typing import Union
|
|
|
|
from django.core.exceptions import ValidationError
|
|
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 plugin.base.label import label as plugin_label
|
|
from plugin.helpers import MixinNotImplementedError
|
|
from report.models import LabelTemplate, TemplateOutput
|
|
|
|
|
|
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.
|
|
"""
|
|
|
|
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__)
|
|
|
|
BLOCKING_PRINT = True
|
|
|
|
def render_to_pdf(self, label: LabelTemplate, instance, request, **kwargs):
|
|
"""Render this label to PDF format.
|
|
|
|
Arguments:
|
|
label: The LabelTemplate object to render against
|
|
instance: The model instance to render
|
|
request: The HTTP request object which triggered this print job
|
|
"""
|
|
try:
|
|
return label.render(instance, request)
|
|
except Exception:
|
|
log_error('label.render_to_pdf')
|
|
raise ValidationError(_('Error rendering label to PDF'))
|
|
|
|
def render_to_html(self, label: LabelTemplate, instance, request, **kwargs):
|
|
"""Render this label to HTML format.
|
|
|
|
Arguments:
|
|
label: The LabelTemplate object to render against
|
|
instance: The model instance to render
|
|
request: The HTTP request object which triggered this print job
|
|
"""
|
|
try:
|
|
return label.render_as_string(instance, request)
|
|
except Exception:
|
|
log_error('label.render_to_html')
|
|
raise ValidationError(_('Error rendering label to HTML'))
|
|
|
|
def render_to_png(self, label: LabelTemplate, instance, request=None, **kwargs):
|
|
"""Render this label to PNG format.
|
|
|
|
Arguments:
|
|
label: The LabelTemplate object to render against
|
|
item: The model instance to render
|
|
request: The HTTP request object which triggered this print job
|
|
Keyword Arguments:
|
|
pdf_data: The raw PDF data of the rendered label (if already rendered)
|
|
dpi: The DPI to use for the PNG rendering
|
|
use_cairo (bool): Whether to use the pdftocairo backend for rendering which provides better results in tests,
|
|
see [#6488](https://github.com/inventree/InvenTree/pull/6488) for details. If False, pdftoppm is used (default: True)
|
|
pdf2image_kwargs (dict): Additional keyword arguments to pass to the
|
|
[`pdf2image.convert_from_bytes`](https://pdf2image.readthedocs.io/en/latest/reference.html#pdf2image.pdf2image.convert_from_bytes) method (optional)
|
|
"""
|
|
# Check if pdf data is provided
|
|
pdf_data = kwargs.get('pdf_data')
|
|
|
|
if not pdf_data:
|
|
pdf_data = (
|
|
self.render_to_pdf(label, instance, request, **kwargs)
|
|
.get_document()
|
|
.write_pdf()
|
|
)
|
|
|
|
pdf2image_kwargs = {
|
|
'dpi': kwargs.get('dpi', InvenTreeSetting.get_setting('LABEL_DPI', 300)),
|
|
'use_pdftocairo': kwargs.get('use_cairo', True),
|
|
**kwargs.get('pdf2image_kwargs', {}),
|
|
}
|
|
|
|
# Convert to png data
|
|
try:
|
|
return pdf2image.convert_from_bytes(pdf_data, **pdf2image_kwargs)[0]
|
|
except Exception:
|
|
log_error('label.render_to_png')
|
|
return None
|
|
|
|
def print_labels(
|
|
self,
|
|
label: LabelTemplate,
|
|
output: TemplateOutput,
|
|
items: list,
|
|
request: Request,
|
|
**kwargs,
|
|
) -> None:
|
|
"""Print one or more labels with the provided template and items.
|
|
|
|
Arguments:
|
|
label: The LabelTemplate object to use for printing
|
|
output: The TemplateOutput object used to store the results
|
|
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:
|
|
None. Output data should be stored in the provided TemplateOutput object
|
|
|
|
Raises:
|
|
ValidationError if there is an error during the print process
|
|
|
|
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
|
|
|
|
# Initial state for the output print job
|
|
output.progress = 0
|
|
output.complete = False
|
|
output.save()
|
|
|
|
N = len(items)
|
|
|
|
if N <= 0:
|
|
raise ValidationError(_('No items provided to print'))
|
|
|
|
# Generate a label output for each provided item
|
|
for item in items:
|
|
context = label.get_context(item, request)
|
|
filename = label.generate_filename(context)
|
|
pdf_file = self.render_to_pdf(label, item, request, **kwargs)
|
|
pdf_data = pdf_file.get_document().write_pdf()
|
|
png_file = self.render_to_png(
|
|
label, item, request, pdf_data=pdf_data, **kwargs
|
|
)
|
|
|
|
print_args = {
|
|
'pdf_file': pdf_file,
|
|
'pdf_data': pdf_data,
|
|
'png_file': png_file,
|
|
'filename': filename,
|
|
'context': context,
|
|
'output': output,
|
|
'label_instance': label,
|
|
'item_instance': item,
|
|
'user': user,
|
|
'width': label.width,
|
|
'height': label.height,
|
|
'printing_options': kwargs['printing_options'],
|
|
}
|
|
|
|
if self.BLOCKING_PRINT:
|
|
# Print the label (blocking)
|
|
self.print_label(**print_args)
|
|
else:
|
|
# Offload the print task to the background worker
|
|
|
|
# Exclude the 'pdf_file' object - cannot be pickled
|
|
print_args.pop('pdf_file', None)
|
|
|
|
# Exclude the 'context' object - cannot be pickled
|
|
print_args.pop('context', None)
|
|
|
|
offload_task(
|
|
plugin_label.print_label,
|
|
self.plugin_slug(),
|
|
group='plugin',
|
|
**print_args,
|
|
)
|
|
|
|
# Update the progress of the print job
|
|
output.progress += int(100 / N)
|
|
output.save()
|
|
|
|
# Mark the output as complete
|
|
output.complete = True
|
|
output.progress = 100
|
|
|
|
# Add in the generated file (if applicable)
|
|
output.output = self.get_generated_file(**print_args)
|
|
|
|
output.save()
|
|
|
|
def get_generated_file(self, **kwargs):
|
|
"""Return the generated file for download (or None, if this plugin does not generate a file output).
|
|
|
|
The default implementation returns None, but this can be overridden by the particular plugin.
|
|
"""
|
|
return None
|
|
|
|
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
|
|
output: The TemplateOutput object used to store the results of the print job
|
|
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 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)
|
|
|
|
def before_printing(self):
|
|
"""Hook method called before printing labels."""
|
|
|
|
def after_printing(self):
|
|
"""Hook method called after printing labels."""
|