Coverage for src/backend/InvenTree/plugin/builtin/labels/inventree_machine.py: 25%

80 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""Label printing plugin that provides support for printing using a label printer machine.""" 

2 

3from typing import cast 

4 

5from django.conf import settings 

6from django.utils.translation import gettext_lazy as _ 

7 

8from rest_framework import serializers 

9 

10from common.models import InvenTreeUserSetting 

11from InvenTree.serializers import DependentField 

12from InvenTree.tasks import offload_task 

13from machine.machine_types import LabelPrinterBaseDriver, LabelPrinterMachine 

14from plugin import InvenTreePlugin 

15from plugin.machine import call_machine_function, registry 

16from plugin.mixins import LabelPrintingMixin 

17from report.models import LabelTemplate 

18 

19 

20def get_machine_and_driver(machine_pk: str): 

21 """Get the driver by machine pk and ensure that it is a label printing driver.""" 

22 machine = registry.get_machine(machine_pk) 

23 

24 # machine should be valid due to the machine select field validator 

25 if machine is None: # pragma: no cover 

26 return None, None 

27 

28 if machine.SLUG != 'label-printer': # pragma: no cover 

29 return None, None 

30 

31 machine = cast(LabelPrinterMachine, machine) 

32 driver = machine.driver 

33 

34 if driver is None: # pragma: no cover 

35 return machine, None 

36 

37 return machine, cast(LabelPrinterBaseDriver, driver) 

38 

39 

40def get_last_used_printers(user): 

41 """Get the last used printers for a specific user.""" 

42 return [ 

43 printer 

44 for printer in cast( 

45 str, 

46 InvenTreeUserSetting.get_setting( 

47 'LAST_USED_PRINTING_MACHINES', '', user=user 

48 ), 

49 ).split(',') 

50 if printer 

51 ] 

52 

53 

54class InvenTreeLabelPlugin(LabelPrintingMixin, InvenTreePlugin): 

55 """Builtin plugin for machine label printing. 

56 

57 This enables machines to print labels. 

58 """ 

59 

60 NAME = 'InvenTreeLabelMachine' 

61 TITLE = _('InvenTree machine label printer') 

62 DESCRIPTION = _('Provides support for printing using a machine') 

63 VERSION = '1.0.0' 

64 AUTHOR = _('InvenTree contributors') 

65 

66 def print_labels(self, label: LabelTemplate, output, items, request, **kwargs): 

67 """Print labels implementation that calls the correct machine driver print_labels method.""" 

68 machine, driver = get_machine_and_driver( 

69 kwargs['printing_options'].get('machine', '') 

70 ) 

71 

72 # the driver and machine should be valid due to the machine select field validator 

73 if driver is None or machine is None: # pragma: no cover 

74 return None 

75 

76 print_kwargs = { 

77 **kwargs, 

78 'printing_options': kwargs['printing_options'].get('driver_options', {}), 

79 } 

80 

81 if output: 

82 user = output.user 

83 elif request: 

84 user = request.user 

85 else: 

86 user = None 

87 

88 # Save the current used printer as last used printer 

89 # Only the last ten used printers are saved so that this list doesn't grow infinitely 

90 if user and user.is_authenticated: 

91 last_used_printers = get_last_used_printers(user) 

92 machine_pk = str(machine.pk) 

93 if machine_pk in last_used_printers: 

94 last_used_printers.remove(machine_pk) 

95 last_used_printers.insert(0, machine_pk) 

96 InvenTreeUserSetting.set_setting( 

97 'LAST_USED_PRINTING_MACHINES', 

98 ','.join(last_used_printers[:10]), 

99 user=user, 

100 ) 

101 

102 offload_task( 

103 call_machine_function, 

104 machine.pk, 

105 'print_labels', 

106 label, 

107 items, 

108 output=output, 

109 force_sync=settings.TESTING or driver.USE_BACKGROUND_WORKER, 

110 group='plugin', 

111 **print_kwargs, 

112 ) 

113 

114 # Inform the user that the process has been offloaded to the printer 

115 if output: 

116 output.mark_complete() 

117 

118 class PrintingOptionsSerializer(serializers.Serializer): 

119 """Printing options serializer that adds a machine select and the machines options.""" 

120 

121 def __init__(self, *args, **kwargs): 

122 """Custom __init__ method to dynamically override the machine choices based on the request.""" 

123 super().__init__(*args, **kwargs) 

124 

125 # TODO @matmair Re-enable this when the need is clear 

126 # view = kwargs['context']['view'] 

127 template = None # view.get_object() 

128 items_to_print = None # view.get_items() 

129 

130 # get all available printers for each driver 

131 machines: list[LabelPrinterMachine] = [] 

132 for driver in cast( 

133 list[LabelPrinterBaseDriver], registry.get_drivers('label-printer') 

134 ): 

135 machines.extend( 

136 driver.get_printers( 

137 template, items_to_print, request=kwargs['context']['request'] 

138 ) 

139 ) 

140 

141 # sort the last used printers for the user to the top 

142 user = kwargs['context']['request'].user 

143 last_used_printers = get_last_used_printers(user)[::-1] 

144 machines = sorted( 

145 machines, 

146 key=lambda m: ( 

147 last_used_printers.index(str(m.pk)) 

148 if str(m.pk) in last_used_printers 

149 else -1 

150 ), 

151 reverse=True, 

152 ) 

153 

154 choices = [(str(m.pk), self.get_printer_name(m)) for m in machines] 

155 

156 # if there are choices available, use the first as default 

157 if len(choices) > 0: 

158 self.fields['machine'].default = choices[0][0] 

159 

160 # add 'last used' flag to the first choice 

161 if choices[0][0] in last_used_printers: 

162 choices[0] = ( 

163 choices[0][0], 

164 choices[0][1] + ' (' + _('last used') + ')', 

165 ) 

166 

167 self.fields['machine'].choices = choices 

168 

169 def get_printer_name(self, machine: LabelPrinterMachine): 

170 """Construct the printers name.""" 

171 name = machine.name 

172 

173 if machine.location: 

174 name += f' @ {machine.location.name}' 

175 

176 return name 

177 

178 machine = serializers.ChoiceField(choices=[]) 

179 

180 driver_options = DependentField( 

181 label=_('Options'), 

182 depends_on=['machine'], 

183 field_serializer='get_driver_options', 

184 required=False, 

185 ) 

186 

187 def get_driver_options(self, fields): 

188 """Returns the selected machines serializer.""" 

189 _, driver = get_machine_and_driver(fields['machine']) 

190 

191 if driver is None: 

192 return None 

193 

194 return driver.get_printing_options_serializer( 

195 self.context['request'], context=self.context 

196 )