Coverage for src/backend/InvenTree/machine/machine_types/label_printer.py: 66%
73 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-07 17:47 +0000
1"""Label printing machine type."""
3from typing import cast
5from django.contrib.auth.models import AnonymousUser
6from django.db import models
7from django.db.models.query import QuerySet
8from django.http import HttpRequest, HttpResponse, JsonResponse
9from django.utils.translation import gettext_lazy as _
11from PIL.Image import Image
12from rest_framework import serializers
13from rest_framework.request import Request
15from generic.states import ColorEnum
16from machine.machine_type import BaseDriver, BaseMachineType, MachineStatus
17from plugin import registry as plg_registry
18from plugin.base.label.mixins import LabelPrintingMixin
19from report.models import LabelTemplate
20from stock.models import StockLocation
23class LabelPrinterBaseDriver(BaseDriver):
24 """Base driver for label printer machines.
26 Attributes:
27 USE_BACKGROUND_WORKER (bool): If True, the `print_label()` and `print_labels()` methods will be run in a background worker (default: True)
28 """
30 machine_type = 'label-printer'
32 USE_BACKGROUND_WORKER = True
34 def print_label(
35 self,
36 machine: 'LabelPrinterMachine',
37 label: LabelTemplate,
38 item: models.Model,
39 **kwargs,
40 ) -> None:
41 """Print a single label with the provided template and item.
43 Arguments:
44 machine: The LabelPrintingMachine instance that should be used for printing
45 label: The LabelTemplate object to use for printing
46 item: The database item to print (e.g. StockItem instance)
48 Keyword Arguments:
49 printing_options (dict): The printing options set for this print job defined in the PrintingOptionsSerializer
50 by default the following options are available:
51 - copies: number of copies to print for the label
53 Note that the supplied args/kwargs may be different if the driver overrides the print_labels() method.
54 """
56 def print_labels(
57 self,
58 machine: 'LabelPrinterMachine',
59 label: LabelTemplate,
60 items: QuerySet[models.Model],
61 **kwargs,
62 ) -> JsonResponse | None:
63 """Print one or more labels with the provided template and items.
65 Arguments:
66 machine: The LabelPrintingMachine instance that should be used for printing
67 label: The LabelTemplate object to use for printing
68 items: The list of database items to print (e.g. StockItem instances)
70 Keyword Arguments:
71 printing_options (dict): The printing options set for this print job defined in the PrintingOptionsSerializer
72 by default the following options are available:
73 - copies: number of copies to print for each label
75 Returns:
76 If `USE_BACKGROUND_WORKER=False`, a JsonResponse object which indicates outcome to the user, otherwise None
78 The default implementation simply calls print_label() for each label, producing multiple single label output "jobs"
79 but this can be overridden by the particular driver.
80 """
81 for item in items:
82 self.print_label(machine, label, item, **kwargs)
84 def get_printers(
85 self, label: LabelTemplate, items: QuerySet, **kwargs
86 ) -> list['LabelPrinterMachine']:
87 """Get all printers that would be available to print this job.
89 By default all printers that are initialized using this driver are returned.
91 Arguments:
92 label: The LabelTemplate object to use for printing
93 items: The lost of database items to print (e.g. StockItem instances)
95 Keyword Arguments:
96 request (Request): The django request used to make the get printers request
97 """
98 return cast(list['LabelPrinterMachine'], self.get_machines())
100 def get_printing_options_serializer(
101 self, request: Request, *args, **kwargs
102 ) -> 'LabelPrinterBaseDriver.PrintingOptionsSerializer':
103 """Return a serializer class instance with dynamic printing options.
105 Arguments:
106 request: The request made to print a label or interfering the available serializer fields via an OPTIONS request
108 Note:
109 `*args`, `**kwargs` needs to be passed to the serializer instance
111 Returns:
112 A class instance of a DRF serializer class, by default this an instance of self.PrintingOptionsSerializer using the *args, **kwargs if existing for this driver
113 """
114 return self.PrintingOptionsSerializer(*args, **kwargs)
116 # --- helper functions
117 @property
118 def machine_plugin(self) -> LabelPrintingMixin:
119 """Returns the builtin machine label printing plugin that manages printing through machines."""
120 plg = plg_registry.get_plugin('inventreelabelmachine')
121 return cast(LabelPrintingMixin, plg)
123 def render_to_pdf(
124 self, label: LabelTemplate, item: models.Model, **kwargs
125 ) -> HttpResponse:
126 """Helper method to render a label to PDF format for a specific item.
128 Arguments:
129 label: The LabelTemplate object to render
130 item: The item to render the label with
131 """
132 request = self._get_dummy_request()
133 return self.machine_plugin.render_to_pdf(label, item, request, **kwargs)
135 def render_to_pdf_data(
136 self, label: LabelTemplate, item: models.Model, **kwargs
137 ) -> bytes:
138 """Helper method to render a label to PDF and return it as bytes for a specific item.
140 Arguments:
141 label: The LabelTemplate object to render
142 item: The item to render the label with
143 """
144 return self.render_to_pdf(label, item, **kwargs)
146 def render_to_html(self, label: LabelTemplate, item: models.Model, **kwargs) -> str:
147 """Helper method to render a label to HTML format for a specific item.
149 Arguments:
150 label: The LabelTemplate object to render
151 item: The item to render the label with
152 """
153 request = self._get_dummy_request()
154 return self.machine_plugin.render_to_html(label, item, request, **kwargs)
156 def render_to_png(
157 self, label: LabelTemplate, item: models.Model, **kwargs
158 ) -> Image | None:
159 """Helper method to render a label to PNG format for a specific item.
161 Arguments:
162 label: The LabelTemplate object to render
163 item: The item to render the label with
165 Keyword Arguments:
166 pdf_data (bytes): The pdf document as bytes (optional)
167 dpi (int): The dpi used to render the image (optional)
168 use_cairo (bool): Whether to use the pdftocairo backend for rendering which provides better results in tests,
169 see [#6488](https://github.com/inventree/InvenTree/pull/6488) for details. If False, pdftoppm is used (default: True)
170 pdf2image_kwargs (dict): Additional keyword arguments to pass to the
171 [`pdf2image.convert_from_bytes`](https://pdf2image.readthedocs.io/en/latest/reference.html#pdf2image.pdf2image.convert_from_bytes) method (optional)
172 """
173 request = self._get_dummy_request()
174 return self.machine_plugin.render_to_png(label, item, request, **kwargs)
176 def _get_dummy_request(self):
177 """Return a dummy request object to it work with legacy code.
179 Note: this is a private method and can be removed at anytime
180 """
181 r = HttpRequest()
182 r.META['SERVER_PORT'] = '80'
183 r.META['SERVER_NAME'] = 'localhost'
184 r.user = AnonymousUser()
186 return r
188 required_overrides = [[print_label, print_labels]]
190 class PrintingOptionsSerializer(serializers.Serializer):
191 """Printing options serializer that implements common options.
193 This can be overridden by the driver to implement custom options, but the driver should always extend this class.
195 Example:
196 This example shows how to extend the default serializer and add a new option:
197 ```py
198 class MyDriver(LabelPrinterBaseDriver):
199 # ...
201 class PrintingOptionsSerializer(LabelPrinterBaseDriver.PrintingOptionsSerializer):
202 auto_cut = serializers.BooleanField(
203 default=True,
204 label=_('Auto cut'),
205 help_text=_('Automatically cut the label after printing'),
206 )
207 ```
208 """
210 copies = serializers.IntegerField(
211 default=1,
212 label=_('Copies'),
213 help_text=_('Number of copies to print for each label'),
214 )
217class LabelPrinterStatus(MachineStatus):
218 """Label printer status codes.
220 Attributes:
221 CONNECTED: The printer is connected and ready to print
222 UNKNOWN: The printer status is unknown (e.g. there is no active connection to the printer)
223 PRINTING: The printer is currently printing a label
224 WARNING: The printer is in an unknown warning condition
225 NO_MEDIA: The printer is out of media (e.g. the label spool is empty)
226 PAPER_JAM: The printer has a paper jam
227 DISCONNECTED: The driver cannot establish a connection to the printer
228 ERROR: The printer is in an unknown error condition
229 """
231 CONNECTED = 100, _('Connected'), ColorEnum.success
232 UNKNOWN = 101, _('Unknown'), ColorEnum.secondary
233 PRINTING = 110, _('Printing'), ColorEnum.primary
234 WARNING = 200, _('Warning'), ColorEnum.warning
235 NO_MEDIA = 301, _('No media'), ColorEnum.warning
236 PAPER_JAM = 302, _('Paper jam'), ColorEnum.warning
237 DISCONNECTED = 400, _('Disconnected'), ColorEnum.danger
238 ERROR = 500, _('Error'), ColorEnum.danger
241class LabelPrinterMachine(BaseMachineType):
242 """Label printer machine type, is a direct integration to print labels for various items."""
244 SLUG = 'label-printer'
245 NAME = _('Label Printer')
246 DESCRIPTION = _('Directly print labels for various items.')
248 base_driver = LabelPrinterBaseDriver
250 MACHINE_SETTINGS = {
251 'LOCATION': {
252 'name': _('Printer Location'),
253 'description': _('Scope the printer to a specific location'),
254 'model': 'stock.stocklocation',
255 }
256 }
258 MACHINE_STATUS: type[LabelPrinterStatus] = LabelPrinterStatus
260 default_machine_status = LabelPrinterStatus.UNKNOWN
262 @property
263 def location(self):
264 """Access the machines location instance using this property."""
265 location_pk = self.get_setting('LOCATION', 'M')
267 if not location_pk:
268 return None
270 return StockLocation.objects.filter(pk=location_pk).first()