Passed
Push — 2.x ( a93928...ced756 )
by Jordi
08:02 queued 01:02
created

StickerView.render_sticker()   B

Complexity

Conditions 5

Size

Total Lines 30
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 30
rs 8.9332
c 0
b 0
f 0
cc 5
nop 2
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE.
4
#
5
# SENAITE.CORE is free software: you can redistribute it and/or modify it under
6
# the terms of the GNU General Public License as published by the Free Software
7
# Foundation, version 2.
8
#
9
# This program is distributed in the hope that it will be useful, but WITHOUT
10
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12
# details.
13
#
14
# You should have received a copy of the GNU General Public License along with
15
# this program; if not, write to the Free Software Foundation, Inc., 51
16
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
#
18
# Copyright 2018-2025 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import os
22
import os.path
23
import tempfile
24
import traceback
25
26
from bika.lims import api
27
from bika.lims import logger
28
from bika.lims import senaiteMessageFactory as _
29
from bika.lims.browser import BrowserView
30
from bika.lims.utils import createPdf
31
from bika.lims.utils import to_int
32
from plone.resource.utils import queryResourceDirectory
33
from Products.CMFPlone.utils import safe_unicode
34
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
35
from senaite.app.supermodel import SuperModel
36
from senaite.core.interfaces import IGetStickerTemplates
37
from senaite.core.vocabularies.stickers import get_sticker_templates
38
from zope.component import getAdapters
39
from zope.component.interfaces import ComponentLookupError
40
41
42
class StickerView(BrowserView):
43
    """Invoked via URL on an object or list of objects from the types
44
       AnalysisRequest, Sample or ReferenceSample.
45
46
       Renders a preview for the objects, a control to allow the user to
47
       select the sticker template to be invoked and print.
48
49
       In order to create a sticker inside an Add-on you have to create a
50
       directory inside the resource directory
51
52
       This defines the resource folder to look for:
53
54
       - path: addon/stickers/configure.zcml
55
           ...
56
           **Defining stickers for samples and partitions
57
           <plone:static
58
             directory="templates"
59
             type="stickers"
60
             name="ADDON stickers" />
61
           ...
62
63
       This is how to add general stickers for samples:
64
65
       - addon/stickers/templates/
66
67
           -- code_39_40x20mm.{css,pt}
68
           -- other_{sample,ar,partition}_stickers_...
69
70
       This is the way to create specific sticker for a content type.
71
72
       Note that in this case the directory '/worksheet' should contain the
73
       sticker templates for worksheet objects.
74
75
       - addon/stickers/templates/worksheet
76
           -- code_...mm.{css,pt}
77
           -- other_worksheet_stickers_...
78
    """
79
    template = ViewPageTemplateFile("templates/stickers_preview.pt")
80
    stickers_template = ViewPageTemplateFile("templates/stickers.pt")
81
82
    def __init__(self, context, request):
83
        super(StickerView, self).__init__(context, request)
84
        self.context = context
85
        self.request = request
86
        self.current_item = None
87
88
    def __call__(self):
89
        # Need to generate a PDF with the stickers?
90
        if self.request.form.get("pdf", "0") == "1":
91
            response = self.request.response
92
            response.setHeader("Content-type", "application/pdf")
93
            response.setHeader("Content-Disposition", "inline")
94
            response.setHeader("filename", "sticker.pdf")
95
            pdfstream = self.pdf_from_post()
96
            return pdfstream
97
98
        if not self.get_items():
99
            logger.warning(
100
                "Cannot print stickers: no items specified in request")
101
            self.request.response.redirect(self.context.absolute_url())
102
            return
103
104
        return self.template()
105
106
    @property
107
    def filter_by_type(self):
108
        """Returns the filter type from the request
109
110
        If filter by type is given in the request, only the templates under the
111
        path with the type name will be given as vocabulary.
112
113
        Example:
114
        If filter_by_type=='worksheet', only *.pt files under a folder with
115
        filter_by_type as name will be displayed.
116
        """
117
        return self.request.get("filter_by_type")
118
119
    @property
120
    def items(self):
121
        """Returns the selected items from the request
122
        """
123
        return self.get_items()
124
125
    def get_items(self):
126
        """Returns a list of SuperModel items
127
        """
128
        uids = self.get_uids()
129
        items = map(lambda uid: SuperModel(uid), uids)
130
        return self._resolve_number_of_copies(items)
131
132
    def get_back_url(self):
133
        """Calculate the Back URL
134
        """
135
        url = api.get_url(self.context)
136
        portal_type = api.get_portal_type(self.context)
137
        redirect_contexts = ["Client", "AnalysisRequest", "Samples", "Batch"]
138
        if portal_type not in redirect_contexts:
139
            parent = api.get_parent(self.context)
140
            url = api.get_url(parent)
141
        # redirect to direct results entry
142
        setup = api.get_setup()
143
        if setup.getImmediateResultsEntry():
144
            url = "{}/multi_results?uids={}".format(
145
                url, ",".join(self.get_uids()))
146
        return url
147
148
    def get_uids(self):
149
        """Parse the UIDs from the request `items` parameter
150
        """
151
        uids = filter(None, self.request.get("items", "").split(","))
152
        if not uids:
153
            return [api.get_uid(self.context)]
154
        return uids
155
156
    def get_sticker_template_adapters(self):
157
        """Query context adapters for IGetStickerTemplate
158
        """
159
        try:
160
            return getAdapters((self.context, ), IGetStickerTemplates)
161
        except ComponentLookupError:
162
            logger.debug("No IGetStickerTemplates adapters found for %s."
163
                         % api.get_path(self.context))
164
            return []
165
166
    def get_available_templates(self):
167
        """Returns a list of available sticker templates
168
169
        Each list item is a dictionary with the following structure:
170
171
            {
172
                "id": <template_id>,
173
                "title": <teamplate_title>,
174
                "selected: True/False",
175
            }
176
177
        :returns: list of template info records
178
        """
179
        templates = []
180
181
        adapters = self.get_sticker_template_adapters()
182
        if adapters is not None:
183
            # Gather all templates
184
            for name, adapter in adapters:
185
                templates += adapter(self.request)
186
187
        if templates:
188
            return templates
189
190
        # If there are no adapters, get all sticker templates available
191
        selected_template = self.get_selected_template()
192
        for temp in get_sticker_templates(filter_by_type=self.filter_by_type):
193
            out = temp
194
            out["selected"] = temp.get("id", "") == selected_template
195
            templates.append(out)
196
197
        return templates
198
199
    def getSelectedTemplateCSS(self):
200
        """Looks for the CSS file from the selected template and return its
201
           contents.
202
203
        If the selected template is default.pt, looks for a file named
204
        default.css in the stickers path and return its contents. If no CSS
205
        file found, retrns an empty string
206
        """
207
        template = self.get_selected_template()
208
        # Look for the CSS
209
        content = ""
210
        if template.find(":") >= 0:
211
            # A template from another add-on
212
            prefix, template = template.split(":")
213
            templates_dir = self._getStickersTemplatesDirectory(prefix)
214
            if not os.path.exists(templates_dir):
215
                return
216
            css = "{0}.css".format(template[:-3])
217
            if css in os.listdir(templates_dir):
218
                path = "%s/%s.css" % (templates_dir, template[:-3])
219
                if os.path.isfile(path):
220
                    with open(path, "r") as content_file:
221
                        content = content_file.read()
222
        else:
223
            this_dir = os.path.dirname(os.path.abspath(__file__))
224
            templates_dir = os.path.join(this_dir, "templates/stickers/")
225
            # Only use the directory asked in "filter_by_type"
226
            if self.filter_by_type:
227
                templates_dir = templates_dir + "/" + self.filter_by_type
228
            path = "%s/%s.css" % (templates_dir, template[:-3])
229
            if os.path.isfile(path):
230
                with open(path, "r") as content_file:
231
                    content = content_file.read()
232
        return content
233
234
    def render_stickers(self):
235
        """Render the outer stickers template
236
237
        NOTE: We wrapped the outer sticker template into a separate template to
238
              allow subclasses easier overrides.
239
        """
240
        return self.stickers_template()
241
242
    def render_sticker(self, item):
243
        """Renders the next available sticker.
244
245
        Uses the template specified in the request ('template' parameter) by
246
        default. If no template defined in the request, uses the default
247
        template set by default in Setup > Stickers.
248
249
        If the template specified doesn't exist, uses the default bika.lims'
250
        Code_128_1x48mm.pt template (was sticker_small.pt).
251
        """
252
        self.current_item = item
253
        templates_dir = "templates/stickers"
254
        embedt = self.get_selected_template()
255
        if embedt.find(":") >= 0:
256
            prefix, embedt = embedt.split(":")
257
            templates_dir = self._getStickersTemplatesDirectory(prefix)
258
            if not os.path.exists(templates_dir):
259
                return
260
        elif self.filter_by_type:
261
            templates_dir = "/".join([templates_dir, self.filter_by_type])
262
        fullpath = os.path.join(templates_dir, embedt)
263
264
        try:
265
            embed = ViewPageTemplateFile(fullpath)
266
            return embed(self, item=item)
267
        except Exception:
268
            exc = traceback.format_exc()
269
            msg = "<div class='error'>{} - {} '{}':<pre>{}</pre></div>".format(
270
                item.id, _("Failed to load sticker"), embedt, exc)
271
            return msg
272
273
    def getItemsURL(self):
274
        """Used in stickers_preview.pt
275
        """
276
        req_items = self.get_uids()
277
        req_items = req_items or [api.get_uid(self.context)]
278
        req = "{}?items={}".format(self.request.URL, ",".join(req_items))
279
        return req
280
281
    def _getStickersTemplatesDirectory(self, resource_name):
282
        """Returns the paths for the directory containing the css and pt files
283
        for the stickers deppending on the filter_by_type.
284
285
        :param resource_name: The name of the resource folder.
286
        :type resource_name: string
287
        :returns: a string as a path
288
        """
289
        templates_dir =\
290
            queryResourceDirectory("stickers", resource_name).directory
291
        if self.filter_by_type:
292
            templates_dir = templates_dir + "/" + self.filter_by_type
293
        return templates_dir
294
295
    def pdf_from_post(self):
296
        """Returns a pdf stream with the stickers
297
        """
298
        html = self.request.form.get("html")
299
        style = self.request.form.get("style")
300
        reporthtml = "<html><head>{0}</head><body>{1}</body></html>"
301
        reporthtml = reporthtml.format(style, html)
302
        reporthtml = safe_unicode(reporthtml).encode("utf-8")
303
        pdf_fn = tempfile.mktemp(suffix=".pdf")
304
        pdf_file = createPdf(htmlreport=reporthtml, outfile=pdf_fn)
305
        return pdf_file
306
307
    def _resolve_number_of_copies(self, items):
308
        """For the given objects generate as many copies as the desired number
309
        of stickers.
310
311
        :param items: list of objects whose stickers are going to be previewed.
312
        :type items: list
313
        :returns: list containing n copies of each object in the items list
314
        :rtype: list
315
        """
316
        copied_items = []
317
        for obj in items:
318
            for copy in range(self.get_copies_count()):
319
                copied_items.append(obj)
320
        return copied_items
321
322
    def get_copies_count(self):
323
        """Return the copies_count number request parameter
324
325
        :returns: the number of copies for each sticker as stated
326
        in the request
327
        :rtype: int
328
        """
329
        setup = api.get_setup()
330
        default_num = setup.getDefaultNumberOfCopies()
331
        request_num = self.request.form.get("copies_count")
332
        return to_int(request_num, default_num)
333
334
    def get_selected_template(self):
335
        """Return the selected template
336
        """
337
        template_id = self.request.get("template")
338
        if template_id:
339
            return template_id
340
341
        if self.filter_by_type:
342
            templates = get_sticker_templates(
343
                filter_by_type=self.filter_by_type)
344
            template_id = templates[0].get("id", "") if templates else ""
345
            if template_id:
346
                return template_id
347
348
        adapters = self.get_sticker_template_adapters()
349
        for name, adapter in adapters:
350
            default_template = getattr(adapter, "default_template", None)
351
            if not default_template:
352
                continue
353
            return default_template
354
355
        # rely on the default setup template
356
        setup = api.get_setup()
357
        size = self.request.get("size", "")
358
        if size == "small":
359
            return setup.getSmallStickerTemplate()
360
        elif size == "large":
361
            return setup.getLargeStickerTemplate()
362
        return setup.getAutoStickerTemplate()
363