Passed
Push — master ( 640ece...db0f1c )
by Pau
09:26
created

setup_batches_ownership()   A

Complexity

Conditions 4

Size

Total Lines 15
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 15
rs 9.85
c 0
b 0
f 0
cc 4
nop 1
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of SENAITE.HEALTH.
4
#
5
# SENAITE.HEALTH is free software: you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by the Free
7
# Software 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-2019 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
import itertools
22
23
from Acquisition import aq_base
24
from Products.CMFCore import permissions
25
from Products.CMFCore.permissions import AccessContentsInformation
26
from Products.CMFCore.permissions import ListFolderContents
27
from Products.CMFCore.permissions import View
28
from Products.CMFCore.utils import getToolByName
29
30
from bika.health import logger
31
from bika.health.catalog import \
32
    getCatalogDefinitions as getCatalogDefinitionsHealth
33
from bika.health.catalog import getCatalogExtensions
34
from bika.health.config import DEFAULT_PROFILE_ID
35
from bika.health.permissions import ViewPatients
36
from bika.lims import api
37
from bika.lims.catalog import \
38
    getCatalogDefinitions as getCatalogDefinitionsLIMS
39
from bika.lims.catalog import setup_catalogs
40
from bika.lims.idserver import renameAfterCreation
41
from bika.lims.permissions import AddAnalysisRequest
42
from bika.lims.permissions import AddBatch
43
from bika.lims.utils import tmpID
44
45
46
class Empty(object):
47
    pass
48
49
50
ROLES = [
51
    # Tuple of (role, permissions)
52
    # NOTE: here we only expect permissions that belong to other products and
53
    #       plone, cause health-specific permissions for this role are
54
    #       explicitly set in profile/rolemap.xml
55
    ("Doctor", [View, AccessContentsInformation, ListFolderContents]),
56
    ("Client", [AddBatch])
57
]
58
59
GROUPS = [
60
    # Tuple of (group_name, roles_group
61
    ("Doctors", ["Member", "Doctor"], ),
62
]
63
64
ID_FORMATTING = [
65
    {
66
        # P000001, P000002
67
        "portal_type": "Patient",
68
        "form": "P{seq:06d}",
69
        "prefix": "patient",
70
        "sequence_type": "generated",
71
        "counter_type": "",
72
        "split_length": 1,
73
    }, {
74
        # D0001, D0002, D0003
75
        "portal_type": "Doctor",
76
        "form": "D{seq:04d}",
77
        "prefix": "doctor",
78
        "sequence_type": "generated",
79
        "counter_type": "",
80
        "split_length": 1,
81
    }
82
]
83
84
85
def post_install(portal_setup):
86
    """Runs after the last import step of the *default* profile
87
    This handler is registered as a *post_handler* in the generic setup profile
88
    :param portal_setup: SetupTool
89
    """
90
    logger.info("SENAITE Health post-install handler [BEGIN]")
91
    context = portal_setup._getImportContext(DEFAULT_PROFILE_ID)
92
    portal = context.getSite()
93
    # Setup catalogs
94
    # TODO use upgrade.utils.setup_catalogs instead!
95
    setup_health_catalogs(portal)
96
97
    # Setup portal permissions
98
    setup_roles_permissions(portal)
99
100
    # Setup user groups (e.g. Doctors)
101
    setup_user_groups(portal)
102
103
    # Setup site structure
104
    setup_site_structure(context)
105
106
    # Setup javascripts
107
    setup_javascripts(portal)
108
109
    # Setup content actions
110
    setup_content_actions(portal)
111
112
    # Setup ID formatting for Health types
113
    setup_id_formatting(portal)
114
115
    # Setup default ethnicities
116
    setup_ethnicities(portal)
117
118
    # Reindex the top level folder in the portal and setup to fix missing icons
119
    reindex_content_structure(portal)
120
121
    # When installing senaite health together with core, health's skins are not
122
    # set before core's, even if after-before is set in profiles/skins.xml
123
    # Ensure health's skin layer(s) always gets priority over core's
124
    portal_setup.runImportStepFromProfile(DEFAULT_PROFILE_ID, "skins")
125
126
    logger.info("SENAITE Health post-install handler [DONE]")
127
128
129
def setup_user_groups(portal):
130
    logger.info("Setup User Groups ...")
131
    portal_groups = portal.portal_groups
132
    for group_name, roles in GROUPS:
133
        if group_name not in portal_groups.listGroupIds():
134
            portal_groups.addGroup(group_name, title=group_name, roles=roles)
135
            logger.info("Group '{}' with roles '{}' added"
136
                        .format(group_name, ", ".join(roles)))
137
        else:
138
            logger.info("Group '{}' already exist [SKIP]".format(group_name))
139
    logger.info("Setup User Groups [DONE]")
140
141
142
def setup_roles_permissions(portal):
143
    """Setup the top-level permissions for new roles. The new role is added to
144
    the roles that already have the permission granted (acquire=1)
145
    """
146
    logger.info("Setup roles permissions ...")
147
    for role_name, permissions in ROLES:
148
        for permission in permissions:
149
            add_permission_for_role(portal, permission, role_name)
150
151
    # Add "Add AnalysisRequest" permission for Clients in base analysisrequests
152
    # This makes the "Add" button to appear in AnalysisRequestsFolder view
153
    analysis_requests = portal.analysisrequests
154
    add_permission_for_role(analysis_requests, AddAnalysisRequest, "Client")
155
    logger.info("Setup roles permissions [DONE]")
156
157
158
def add_permission_for_role(folder, permission, role):
159
    """Grants a permission to the given role and given folder
160
    :param folder: the folder to which the permission for the role must apply
161
    :param permission: the permission to be assigned
162
    :param role: role to which the permission must be granted
163
    :return True if succeed, otherwise, False
164
    """
165
    roles = filter(lambda perm: perm.get('selected') == 'SELECTED',
166
                   folder.rolesOfPermission(permission))
167
    roles = map(lambda perm_role: perm_role['name'], roles)
168
    if role in roles:
169
        # Nothing to do, the role has the permission granted already
170
        logger.info(
171
            "Role '{}' has permission {} for {} already".format(role,
172
                                                                repr(permission),
173
                                                                repr(folder)))
174
        return False
175
    roles.append(role)
176
    acquire = folder.acquiredRolesAreUsedBy(permission) == 'CHECKED' and 1 or 0
177
    folder.manage_permission(permission, roles=roles, acquire=acquire)
178
    folder.reindexObject()
179
    logger.info(
180
        "Added permission {} to role '{}' for {}".format(repr(permission), role,
181
                                                         repr(folder)))
182
    return True
183
184
185
def setup_ethnicities(portal):
186
    """
187
    Creates standard ethnicities
188
    """
189
    logger.info("Setup default ethnicities ...")
190
    ethnicities = ['Native American', 'Asian', 'Black',
191
                   'Native Hawaiian or Other Pacific Islander', 'White',
192
                   'Hispanic or Latino']
193
    folder = portal.bika_setup.bika_ethnicities
194
    for ethnicityName in ethnicities:
195
        _id = folder.invokeFactory('Ethnicity', id=tmpID())
196
        obj = folder[_id]
197
        obj.edit(title=ethnicityName,
198
                 description='')
199
        obj.unmarkCreationFlag()
200
        renameAfterCreation(obj)
201
    logger.info("Setup default ethnicities [DONE]")
202
203
204
def setup_site_structure(context):
205
    """ Setup contents structure for health
206
    """
207
    if context.readDataFile('bika.health.txt') is None:
208
        return
209
    portal = context.getSite()
210
    logger.info("Setup site structure ...")
211
212
    # index objects - importing through GenericSetup doesn't
213
    for obj_id in ('doctors',
214
                   'patients', ):
215
        obj = portal._getOb(obj_id)
216
        obj.unmarkCreationFlag()
217
        obj.reindexObject()
218
219
    # same for objects in bika_setup
220
    bika_setup = portal.bika_setup
221
    for obj_id in ('bika_aetiologicagents',
222
                   'bika_analysiscategories',
223
                   'bika_drugs',
224
                   'bika_drugprohibitions',
225
                   'bika_diseases',
226
                   'bika_treatments',
227
                   'bika_immunizations',
228
                   'bika_vaccinationcenters',
229
                   'bika_casestatuses',
230
                   'bika_caseoutcomes',
231
                   'bika_identifiertypes',
232
                   'bika_casesyndromicclassifications',
233
                   'bika_insurancecompanies',
234
                   'bika_ethnicities',):
235
        obj = bika_setup._getOb(obj_id)
236
        obj.unmarkCreationFlag()
237
        obj.reindexObject()
238
239
    logger.info("Setup site structure [DONE]")
240
241
242
def setup_javascripts(portal):
243
    # Plone's jQuery gets clobbered when jsregistry is loaded.
244
    setup = portal.portal_setup
245
    setup.runImportStepFromProfile('profile-plone.app.jquery:default',
246
                                   'jsregistry')
247
    setup.runImportStepFromProfile('profile-plone.app.jquerytools:default',
248
                                   'jsregistry')
249
250
    # Load bika.lims js always before bika.health ones.
251
    setup.runImportStepFromProfile('profile-bika.lims:default', 'jsregistry')
252
253
254
def setup_content_actions(portal):
255
    """Add "patients" and "doctors" action views inside Client view
256
    """
257
    logger.info("Setup content actions ...")
258
    client_type = portal.portal_types.getTypeInfo("Client")
259
260
    remove_action(client_type, "patients")
261
    client_type.addAction(
262
        id="patients",
263
        name="Patients",
264
        action="string:${object_url}/patients",
265
        permission=ViewPatients,
266
        category="object",
267
        visible=True,
268
        icon_expr="string:${portal_url}/images/patient.png",
269
        link_target="",
270
        description="",
271
        condition="")
272
273
    remove_action(client_type, "doctors")
274
    client_type.addAction(
275
        id="doctors",
276
        name="Doctors",
277
        action="string:${object_url}/doctors",
278
        permission=permissions.View,
279
        category="object",
280
        visible=True,
281
        icon_expr="string:${portal_url}/images/doctor.png",
282
        link_target="",
283
        description="",
284
        condition="")
285
    logger.info("Setup content actions [DONE]")
286
287
288
def remove_action(type_info, action_id):
289
    """Removes the action id from the type passed in
290
    """
291
    actions = map(lambda action: action.id, type_info._actions)
292
    if action_id not in actions:
293
        return True
294
    index = actions.index(action_id)
295
    type_info.deleteActions([index])
296
    return remove_action(type_info, action_id)
297
298
299
def setup_health_catalogs(portal):
300
    # an item should belong to only one catalog.
301
    # that way looking it up means first looking up *the* catalog
302
    # in which it is indexed, as well as making it cheaper to index.
303
    def addIndex(cat, *args):
304
        try:
305
            cat.addIndex(*args)
306
        except:
307
            pass
308
309
    def addColumn(cat, col):
310
        try:
311
            cat.addColumn(col)
312
        except:
313
            pass
314
315
    logger.info("Setup catalogs ...")
316
317
    # bika_catalog
318
    bc = getToolByName(portal, 'bika_catalog', None)
319
    if not bc:
320
        logger.warning('Could not find the bika_catalog tool.')
321
        return
322
    addIndex(bc, 'getClientTitle', 'FieldIndex')
323
    addIndex(bc, 'getPatientID', 'FieldIndex')
324
    addIndex(bc, 'getPatientUID', 'FieldIndex')
325
    addIndex(bc, 'getPatientTitle', 'FieldIndex')
326
    addIndex(bc, 'getDoctorID', 'FieldIndex')
327
    addIndex(bc, 'getDoctorUID', 'FieldIndex')
328
    addIndex(bc, 'getDoctorTitle', 'FieldIndex')
329
    addIndex(bc, 'getClientPatientID', 'FieldIndex')
330
    addColumn(bc, 'getClientTitle')
331
    addColumn(bc, 'getPatientID')
332
    addColumn(bc, 'getPatientUID')
333
    addColumn(bc, 'getPatientTitle')
334
    addColumn(bc, 'getDoctorID')
335
    addColumn(bc, 'getDoctorUID')
336
    addColumn(bc, 'getDoctorTitle')
337
    addColumn(bc, 'getClientPatientID')
338
339
    # portal_catalog
340
    pc = getToolByName(portal, 'portal_catalog', None)
341
    if not pc:
342
        logger.warning('Could not find the portal_catalog tool.')
343
        return
344
    addIndex(pc, 'getDoctorID', 'FieldIndex')
345
    addIndex(pc, 'getDoctorUID', 'FieldIndex')
346
    addIndex(pc, 'getPrimaryReferrerUID', 'FieldIndex')
347
    addColumn(pc, 'getDoctorID')
348
    addColumn(pc, 'getDoctorUID')
349
350
    # bika_setup_catalog
351
    bsc = getToolByName(portal, 'bika_setup_catalog', None)
352
    if not bsc:
353
        logger.warning('Could not find the bika_setup_catalog tool.')
354
        return
355
    at = getToolByName(portal, 'archetype_tool')
356
    at.setCatalogsByType('Disease', ['bika_setup_catalog', ])
357
    at.setCatalogsByType('AetiologicAgent', ['bika_setup_catalog', ])
358
    at.setCatalogsByType('Treatment', ['bika_setup_catalog'])
359
    at.setCatalogsByType('Symptom', ['bika_setup_catalog'])
360
    at.setCatalogsByType('Drug', ['bika_setup_catalog'])
361
    at.setCatalogsByType('DrugProhibition', ['bika_setup_catalog'])
362
    at.setCatalogsByType('VaccinationCenter', ['bika_setup_catalog', ])
363
    at.setCatalogsByType('InsuranceCompany', ['bika_setup_catalog', ])
364
    at.setCatalogsByType('Immunization', ['bika_setup_catalog', ])
365
    at.setCatalogsByType('CaseStatus', ['bika_setup_catalog', ])
366
    at.setCatalogsByType('CaseOutcome', ['bika_setup_catalog', ])
367
    at.setCatalogsByType('IdentifierType', ['bika_setup_catalog', ])
368
    at.setCatalogsByType('CaseSyndromicClassification', ['bika_setup_catalog'])
369
    at.setCatalogsByType('Ethnicity', ['bika_setup_catalog', ])
370
371
    addIndex(bsc, 'getGender', 'FieldIndex')
372
    addColumn(bsc, 'getGender')
373
374
    catalog_definitions_lims_health = getCatalogDefinitionsLIMS()
375
    catalog_definitions_lims_health.update(getCatalogDefinitionsHealth())
376
    # Updating health catalogs if there is any change in them
377
    setup_catalogs(
378
        portal, catalog_definitions_lims_health,
379
        catalogs_extension=getCatalogExtensions())
380
381
    logger.info("Setup catalogs [DONE]")
382
383
384
def setup_id_formatting(portal, format=None):
385
    """Setup default ID Formatting for health content types
386
    """
387
    if not format:
388
        logger.info("Setup ID formatting ...")
389
        for formatting in ID_FORMATTING:
390
            setup_id_formatting(portal, format=formatting)
391
        logger.info("Setup ID formatting [DONE]")
392
        return
393
394
    bs = portal.bika_setup
395
    p_type = format.get("portal_type", None)
396
    if not p_type:
397
        return
398
    id_map = bs.getIDFormatting()
399
    id_format = filter(lambda idf: idf.get("portal_type", "") == p_type, id_map)
0 ignored issues
show
introduced by
The variable p_type does not seem to be defined for all execution paths.
Loading history...
400
    if id_format:
401
        logger.info("ID Format for {} already set: '{}' [SKIP]"
402
                    .format(p_type, id_format[0]["form"]))
403
        return
404
405
    form = format.get("form", "")
406
    if not form:
407
        logger.info("Param 'form' for portal type {} not set [SKIP")
408
        return
409
410
    logger.info("Applying format '{}' for {}".format(form, p_type))
411
    ids = list()
412
    for record in id_map:
413
        if record.get('portal_type', '') == p_type:
414
            continue
415
        ids.append(record)
416
    ids.append(format)
417
    bs.setIDFormatting(ids)
418
419
420
def reindex_content_structure(portal):
421
    """Reindex contents generated by Generic Setup
422
    """
423
    logger.info("*** Reindex content structure ***")
424
425
    def reindex(obj, recurse=False):
426
        # skip catalog tools etc.
427
        if api.is_object(obj):
428
            obj.reindexObject()
429
        if recurse and hasattr(aq_base(obj), "objectValues"):
430
            map(reindex, obj.objectValues())
431
432
    setup = api.get_setup()
433
    setupitems = setup.objectValues()
434
    rootitems = portal.objectValues()
435
436
    for obj in itertools.chain(setupitems, rootitems):
437
        if not api.is_object(obj):
438
            continue
439
        logger.info("Reindexing {}".format(repr(obj)))
440
        reindex(obj)
441