Passed
Push — 2.x ( 908061...0c6678 )
by Ramon
27:59 queued 21:19
created

SamplePointVocabulary.__init__()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nop 1
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-2024 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from bika.lims import bikaMessageFactory as _
22
from bika.lims.api import is_active
23
from senaite.core.i18n import translate as t
24
from bika.lims.interfaces import IDisplayListVocabulary
25
from bika.lims.utils import to_utf8
26
from Products.Archetypes.public import DisplayList
27
from Products.CMFCore.utils import getToolByName
28
from zope.interface import implements
29
from pkg_resources import resource_filename
30
from plone.resource.utils import iterDirectoriesOfType
31
from senaite.core.p3compat import cmp
32
from zope.schema.interfaces import IVocabularyFactory
33
from zope.schema.vocabulary import SimpleTerm
34
from zope.schema.vocabulary import SimpleVocabulary
35
from zope.site.hooks import getSite
36
37
import os
38
import glob
39
40
41
class CatalogVocabulary(object):
42
    """Make vocabulary from catalog query.
43
44
    """
45
    implements(IDisplayListVocabulary)
46
47
    catalog = 'uid_catalog'
48
    contentFilter = {}
49
    key = 'UID'
50
    value = 'Title'
51
52
    def __init__(self, context, key=None, value=None, contentFilter=None):
53
        self.context = context
54
        self.key = key if key else self.key
55
        self.value = value if value else self.value
56
        self.contentFilter = \
57
            contentFilter if contentFilter else self.contentFilter
58
59
    def __call__(self, **kwargs):
60
        site = getSite()
61
        catalog = getToolByName(site, self.catalog)
62
        allow_blank = False
63
        if 'allow_blank' in kwargs:
64
            allow_blank = kwargs.get('allow_blank')
65
            del (kwargs['allow_blank'])
66
67
        self.contentFilter.update(**kwargs)
68
69
        # If a secondary deactivation/cancellation workflow is anbled,
70
        # Be sure and select only active objects, unless other instructions
71
        # are explicitly specified:
72
        if "is_active" not in self.contentFilter:
73
            self.contentFilter["is_active"] = True
74
75
        brains = catalog(self.contentFilter)
76
77
        items = [('', '')] if allow_blank else []
78
        for brain in brains:
79
            if self.key in brain and self.value in brain:
80
                key = getattr(brain, self.key)
81
                value = getattr(brain, self.value)
82
            else:
83
                obj = brain.getObjec()
84
                key = obj[self.key]
85
                key = callable(key) and key() or key
86
                value = obj[self.value]
87
                value = callable(value) and value() or value
88
            items.append((key, t(value)))
89
90
        return DisplayList(items)
91
92
93
class BikaContentVocabulary(object):
94
    """Vocabulary factory for Bika Setup objects.  We find them by listing
95
    folder contents directly.
96
    """
97
    implements(IVocabularyFactory)
98
99
    def __init__(self, folders, portal_types):
100
        self.folders = isinstance(folders, (tuple, list)) and \
101
                       folders or [folders, ]
102
        self.portal_types = isinstance(portal_types, (tuple, list)) and \
103
                            portal_types or [portal_types, ]
104
105
    def __call__(self, context):
106
        site = getSite()
107
        items = []
108
        for folder in self.folders:
109
            folder = site.restrictedTraverse(folder)
110
            for portal_type in self.portal_types:
111
                objects = list(folder.objectValues(portal_type))
112
                objects = filter(is_active, objects)
113
                if not objects:
114
                    continue
115
                objects.sort(lambda x, y: cmp(x.Title().lower(),
116
                                              y.Title().lower()))
117
                xitems = [(t(item.Title()), item.Title()) for item in objects]
118
                xitems = [SimpleTerm(i[1], i[1], i[0]) for i in xitems]
119
                items += xitems
120
        return SimpleVocabulary(items)
121
122
123
class BikaCatalogTypesVocabulary(object):
124
    """Vocabulary factory for really user friendly portal types,
125
    filtered to return only types listed as indexed by senaite_catalog
126
    """
127
    implements(IVocabularyFactory)
128
129
    def __call__(self, context):
130
        translate = context.translate
131
        types = (
132
            ('AnalysisRequest', translate(to_utf8(_('Sample')))),
133
            ('Batch', translate(to_utf8(_('Batch')))),
134
            # TODO Remove in >v1.3.0
135
            ('Sample', translate(to_utf8(_('Sample')))),
136
            ('ReferenceSample', translate(to_utf8(_('Reference Sample')))),
137
            ('Worksheet', translate(to_utf8(_('Worksheet'))))
138
        )
139
        items = [SimpleTerm(i[0], i[0], i[1]) for i in types]
140
        return SimpleVocabulary(items)
141
142
143
BikaCatalogTypesVocabularyFactory = BikaCatalogTypesVocabulary()
144
145
146
class AnalysisCategoryVocabulary(BikaContentVocabulary):
147
    """" AnalysisCategories
148
149
    >>> portal = layer['portal']
150
151
    >>> from plone.app.testing import TEST_USER_NAME
152
    >>> from plone.app.testing import TEST_USER_ID
153
    >>> from plone.app.testing import setRoles
154
    >>> from plone.app.testing import login
155
    >>> login(portal, TEST_USER_NAME)
156
    >>> setRoles(portal, TEST_USER_ID, ['Manager',])
157
158
    >>> from zope.component import queryUtility
159
    >>> name = 'bika.lims.vocabularies.AnalysisCategories'
160
    >>> util = queryUtility(IVocabularyFactory, name)
161
    >>> folder = portal.bika_setup.bika_analysiscategories
162
    >>> objects = folder.objectValues()
163
    >>> len(objects)
164
    3
165
166
    >>> source = util(portal)
167
    >>> source
168
    <zope.schema.vocabulary.SimpleVocabulary object at ...>
169
170
    >>> 'Water Chemistry' in source.by_token
171
    True
172
    """
173
174
    def __init__(self):
175
        BikaContentVocabulary.__init__(self,
176
                                       ['bika_setup/bika_analysiscategories', ],
177
                                       ['AnalysisCategory', ])
178
179
180
AnalysisCategoryVocabularyFactory = AnalysisCategoryVocabulary()
181
182
183
class AnalysisProfileVocabulary(BikaContentVocabulary):
184
    def __init__(self):
185
        BikaContentVocabulary.__init__(self,
186
                                       ['bika_setup/bika_analysisprofiles', ],
187
                                       ['AnalysisProfile', ])
188
189
190
AnalysisProfileVocabularyFactory = AnalysisProfileVocabulary()
191
192
193
class StorageLocationVocabulary(BikaContentVocabulary):
194
    def __init__(self):
195
        BikaContentVocabulary.__init__(self,
196
                                       ['bika_setup/bika_storagelocations', ],
197
                                       ['StorageLocation', ])
198
199
200
StorageLocationVocabularyFactory = StorageLocationVocabulary()
201
202
203
class AnalysisServiceVocabulary(BikaContentVocabulary):
204
    def __init__(self):
205
        BikaContentVocabulary.__init__(self,
206
                                       ['bika_setup/bika_analysisservices', ],
207
                                       ['AnalysisService', ])
208
209
210
AnalysisServiceVocabularyFactory = AnalysisServiceVocabulary()
211
212
213
class ClientVocabulary(BikaContentVocabulary):
214
    def __init__(self):
215
        BikaContentVocabulary.__init__(self,
216
                                       ['clients', ],
217
                                       ['Client', ])
218
219
220
ClientVocabularyFactory = ClientVocabulary()
221
222
223
class UserVocabulary(object):
224
    """ Present a vocabulary containing users in the specified
225
    list of roles
226
227
    >>> from zope.component import queryUtility
228
229
    >>> portal = layer['portal']
230
    >>> name = 'bika.lims.vocabularies.Users'
231
    >>> util = queryUtility(IVocabularyFactory, name)
232
233
    >>> tool = portal.portal_registration
234
    >>> tool.addMember('user1', 'user1',
235
    ...     properties = {
236
    ...         'username': 'user1',
237
    ...         'email': '[email protected]',
238
    ...         'fullname': 'user1'}
239
    ... )
240
    <MemberData at /plone/portal_memberdata/user1 used for /plone/acl_users>
241
242
    >>> source = util(portal)
243
    >>> source
244
    <zope.schema.vocabulary.SimpleVocabulary object at ...>
245
246
    >>> 'test_user_1_' in source.by_value
247
    True
248
    >>> 'user1' in source.by_value
249
    True
250
    """
251
    implements(IVocabularyFactory)
252
253
    def __init__(self, roles=[]):
254
        self.roles = roles if isinstance(roles, (tuple, list)) else [roles, ]
255
256
    def __call__(self, context):
257
        site = getSite()
258
        mtool = getToolByName(site, 'portal_membership')
259
        users = mtool.searchForMembers(roles=self.roles)
260
        items = [(item.getProperty('fullname'), item.getId())
261
                 for item in users]
262
        items.sort(lambda x, y: cmp(x[0].lower(), y[0].lower()))
263
        items = [SimpleTerm(i[1], i[1], i[0]) for i in items]
264
        return SimpleVocabulary(items)
265
266
267
UserVocabularyFactory = UserVocabulary()
268
269
ClientVocabularyFactory = ClientVocabulary()
270
271
272
class ClientContactVocabulary(object):
273
    """ Present Client Contacts
274
275
    >>> from zope.component import queryUtility
276
277
    >>> portal = layer['portal']
278
    >>> name = 'bika.lims.vocabularies.ClientContacts'
279
    >>> util = queryUtility(IVocabularyFactory, name)
280
281
    >>> from plone.app.testing import TEST_USER_NAME
282
    >>> from plone.app.testing import TEST_USER_ID
283
    >>> from plone.app.testing import setRoles
284
    >>> from plone.app.testing import login
285
    >>> login(portal, TEST_USER_NAME)
286
    >>> setRoles(portal, TEST_USER_ID, ['Manager',])
287
288
    >>> portal.clients.invokeFactory('Client', id='client1')
289
    'client1'
290
    >>> client1 = portal.clients.client1
291
    >>> client1.processForm()
292
    >>> client1.invokeFactory('Contact', id='contact1')
293
    'contact1'
294
    >>> contact1 = client1.contact1
295
    >>> contact1.processForm()
296
    >>> contact1.edit(Firstname='Contact', Surname='One')
297
    >>> contact1.reindexObject()
298
299
    >>> source = util(portal)
300
    >>> source
301
    <zope.schema.vocabulary.SimpleVocabulary object at ...>
302
303
    >>> 'Contact One' in source.by_value
304
    True
305
    """
306
    implements(IVocabularyFactory)
307
308
    def __call__(self, context):
309
        site = getSite()
310
        items = []
311
        for client in site.clients.objectValues('Client'):
312
            objects = list(client.objectValues('Contact'))
313
            objects.sort(lambda x, y: cmp(x.getFullname().lower(),
314
                                          y.getFullname().lower()))
315
            xitems = [(to_utf8(item.getFullname()), item.getFullname())
316
                      for item in objects]
317
            xitems = [SimpleTerm(i[1], i[1], i[0]) for i in xitems]
318
            items += xitems
319
        return SimpleVocabulary(items)
320
321
322
ClientContactVocabularyFactory = ClientContactVocabulary()
323
324
325
class AnalystVocabulary(UserVocabulary):
326
    def __init__(self):
327
        UserVocabulary.__init__(self, roles=['Analyst', ])
328
329
330
AnalystVocabularyFactory = AnalystVocabulary()
331
332
333
def getTemplates(bikalims_path, restype, filter_by_type=False):
334
    """ Returns an array with the Templates available in the Bika LIMS path
335
        specified plus the templates from the resources directory specified and
336
        available on each additional product (restype).
337
338
        Each array item is a dictionary with the following structure:
339
            {'id': <template_id>,
340
             'title': <template_title>}
341
342
        If the template lives outside the bika.lims add-on, both the template_id
343
        and template_title include a prefix that matches with the add-on
344
        identifier. template_title is the same name as the id, but with
345
        whitespaces and without extension.
346
347
        As an example, for a template from the my.product add-on located in
348
        <restype> resource dir, and with a filename "My_cool_report.pt", the
349
        dictionary will look like:
350
            {'id': 'my.product:My_cool_report.pt',
351
             'title': 'my.product: My cool report'}
352
353
        :param bikalims_path: the path inside bika lims to find the stickers.
354
        :type bikalims_path: an string as a path
355
        :param restype: the resource directory type to search for inside
356
            an addon.
357
        :type restype: string
358
        :param filter_by_type: the folder name to look for inside the
359
        templates path
360
        :type filter_by_type: string/boolean
361
    """
362
    # Retrieve the templates from bika.lims add-on
363
    templates_dir = resource_filename("bika.lims", bikalims_path)
364
    tempath = os.path.join(templates_dir, '*.pt')
365
    templates = [os.path.split(x)[-1] for x in glob.glob(tempath)]
366
367
    # Retrieve the templates from other add-ons
368
    for templates_resource in iterDirectoriesOfType(restype):
369
        prefix = templates_resource.__name__
370
        if prefix == 'bika.lims':
371
            continue
372
        directory = templates_resource.directory
373
        # Only use the directory asked in 'filter_by_type'
374
        if filter_by_type:
375
            directory = directory + '/' + filter_by_type
376
        if os.path.isdir(directory):
377
            dirlist = os.listdir(directory)
378
            exts = ['{0}:{1}'.format(prefix, tpl) for tpl in dirlist if
379
                    tpl.endswith('.pt')]
380
            templates.extend(exts)
381
382
    out = []
383
    templates.sort()
384
    for template in templates:
385
        title = template[:-3]
386
        title = title.replace('_', ' ')
387
        title = title.replace(':', ': ')
388
        out.append({'id': template,
389
                    'title': title})
390
391
    return out
392
393
394
def getStickerTemplates(filter_by_type=False):
395
    """ Returns an array with the sticker templates available. Retrieves the
396
        TAL templates saved in templates/stickers folder.
397
398
        Each array item is a dictionary with the following structure:
399
            {'id': <template_id>,
400
             'title': <template_title>}
401
402
        If the template lives outside the bika.lims add-on, both the template_id
403
        and template_title include a prefix that matches with the add-on
404
        identifier. template_title is the same name as the id, but with
405
        whitespaces and without extension.
406
407
        As an example, for a template from the my.product add-on located in
408
        templates/stickers, and with a filename "EAN128_default_small.pt", the
409
        dictionary will look like:
410
            {'id': 'my.product:EAN128_default_small.pt',
411
             'title': 'my.product: EAN128 default small'}
412
        If filter by type is given in the request, only the templates under
413
        the path with the type name will be rendered given as vocabulary.
414
        Example: If filter_by_type=='worksheet', only *.tp files under a
415
        folder with this name will be displayed.
416
417
        :param filter_by_type:
418
        :type filter_by_type: string/bool.
419
        :returns: an array with the sticker templates available
420
    """
421
    # Retrieve the templates from bika.lims add-on
422
    # resdirname
423
    resdirname = 'stickers'
424
    if filter_by_type:
425
        bikalims_path = os.path.join(
426
            "browser", "templates", resdirname, filter_by_type)
427
    else:
428
        bikalims_path = os.path.join("browser", "templates", resdirname)
429
    # getTemplates needs two parameters, the first one is the bikalims path
430
    # where the stickers will be found. The second one is the resource
431
    # directory type. This allows us to filter stickers by the type we want.
432
    return getTemplates(bikalims_path, resdirname, filter_by_type)
433
434
435
class StickerTemplatesVocabulary(object):
436
    """ Locate all sticker templates
437
    """
438
    implements(IVocabularyFactory)
439
440
    def __call__(self, context, filter_by_type=False):
441
        out = [SimpleTerm(x['id'], x['id'], x['title']) for x in
442
               getStickerTemplates(filter_by_type=filter_by_type)]
443
        return SimpleVocabulary(out)
444