Completed
Branch master (9edffc)
by Jordi
04:36
created

bika.lims.browser.stickers.Sticker.__call__()   A

Complexity

Conditions 4

Size

Total Lines 34
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 34
rs 9.352
c 0
b 0
f 0
cc 4
nop 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.CORE
4
#
5
# Copyright 2018 by it's authors.
6
# Some rights reserved. See LICENSE.rst, CONTRIBUTORS.rst.
7
8
from Products.CMFCore.utils import getToolByName
9
from Products.CMFPlone.utils import safe_unicode
10
from bika.lims import bikaMessageFactory as _, t
11
from bika.lims import logger
12
from bika.lims.browser import BrowserView
13
from zope.component.interfaces import ComponentLookupError
14
from bika.lims.utils import createPdf, to_int
15
from bika.lims.vocabularies import getStickerTemplates
16
from plone.resource.utils import iterDirectoriesOfType, queryResourceDirectory
17
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
18
import glob, os, os.path, sys, traceback
19
from bika.lims.interfaces import IGetStickerTemplates
20
from zope.component import getAdapters
21
22
import os
23
import App
24
import tempfile
25
26
27
class Sticker(BrowserView):
28
    """ Invoked via URL on an object or list of objects from the types
29
        AnalysisRequest, Sample or ReferenceSample.
30
        Renders a preview for the objects, a control to allow the user to
31
        select the stcker template to be invoked and print.
32
33
        In order to create a sticker inside an Add-on you have to create a
34
        directory inside the resource directory. Look at those examples:
35
        This defines the resource folder to look for.
36
        - path: bika/addon/stickers/configure.zcml
37
            ...
38
            **Defining stickers for samples, analysisrequests and partitions
39
            <plone:static
40
              directory="templates"
41
              type="stickers"
42
              name="ADDON stickers" />
43
            ...
44
        This is how to add general stickers for analysis requests, samples
45
        and partitions.
46
        - bika/addon/stickers/templates/
47
            -- code_39_40x20mm.{css,pt}
48
            -- other_{sample,ar,partition}_stickers_...
49
50
        This is the wasy to create specific sticker for a content type.
51
        Note that in this case the directory '/worksheet' should contain the
52
        sticker templates for worksheet objects.
53
        - bika/addon/stickers/templates/worksheet
54
            -- code_...mm.{css,pt}
55
            -- other_worksheet_stickers_...
56
    """
57
    template = ViewPageTemplateFile("templates/stickers_preview.pt")
58
59
    def __init__(self, context, request):
60
        super(Sticker, self).__init__(context, request)
61
        self.item_index = 0
62
        self.current_item = None
63
        self.copies_count = None
64
        self.context = context
65
        self.request = request
66
67
    def __call__(self):
68
        # Need to generate a PDF with the stickers?
69
        if self.request.form.get('pdf', '0') == '1':
70
            response = self.request.response
71
            response.setHeader('Content-type', 'application/pdf')
72
            response.setHeader('Content-Disposition', 'inline')
73
            response.setHeader('filename', 'sticker.pdf')
74
            pdfstream = self.pdf_from_post()
75
            return pdfstream
76
77
        self.copies_count = self.get_copies_count()
78
79
        items = self.request.get('items', '')
80
        # If filter by type is given in the request, only the templates under
81
        # the path with the type name will be given as vocabulary.
82
        # Example: If filter_by_type=='worksheet', only *.tp files under a
83
        # folder with filter_by_type as name will be displayed.
84
        self.filter_by_type = self.request.get('filter_by_type', False)
85
        catalog = getToolByName(self.context, 'uid_catalog')
86
        self.items = [o.getObject() for o in catalog(UID=items.split(","))]
87
        if not self.items:
88
            # Default fallback, load from context
89
            self.items = [self.context, ]
90
91
        # before retrieving the required data for each type of object copy
92
        # each object as many times as the number of desired sticker copies
93
        self.items = self._resolve_number_of_copies(self.items)
94
        if not self.items:
95
            logger.warning(
96
                "Cannot print stickers: no items specified in request")
97
            self.request.response.redirect(self.context.absolute_url())
98
            return
99
100
        return self.template()
101
102
    def getAvailableTemplates(self):
103
        """ Returns an array with the templates of stickers available. Each
104
            array item is a dictionary with the following structure:
105
                {'id': <template_id>,
106
                 'title': <teamplate_title>,
107
                 'selected: True/False'}
108
        """
109
        # Getting adapters for current context. those adapters will return
110
        # the desired sticker templates for the current context:
111
        try:
112
            adapters = getAdapters((self.context, ), IGetStickerTemplates)
113
        except ComponentLookupError:
114
            logger.info('No IGetStickerTemplates adapters found.')
115
            adapters = None
116
        templates = []
117
        if adapters is not None:
118
            # Gather all templates
119
            for name, adapter in adapters:
120
                templates += adapter(self.request)
121
        if templates:
122
            return templates
123
        # If there are no adapters, get all sticker templates in the system
124
        seltemplate = self.getSelectedTemplate()
125
        for temp in getStickerTemplates(filter_by_type=self.filter_by_type):
126
            out = temp
127
            out['selected'] = temp.get('id', '') == seltemplate
128
            templates.append(out)
129
        return templates
130
131
    def getSelectedTemplate(self):
132
        """ Returns the id of the sticker template selected. If no specific
133
            template found in the request (parameter template), returns the
134
            default template set in Bika Setup > Stickers.
135
            If the template doesn't exist, uses the default bika.lims'
136
            Code_128_1x48mm.pt template (was sticker_small.pt).
137
            If no template selected but size param, get the sticker template
138
            set as default in Bika Setup for the size set.
139
        """
140
        # Default sticker
141
        bs_template = self.context.bika_setup.getAutoStickerTemplate()
142
        size = self.request.get('size', '')
143
        resource_type = 'stickers'
144
        if self.filter_by_type:
145
            templates = getStickerTemplates(filter_by_type=self.filter_by_type)
146
            # Get the first sticker
147
            bs_template = templates[0].get('id', '') if templates else ''
148
        elif size == 'small':
149
            bs_template = self.context.bika_setup.getSmallStickerTemplate()
150
        elif size == 'large':
151
            bs_template = self.context.bika_setup.getLargeStickerTemplate()
152
        rq_template = self.request.get('template', bs_template)
153
        # Check if the template exists. If not, fallback to default's
154
        # 'prefix' is also the resource folder's name
155
        prefix = ''
156
        templates_dir = ''
157
        if rq_template.find(':') >= 0:
158
            prefix, rq_template = rq_template.split(':')
159
            templates_dir = self._getStickersTemplatesDirectory(prefix)
160
        else:
161
            this_dir = os.path.dirname(os.path.abspath(__file__))
162
            templates_dir = os.path.join(this_dir, 'templates/stickers/')
163
            if self.filter_by_type:
164
                templates_dir = templates_dir + '/' + self.filter_by_type
165
        if not os.path.isfile(os.path.join(templates_dir, rq_template)):
166
            rq_template = 'Code_128_1x48mm.pt'
167
        return '%s:%s' % (prefix, rq_template) if prefix else rq_template
168
169
    def getSelectedTemplateCSS(self):
170
        """ Looks for the CSS file from the selected template and return its
171
            contents. If the selected template is default.pt, looks for a
172
            file named default.css in the stickers path and return its contents.
173
            If no CSS file found, retrns an empty string
174
        """
175
        template = self.getSelectedTemplate()
176
        # Look for the CSS
177
        content = ''
178
        if template.find(':') >= 0:
179
            # A template from another add-on
180
            prefix, template = template.split(':')
181
            templates_dir = self._getStickersTemplatesDirectory(prefix)
182
            css = '{0}.css'.format(template[:-3])
183
            if css in os.listdir(templates_dir):
184
                path = '%s/%s.css' % (templates_dir, template[:-3])
185
                if os.path.isfile(path):
186
                    with open(path, 'r') as content_file:
187
                        content = content_file.read()
188
        else:
189
            this_dir = os.path.dirname(os.path.abspath(__file__))
190
            templates_dir = os.path.join(this_dir, 'templates/stickers/')
191
            # Only use the directory asked in 'filter_by_type'
192
            if self.filter_by_type:
193
                templates_dir = templates_dir + '/' + self.filter_by_type
194
            path = '%s/%s.css' % (templates_dir, template[:-3])
195
            if os.path.isfile(path):
196
                with open(path, 'r') as content_file:
197
                    content = content_file.read()
198
        return content
199
200
    def nextItem(self):
201
        """ Iterates to the next item in the list and moves one position up the
202
            item index. If the end of the list of items is reached, returns the
203
            first item of the list.
204
        """
205
        if self.item_index == len(self.items):
206
            self.item_index = 0
207
        self.current_item = [self.items[self.item_index]]
208
        self.item_index += 1
209
        return self.current_item
210
211
    def renderItem(self):
212
        """ Renders the next available sticker.
213
            Uses the template specified in the request ('template' parameter) by
214
            default. If no template defined in the request, uses the default
215
            template set by default in Bika Setup > Stickers.
216
            If the template specified doesn't exist, uses the default
217
            bika.lims' Code_128_1x48mm.pt template (was sticker_small.pt).
218
        """
219
        curritem = self.nextItem()
220
        templates_dir = 'templates/stickers'
221
        embedt = self.getSelectedTemplate()
222
        if embedt.find(':') >= 0:
223
            prefix, embedt = embedt.split(':')
224
            templates_dir = self._getStickersTemplatesDirectory(prefix)
225
        elif self.filter_by_type:
226
            templates_dir = '/'.join([templates_dir, self.filter_by_type])
227
        fullpath = os.path.join(templates_dir, embedt)
228
        try:
229
            embed = ViewPageTemplateFile(fullpath)
230
            return embed(self)
231
        except:
232
            tbex = traceback.format_exc()
233
            return "<div class='error'>%s - %s '%s':<pre>%s</pre></div>" % \
234
                    (repr(curritem), _("Unable to load the template"), embedt, tbex)
235
236
    def getItemsURL(self):
237
        req_items = self.request.get('items', '')
238
        req_items = req_items if req_items else self.context.getId()
239
        req = '%s?items=%s' % (self.request.URL, req_items)
240
        return req
241
242
    def _getStickersTemplatesDirectory(self, resource_name):
243
        """
244
        Returns the paths for the directory containing the css and pt files
245
        for the stickers deppending on the filter_by_type.
246
        :param resource_name: The name of the resource folder.
247
        :type resource_name: string
248
        :returns: a string as a path
249
        """
250
        templates_dir =\
251
            queryResourceDirectory('stickers', resource_name).directory
252
        if self.filter_by_type:
253
            templates_dir = templates_dir + '/' + self.filter_by_type
254
        return templates_dir
255
256
    def pdf_from_post(self):
257
        """Returns a pdf stream with the stickers
258
        """
259
        html = self.request.form.get('html')
260
        style = self.request.form.get('style')
261
        reporthtml = '<html><head>{0}</head><body>{1}</body></html>'
262
        reporthtml = reporthtml.format(style, html)
263
        reporthtml = safe_unicode(reporthtml).encode('utf-8')
264
        pdf_fn = tempfile.mktemp(suffix='.pdf')
265
        pdf_file = createPdf(htmlreport=reporthtml, outfile=pdf_fn)
266
        return pdf_file
267
268
    def _resolve_number_of_copies(self, items):
269
        """For the given objects generate as many copies as the desired
270
        number of stickers. The desired number of stickers for each
271
        object is given by copies_count
272
273
        :param items: list of objects whose stickers are going to be previewed.
274
        :type items: list
275
        :returns: list containing n copies of each object in the items list,
276
        where n is self.copies_count
277
        :rtype: list
278
        """
279
        copied_items = []
280
        for obj in items:
281
            for copy in range(self.copies_count):
282
                copied_items.append(obj)
283
        return copied_items
284
285
    def get_copies_count(self):
286
        """Return the copies_count number request parameter
287
288
        :returns: the number of copies for each sticker as stated
289
        in the request
290
        :rtype: int
291
        """
292
        default_num = self.context.bika_setup.getDefaultNumberOfCopies()
293
        request_num = self.request.form.get("copies_count")
294
        return to_int(request_num, default_num)
295