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