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