• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

inventree / InvenTree / 8535206116

03 Apr 2024 07:59AM UTC coverage: 92.51% (+0.01%) from 92.496%
8535206116

Pull #6916

github

web-flow
Merge 521f23097 into 6be2ede5e
Pull Request #6916: Some small style fixes

32 of 38 new or added lines in 16 files covered. (84.21%)

1 existing line in 1 file now uncovered.

31259 of 33790 relevant lines covered (92.51%)

0.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.96
/src/backend/InvenTree/plugin/base/label/mixins.py
1
"""Plugin mixin classes for label plugins."""
2

3
from typing import Union
4

5
from django.core.exceptions import ValidationError
6
from django.db.models.query import QuerySet
7
from django.http import JsonResponse
8
from django.utils.translation import gettext_lazy as _
9

10
import pdf2image
11
from rest_framework import serializers
12
from rest_framework.request import Request
13

14
from build.models import BuildLine
15
from common.models import InvenTreeSetting
16
from InvenTree.exceptions import log_error
17
from InvenTree.tasks import offload_task
18
from label.models import LabelTemplate
19
from part.models import Part
20
from plugin.base.label import label as plugin_label
21
from plugin.helpers import MixinNotImplementedError
22
from stock.models import StockItem, StockLocation
23

24
LabelItemType = Union[StockItem, StockLocation, Part, BuildLine]
25

26

27
class LabelPrintingMixin:
28
    """Mixin which enables direct printing of stock labels.
29

30
    Each plugin must provide a NAME attribute, which is used to uniquely identify the printer.
31

32
    The plugin *must* also implement the print_label() function for rendering an individual label
33

34
    Note that the print_labels() function can also be overridden to provide custom behavior.
35
    """
36

37
    # If True, the print_label() method will block until the label is printed
38
    # If False, the offload_label() method will be called instead
39
    # By default, this is False, which means that labels will be printed in the background
40
    BLOCKING_PRINT = False
1✔
41

42
    class MixinMeta:
1✔
43
        """Meta options for this mixin."""
44

45
        MIXIN_NAME = 'Label printing'
46

47
    def __init__(self):  # pragma: no cover
48
        """Register mixin."""
49
        super().__init__()
1✔
50
        self.add_mixin('labels', True, __class__)
1✔
51

52
    def render_to_pdf(self, label: LabelTemplate, request, **kwargs):
1✔
53
        """Render this label to PDF format.
54

55
        Arguments:
56
            label: The LabelTemplate object to render
57
            request: The HTTP request object which triggered this print job
58
        """
59
        try:
1✔
60
            return label.render(request)
1✔
NEW
61
        except Exception:
×
62
            log_error('label.render_to_pdf')
×
63
            raise ValidationError(_('Error rendering label to PDF'))
×
64

65
    def render_to_html(self, label: LabelTemplate, request, **kwargs):
1✔
66
        """Render this label to HTML format.
67

68
        Arguments:
69
            label: The LabelTemplate object to render
70
            request: The HTTP request object which triggered this print job
71
        """
72
        try:
1✔
73
            return label.render_as_string(request)
1✔
NEW
74
        except Exception:
×
75
            log_error('label.render_to_html')
×
76
            raise ValidationError(_('Error rendering label to HTML'))
×
77

78
    def render_to_png(self, label: LabelTemplate, request=None, **kwargs):
1✔
79
        """Render this label to PNG format.
80

81
        Arguments:
82
            label: The LabelTemplate object to render
83
            request: The HTTP request object which triggered this print job
84
        Keyword Arguments:
85
            pdf_data: The raw PDF data of the rendered label (if already rendered)
86
            dpi: The DPI to use for the PNG rendering
87
            use_cairo (bool): Whether to use the pdftocairo backend for rendering which provides better results in tests,
88
                see [#6488](https://github.com/inventree/InvenTree/pull/6488) for details. If False, pdftoppm is used (default: True)
89
            pdf2image_kwargs (dict): Additional keyword arguments to pass to the
90
                [`pdf2image.convert_from_bytes`](https://pdf2image.readthedocs.io/en/latest/reference.html#pdf2image.pdf2image.convert_from_bytes) method (optional)
91
        """
92
        # Check if pdf data is provided
93
        pdf_data = kwargs.get('pdf_data', None)
1✔
94

95
        if not pdf_data:
1✔
96
            pdf_data = (
97
                self.render_to_pdf(label, request, **kwargs).get_document().write_pdf()
98
            )
99

100
        pdf2image_kwargs = {
1✔
101
            'dpi': kwargs.get('dpi', InvenTreeSetting.get_setting('LABEL_DPI', 300)),
1✔
102
            'use_pdftocairo': kwargs.get('use_cairo', True),
1✔
103
            **kwargs.get('pdf2image_kwargs', {}),
1✔
104
        }
105

106
        # Convert to png data
107
        try:
1✔
108
            return pdf2image.convert_from_bytes(pdf_data, **pdf2image_kwargs)[0]
1✔
NEW
109
        except Exception:
×
110
            log_error('label.render_to_png')
×
111
            raise ValidationError(_('Error rendering label to PNG'))
×
112

113
    def print_labels(
1✔
114
        self,
115
        label: LabelTemplate,
1✔
116
        items: QuerySet[LabelItemType],
1✔
117
        request: Request,
1✔
118
        **kwargs,
119
    ):
120
        """Print one or more labels with the provided template and items.
121

122
        Arguments:
123
            label: The LabelTemplate object to use for printing
124
            items: The list of database items to print (e.g. StockItem instances)
125
            request: The HTTP request object which triggered this print job
126

127
        Keyword Arguments:
128
            printing_options: The printing options set for this print job defined in the PrintingOptionsSerializer
129

130
        Returns:
131
            A JSONResponse object which indicates outcome to the user
132

133
        The default implementation simply calls print_label() for each label, producing multiple single label output "jobs"
134
        but this can be overridden by the particular plugin.
135
        """
136
        try:
1✔
137
            user = request.user
1✔
138
        except AttributeError:
139
            user = None
140

141
        # Generate a label output for each provided item
142
        for item in items:
1✔
143
            label.object_to_print = item
1✔
144
            filename = label.generate_filename(request)
1✔
145
            pdf_file = self.render_to_pdf(label, request, **kwargs)
1✔
146
            pdf_data = pdf_file.get_document().write_pdf()
1✔
147
            png_file = self.render_to_png(label, request, pdf_data=pdf_data, **kwargs)
1✔
148

149
            print_args = {
1✔
150
                'pdf_file': pdf_file,
1✔
151
                'pdf_data': pdf_data,
1✔
152
                'png_file': png_file,
1✔
153
                'filename': filename,
1✔
154
                'label_instance': label,
1✔
155
                'item_instance': item,
1✔
156
                'user': user,
1✔
157
                'width': label.width,
1✔
158
                'height': label.height,
1✔
159
                'printing_options': kwargs['printing_options'],
1✔
160
            }
161

162
            if self.BLOCKING_PRINT:
1✔
163
                # Blocking print job
164
                self.print_label(**print_args)
165
            else:
166
                # Non-blocking print job
167

168
                # Offload the print job to a background worker
169
                self.offload_label(**print_args)
1✔
170

171
        # Return a JSON response to the user
172
        return JsonResponse({
1✔
173
            'success': True,
1✔
174
            'message': f'{len(items)} labels printed',
1✔
175
        })
176

177
    def print_label(self, **kwargs):
1✔
178
        """Print a single label (blocking).
179

180
        kwargs:
181
            pdf_file: The PDF file object of the rendered label (WeasyTemplateResponse object)
182
            pdf_data: Raw PDF data of the rendered label
183
            filename: The filename of this PDF label
184
            label_instance: The instance of the label model which triggered the print_label() method
185
            item_instance: The instance of the database model against which the label is printed
186
            user: The user who triggered this print job
187
            width: The expected width of the label (in mm)
188
            height: The expected height of the label (in mm)
189
            printing_options: The printing options set for this print job defined in the PrintingOptionsSerializer
190

191
        Note that the supplied kwargs may be different if the plugin overrides the print_labels() method.
192
        """
193
        # Unimplemented (to be implemented by the particular plugin class)
194
        raise MixinNotImplementedError(
1✔
195
            'This Plugin must implement a `print_label` method'
1✔
196
        )
197

198
    def offload_label(self, **kwargs):
1✔
199
        """Offload a single label (non-blocking).
200

201
        Instead of immediately printing the label (which is a blocking process),
202
        this method should offload the label to a background worker process.
203

204
        Offloads a call to the 'print_label' method (of this plugin) to a background worker.
205
        """
206
        # Exclude the 'pdf_file' object - cannot be pickled
207
        kwargs.pop('pdf_file', None)
1✔
208

209
        offload_task(plugin_label.print_label, self.plugin_slug(), **kwargs)
1✔
210

211
    def get_printing_options_serializer(
1✔
212
        self, request: Request, *args, **kwargs
1✔
213
    ) -> Union[serializers.Serializer, None]:
1✔
214
        """Return a serializer class instance with dynamic printing options.
215

216
        Arguments:
217
            request: The request made to print a label or interfering the available serializer fields via an OPTIONS request
218
            *args, **kwargs: need to be passed to the serializer instance
219

220
        Returns:
221
            A class instance of a DRF serializer class, by default this an instance of
222
            self.PrintingOptionsSerializer using the *args, **kwargs if existing for this plugin
223
        """
224
        serializer = getattr(self, 'PrintingOptionsSerializer', None)
1✔
225

226
        if not serializer:
1✔
227
            return None
1✔
228

229
        return serializer(*args, **kwargs)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc