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

1"""Label printing machine type.""" 

2 

3from typing import cast 

4 

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 _ 

10 

11from PIL.Image import Image 

12from rest_framework import serializers 

13from rest_framework.request import Request 

14 

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 

21 

22 

23class LabelPrinterBaseDriver(BaseDriver): 

24 """Base driver for label printer machines. 

25 

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 """ 

29 

30 machine_type = 'label-printer' 

31 

32 USE_BACKGROUND_WORKER = True 

33 

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. 

42 

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) 

47 

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 

52 

53 Note that the supplied args/kwargs may be different if the driver overrides the print_labels() method. 

54 """ 

55 

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. 

64 

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) 

69 

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 

74 

75 Returns: 

76 If `USE_BACKGROUND_WORKER=False`, a JsonResponse object which indicates outcome to the user, otherwise None 

77 

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) 

83 

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. 

88 

89 By default all printers that are initialized using this driver are returned. 

90 

91 Arguments: 

92 label: The LabelTemplate object to use for printing 

93 items: The lost of database items to print (e.g. StockItem instances) 

94 

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()) 

99 

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. 

104 

105 Arguments: 

106 request: The request made to print a label or interfering the available serializer fields via an OPTIONS request 

107 

108 Note: 

109 `*args`, `**kwargs` needs to be passed to the serializer instance 

110 

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) 

115 

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) 

122 

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. 

127 

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) 

134 

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. 

139 

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) 

145 

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. 

148 

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) 

155 

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. 

160 

161 Arguments: 

162 label: The LabelTemplate object to render 

163 item: The item to render the label with 

164 

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) 

175 

176 def _get_dummy_request(self): 

177 """Return a dummy request object to it work with legacy code. 

178 

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() 

185 

186 return r 

187 

188 required_overrides = [[print_label, print_labels]] 

189 

190 class PrintingOptionsSerializer(serializers.Serializer): 

191 """Printing options serializer that implements common options. 

192 

193 This can be overridden by the driver to implement custom options, but the driver should always extend this class. 

194 

195 Example: 

196 This example shows how to extend the default serializer and add a new option: 

197 ```py 

198 class MyDriver(LabelPrinterBaseDriver): 

199 # ... 

200 

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 """ 

209 

210 copies = serializers.IntegerField( 

211 default=1, 

212 label=_('Copies'), 

213 help_text=_('Number of copies to print for each label'), 

214 ) 

215 

216 

217class LabelPrinterStatus(MachineStatus): 

218 """Label printer status codes. 

219 

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 """ 

230 

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 

239 

240 

241class LabelPrinterMachine(BaseMachineType): 

242 """Label printer machine type, is a direct integration to print labels for various items.""" 

243 

244 SLUG = 'label-printer' 

245 NAME = _('Label Printer') 

246 DESCRIPTION = _('Directly print labels for various items.') 

247 

248 base_driver = LabelPrinterBaseDriver 

249 

250 MACHINE_SETTINGS = { 

251 'LOCATION': { 

252 'name': _('Printer Location'), 

253 'description': _('Scope the printer to a specific location'), 

254 'model': 'stock.stocklocation', 

255 } 

256 } 

257 

258 MACHINE_STATUS: type[LabelPrinterStatus] = LabelPrinterStatus 

259 

260 default_machine_status = LabelPrinterStatus.UNKNOWN 

261 

262 @property 

263 def location(self): 

264 """Access the machines location instance using this property.""" 

265 location_pk = self.get_setting('LOCATION', 'M') 

266 

267 if not location_pk: 

268 return None 

269 

270 return StockLocation.objects.filter(pk=location_pk).first()