Completed
Push — master ( 65c723...b6585a )
by Jordi
76:39 queued 72:29
created

add_dexterity_setup_items()   A

Complexity

Conditions 3

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 17
dl 0
loc 31
rs 9.55
c 0
b 0
f 0
cc 3
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-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 bika.lims import api
25
from bika.lims import logger
26
from bika.lims.catalog import auditlog_catalog
27
from bika.lims.catalog import getCatalogDefinitions
28
from bika.lims.catalog import setup_catalogs
29
from bika.lims.catalog.catalog_utilities import addZCTextIndex
30
from plone import api as ploneapi
31
32
PROFILE_ID = "profile-bika.lims:default"
33
34
GROUPS = [
35
    {
36
        "id": "Analysts",
37
        "title": "Analysts",
38
        "roles": ["Analyst"],
39
    }, {
40
        "id": "Clients",
41
        "title": "Clients",
42
        "roles": ["Client"],
43
    }, {
44
        "id": "LabClerks",
45
        "title": "Lab Clerks",
46
        "roles": ["LabClerk"],
47
    }, {
48
        "id": "LabManagers",
49
        "title": "Lab Managers",
50
        "roles": ["LabManager"],
51
    }, {
52
        "id": "Preservers",
53
        "title": "Preservers",
54
        "roles": ["Preserver"],
55
    }, {
56
        "id": "Publishers",
57
        "title": "Publishers",
58
        "roles": ["Publisher"],
59
    }, {
60
        "id": "Verifiers",
61
        "title": "Verifiers",
62
        "roles": ["Verifier"],
63
    }, {
64
        "id": "Samplers",
65
        "title": "Samplers",
66
        "roles": ["Sampler"],
67
    }, {
68
        "id": "RegulatoryInspectors",
69
        "title": "Regulatory Inspectors",
70
        "roles": ["RegulatoryInspector"],
71
    }, {
72
        "id": "SamplingCoordinators",
73
        "title": "Sampling Coordinator",
74
        "roles": ["SamplingCoordinator"],
75
    }
76
]
77
78
NAV_BAR_ITEMS_TO_HIDE = (
79
    # List of items to hide from navigation bar
80
    "arimports",
81
    "pricelists",
82
    "supplyorders",
83
)
84
85
86
CONTENTS_TO_DELETE = (
87
    # List of items to delete
88
    "Members",
89
    "news",
90
    "events",
91
)
92
93
CATALOG_MAPPINGS = (
94
    # portal_type, catalog_ids
95
    ("ARTemplate", ["bika_setup_catalog", "portal_catalog"]),
96
    ("AnalysisCategory", ["bika_setup_catalog"]),
97
    ("AnalysisProfile", ["bika_setup_catalog", "portal_catalog"]),
98
    ("AnalysisService", ["bika_setup_catalog", "portal_catalog"]),
99
    ("AnalysisSpec", ["bika_setup_catalog"]),
100
    ("Attachment", ["portal_catalog"]),
101
    ("AttachmentType", ["bika_setup_catalog"]),
102
    ("Batch", ["bika_catalog", "portal_catalog"]),
103
    ("BatchLabel", ["bika_setup_catalog"]),
104
    ("Calculation", ["bika_setup_catalog", "portal_catalog"]),
105
    ("Container", ["bika_setup_catalog"]),
106
    ("ContainerType", ["bika_setup_catalog"]),
107
    ("Department", ["bika_setup_catalog", "portal_catalog"]),
108
    ("Instrument", ["bika_setup_catalog", "portal_catalog"]),
109
    ("InstrumentLocation", ["bika_setup_catalog", "portal_catalog"]),
110
    ("InstrumentType", ["bika_setup_catalog", "portal_catalog"]),
111
    ("LabContact", ["bika_setup_catalog", "portal_catalog"]),
112
    ("LabProduct", ["bika_setup_catalog", "portal_catalog"]),
113
    ("Manufacturer", ["bika_setup_catalog", "portal_catalog"]),
114
    ("Method", ["bika_setup_catalog", "portal_catalog"]),
115
    ("Multifile", ["bika_setup_catalog"]),
116
    ("Preservation", ["bika_setup_catalog"]),
117
    ("ReferenceDefinition", ["bika_setup_catalog", "portal_catalog"]),
118
    ("ReferenceSample", ["bika_catalog", "portal_catalog"]),
119
    ("SRTemplate", ["bika_setup_catalog", "portal_catalog"]),
120
    ("SampleCondition", ["bika_setup_catalog"]),
121
    ("SampleMatrix", ["bika_setup_catalog"]),
122
    ("SamplePoint", ["bika_setup_catalog", "portal_catalog"]),
123
    ("SampleType", ["bika_setup_catalog", "portal_catalog"]),
124
    ("SamplingDeviation", ["bika_setup_catalog"]),
125
    ("StorageLocation", ["bika_setup_catalog", "portal_catalog"]),
126
    ("SubGroup", ["bika_setup_catalog"]),
127
    ("Supplier", ["bika_setup_catalog", "portal_catalog"]),
128
    ("WorksheetTemplate", ["bika_setup_catalog", "portal_catalog"]),
129
)
130
131
INDEXES = (
132
    # catalog, id, indexed attribute, type
133
    ("bika_catalog", "BatchDate", "", "DateIndex"),
134
    ("bika_catalog", "Creator", "", "FieldIndex"),
135
    ("bika_catalog", "Description", "", "ZCTextIndex"),
136
    ("bika_catalog", "Title", "", "ZCTextIndex"),
137
    ("bika_catalog", "Type", "", "FieldIndex"),
138
    ("bika_catalog", "UID", "", "FieldIndex"),
139
    ("bika_catalog", "allowedRolesAndUsers", "", "KeywordIndex"),
140
    ("bika_catalog", "created", "", "DateIndex"),
141
    ("bika_catalog", "getBlank", "", "BooleanIndex"),
142
    ("bika_catalog", "getClientBatchID", "", "FieldIndex"),
143
    ("bika_catalog", "getClientID", "", "FieldIndex"),
144
    ("bika_catalog", "getClientTitle", "", "FieldIndex"),
145
    ("bika_catalog", "getClientUID", "", "FieldIndex"),
146
    ("bika_catalog", "getDateReceived", "", "DateIndex"),
147
    ("bika_catalog", "getDateSampled", "", "DateIndex"),
148
    ("bika_catalog", "getDueDate", "", "DateIndex"),
149
    ("bika_catalog", "getExpiryDate", "", "DateIndex"),
150
    ("bika_catalog", "getId", "", "FieldIndex"),
151
    ("bika_catalog", "getReferenceDefinitionUID", "", "FieldIndex"),
152
    ("bika_catalog", "getSupportedServices", "", "KeywordIndex"),
153
    ("bika_catalog", "id", "getId", "FieldIndex"),
154
    ("bika_catalog", "isValid", "", "BooleanIndex"),
155
    ("bika_catalog", "is_active", "", "BooleanIndex"),
156
    ("bika_catalog", "path", "getPhysicalPath", "ExtendedPathIndex"),
157
    ("bika_catalog", "portal_type", "", "FieldIndex"),
158
    ("bika_catalog", "review_state", "", "FieldIndex"),
159
    ("bika_catalog", "sortable_title", "", "FieldIndex"),
160
    ("bika_catalog", "title", "", "FieldIndex"),
161
    ("bika_catalog", "listing_searchable_text", "", "TextIndexNG3"),
162
163
    ("bika_setup_catalog", "Creator", "", "FieldIndex"),
164
    ("bika_setup_catalog", "Description", "", "ZCTextIndex"),
165
    ("bika_setup_catalog", "Title", "", "ZCTextIndex"),
166
    ("bika_setup_catalog", "Type", "", "FieldIndex"),
167
    ("bika_setup_catalog", "UID", "", "FieldIndex"),
168
    ("bika_setup_catalog", "allowedRolesAndUsers", "", "KeywordIndex"),
169
    ("bika_setup_catalog", "category_uid", "", "KeywordIndex"),
170
    ("bika_setup_catalog", "created", "", "DateIndex"),
171
    ("bika_setup_catalog", "department_title", "", "KeywordIndex"),
172
    ("bika_setup_catalog", "department_uid", "", "KeywordIndex"),
173
    ("bika_setup_catalog", "getClientUID", "", "FieldIndex"),
174
    ("bika_setup_catalog", "getId", "", "FieldIndex"),
175
    ("bika_setup_catalog", "getKeyword", "", "FieldIndex"),
176
    ("bika_setup_catalog", "id", "getId", "FieldIndex"),
177
    ("bika_setup_catalog", "instrument_title", "", "KeywordIndex"),
178
    ("bika_setup_catalog", "instrumenttype_title", "", "KeywordIndex"),
179
    ("bika_setup_catalog", "is_active", "", "BooleanIndex"),
180
    ("bika_setup_catalog", "listing_searchable_text", "", "TextIndexNG3"),
181
    ("bika_setup_catalog", "method_available_uid", "", "KeywordIndex"),
182
    ("bika_setup_catalog", "path", "getPhysicalPath", "ExtendedPathIndex"),
183
    ("bika_setup_catalog", "point_of_capture", "", "FieldIndex"),
184
    ("bika_setup_catalog", "portal_type", "", "FieldIndex"),
185
    ("bika_setup_catalog", "price", "", "FieldIndex"),
186
    ("bika_setup_catalog", "price_total", "", "FieldIndex"),
187
    ("bika_setup_catalog", "review_state", "", "FieldIndex"),
188
    ("bika_setup_catalog", "sampletype_title", "", "KeywordIndex"),
189
    ("bika_setup_catalog", "sampletype_uid", "", "KeywordIndex"),
190
    ("bika_setup_catalog", "sortable_title", "", "FieldIndex"),
191
    ("bika_setup_catalog", "title", "", "FieldIndex"),
192
193
    ("portal_catalog", "Analyst", "", "FieldIndex"),
194
)
195
196
COLUMNS = (
197
    # catalog, column name
198
    ("bika_catalog", "path"),
199
    ("bika_catalog", "UID"),
200
    ("bika_catalog", "id"),
201
    ("bika_catalog", "getId"),
202
    ("bika_catalog", "Type"),
203
    ("bika_catalog", "portal_type"),
204
    ("bika_catalog", "creator"),
205
    ("bika_catalog", "Created"),
206
    ("bika_catalog", "Title"),
207
    ("bika_catalog", "Description"),
208
    ("bika_catalog", "sortable_title"),
209
    ("bika_catalog", "getClientTitle"),
210
    ("bika_catalog", "getClientID"),
211
    ("bika_catalog", "getClientBatchID"),
212
    ("bika_catalog", "getDateReceived"),
213
    ("bika_catalog", "getDateSampled"),
214
    ("bika_catalog", "review_state"),
215
216
    ("bika_setup_catalog", "path"),
217
    ("bika_setup_catalog", "UID"),
218
    ("bika_setup_catalog", "id"),
219
    ("bika_setup_catalog", "getId"),
220
    ("bika_setup_catalog", "Type"),
221
    ("bika_setup_catalog", "portal_type"),
222
    ("bika_setup_catalog", "Title"),
223
    ("bika_setup_catalog", "Description"),
224
    ("bika_setup_catalog", "title"),
225
    ("bika_setup_catalog", "sortable_title"),
226
    ("bika_setup_catalog", "description"),
227
    ("bika_setup_catalog", "review_state"),
228
    ("bika_setup_catalog", "getCategoryTitle"),
229
    ("bika_setup_catalog", "getCategoryUID"),
230
    ("bika_setup_catalog", "getClientUID"),
231
    ("bika_setup_catalog", "getKeyword"),
232
233
    ("portal_catalog", "Analyst"),
234
)
235
236
237
def pre_install(portal_setup):
238
    """Runs before the first import step of the *default* profile
239
240
    This handler is registered as a *pre_handler* in the generic setup profile
241
242
    :param portal_setup: SetupTool
243
    """
244
    logger.info("SENAITE PRE-INSTALL handler [BEGIN]")
245
246
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
247
    profile_id = PROFILE_ID
248
249
    context = portal_setup._getImportContext(profile_id)
250
    portal = context.getSite()  # noqa
251
252
    logger.info("SENAITE PRE-INSTALL handler [DONE]")
253
254
255
def post_install(portal_setup):
256
    """Runs after the last import step of the *default* profile
257
258
    This handler is registered as a *post_handler* in the generic setup profile
259
260
    :param portal_setup: SetupTool
261
    """
262
    logger.info("SENAITE POST-INSTALL handler [BEGIN]")
263
264
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
265
    profile_id = PROFILE_ID
266
267
    context = portal_setup._getImportContext(profile_id)
268
    portal = context.getSite()  # noqa
269
270
    logger.info("SENAITE POST-INSTALL handler [DONE]")
271
272
273
def setup_handler(context):
274
    """SENAITE setup handler
275
    """
276
277
    if context.readDataFile("bika.lims_various.txt") is None:
278
        return
279
280
    logger.info("SENAITE setup handler [BEGIN]")
281
282
    portal = context.getSite()
283
284
    # Run Installers
285
    remove_default_content(portal)
286
    hide_navbar_items(portal)
287
    reindex_content_structure(portal)
288
    setup_groups(portal)
289
    setup_catalog_mappings(portal)
290
    setup_core_catalogs(portal)
291
    add_dexterity_setup_items(portal)
292
293
    # Setting up all LIMS catalogs defined in catalog folder
294
    setup_catalogs(portal, getCatalogDefinitions())
295
296
    # Run after all catalogs have been setup
297
    setup_auditlog_catalog(portal)
298
299
    # Set CMF Form actions
300
    setup_form_controller_actions(portal)
301
302
    logger.info("SENAITE setup handler [DONE]")
303
304
305
def remove_default_content(portal):
306
    """Remove default Plone contents
307
    """
308
    logger.info("*** Delete Default Content ***")
309
310
    # Get the list of object ids for portal
311
    object_ids = portal.objectIds()
312
    delete_ids = filter(lambda id: id in object_ids, CONTENTS_TO_DELETE)
313
    portal.manage_delObjects(ids=delete_ids)
314
315
316
def hide_navbar_items(portal):
317
    """Hide root items in navigation
318
    """
319
    logger.info("*** Hide Navigation Items ***")
320
321
    # Get the list of object ids for portal
322
    object_ids = portal.objectIds()
323
    object_ids = filter(lambda id: id in object_ids, NAV_BAR_ITEMS_TO_HIDE)
324
    for object_id in object_ids:
325
        item = portal[object_id]
326
        item.setExcludeFromNav(True)
327
        item.reindexObject()
328
329
330
def reindex_content_structure(portal):
331
    """Reindex contents generated by Generic Setup
332
    """
333
    logger.info("*** Reindex content structure ***")
334
335
    def reindex(obj, recurse=False):
336
        # skip catalog tools etc.
337
        if api.is_object(obj):
338
            obj.reindexObject()
339
        if recurse and hasattr(aq_base(obj), "objectValues"):
340
            map(reindex, obj.objectValues())
341
342
    setup = api.get_setup()
343
    setupitems = setup.objectValues()
344
    rootitems = portal.objectValues()
345
346
    for obj in itertools.chain(setupitems, rootitems):
347
        logger.info("Reindexing {}".format(repr(obj)))
348
        reindex(obj)
349
350
351
def setup_groups(portal):
352
    """Setup roles and groups
353
    """
354
    logger.info("*** Setup Roles and Groups ***")
355
356
    portal_groups = api.get_tool("portal_groups")
357
358
    for gdata in GROUPS:
359
        group_id = gdata["id"]
360
        # create the group and grant the roles
361
        if group_id not in portal_groups.listGroupIds():
362
            logger.info("+++ Adding group {title} ({id})".format(**gdata))
363
            portal_groups.addGroup(group_id,
364
                                   title=gdata["title"],
365
                                   roles=gdata["roles"])
366
        # grant the roles to the existing group
367
        else:
368
            ploneapi.group.grant_roles(
369
                groupname=gdata["id"],
370
                roles=gdata["roles"],)
371
            logger.info("+++ Granted group {title} ({id}) the roles {roles}"
372
                        .format(**gdata))
373
374
375
def setup_catalog_mappings(portal):
376
    """Setup portal_type -> catalog mappings
377
    """
378
    logger.info("*** Setup Catalog Mappings ***")
379
380
    at = api.get_tool("archetype_tool")
381
    for portal_type, catalogs in CATALOG_MAPPINGS:
382
        at.setCatalogsByType(portal_type, catalogs)
383
384
385
def setup_core_catalogs(portal):
386
    """Setup core catalogs
387
    """
388
    logger.info("*** Setup Core Catalogs ***")
389
390
    to_reindex = []
391
    for catalog, name, attribute, meta_type in INDEXES:
392
        c = api.get_tool(catalog)
393
        indexes = c.indexes()
394
        if name in indexes:
395
            logger.info("*** Index '%s' already in Catalog [SKIP]" % name)
396
            continue
397
398
        logger.info("*** Adding Index '%s' for field '%s' to catalog ..."
399
                    % (meta_type, name))
400
401
        # do we still need ZCTextIndexes?
402
        if meta_type == "ZCTextIndex":
403
            addZCTextIndex(c, name)
404
        else:
405
            c.addIndex(name, meta_type)
406
407
        # get the new created index
408
        index = c._catalog.getIndex(name)
409
        # set the indexed attributes
410
        if hasattr(index, "indexed_attrs"):
411
            index.indexed_attrs = [attribute or name]
412
413
        to_reindex.append((c, name))
414
        logger.info("*** Added Index '%s' for field '%s' to catalog [DONE]"
415
                    % (meta_type, name))
416
417
    # catalog columns
418
    for catalog, name in COLUMNS:
419
        c = api.get_tool(catalog)
420
        if name not in c.schema():
421
            logger.info("*** Adding Column '%s' to catalog '%s' ..."
422
                        % (name, catalog))
423
            c.addColumn(name)
424
            logger.info("*** Added Column '%s' to catalog '%s' [DONE]"
425
                        % (name, catalog))
426
        else:
427
            logger.info("*** Column '%s' already in catalog '%s'  [SKIP]"
428
                        % (name, catalog))
429
            continue
430
431
    for catalog, name in to_reindex:
432
        logger.info("*** Indexing new index '%s' ..." % name)
433
        catalog.manage_reindexIndex(name)
434
        logger.info("*** Indexing new index '%s' [DONE]" % name)
435
436
437
def setup_auditlog_catalog(portal):
438
    """Setup auditlog catalog
439
    """
440
    logger.info("*** Setup Audit Log Catalog ***")
441
442
    catalog_id = auditlog_catalog.CATALOG_AUDITLOG
443
    catalog = api.get_tool(catalog_id)
444
445
    for name, meta_type in auditlog_catalog._indexes.iteritems():
446
        indexes = catalog.indexes()
447
        if name in indexes:
448
            logger.info("*** Index '%s' already in Catalog [SKIP]" % name)
449
            continue
450
451
        logger.info("*** Adding Index '%s' for field '%s' to catalog ..."
452
                    % (meta_type, name))
453
454
        catalog.addIndex(name, meta_type)
455
456
        # Setup TextIndexNG3 for listings
457
        # XXX is there another way to do this?
458
        if meta_type == "TextIndexNG3":
459
            index = catalog._catalog.getIndex(name)
460
            index.index.default_encoding = "utf-8"
461
            index.index.query_parser = "txng.parsers.en"
462
            index.index.autoexpand = "always"
463
            index.index.autoexpand_limit = 3
464
465
        logger.info("*** Added Index '%s' for field '%s' to catalog [DONE]"
466
                    % (meta_type, name))
467
468
    # Attach the catalog to all known portal types
469
    at = api.get_tool("archetype_tool")
470
    pt = api.get_tool("portal_types")
471
472
    for portal_type in pt.listContentTypes():
473
        catalogs = at.getCatalogsByType(portal_type)
474
        if catalog not in catalogs:
475
            new_catalogs = map(lambda c: c.getId(), catalogs) + [catalog_id]
476
            at.setCatalogsByType(portal_type, new_catalogs)
477
            logger.info("*** Adding catalog '{}' for '{}'".format(
478
                catalog_id, portal_type))
479
480
481
def setup_form_controller_actions(portal):
482
    """Setup custom CMF Form actions
483
    """
484
    logger.info("*** Setup Form Controller custom actions ***")
485
    fc_tool = api.get_tool("portal_form_controller")
486
487
    # Redirect the user to Worksheets listing view after the "remove" action
488
    # from inside Worksheet context is pressed
489
    # https://github.com/senaite/senaite.core/pull/1480
490
    fc_tool.addFormAction(
491
        object_id="content_status_modify",
492
        status="success",
493
        context_type="Worksheet",
494
        button=None,
495
        action_type="redirect_to",
496
        action_arg="python:object.aq_inner.aq_parent.absolute_url()")
497
498
499
def add_dexterity_setup_items(portal):
500
    """Adds the Dexterity Container in the Setup Folder
501
502
    N.B.: We do this in code, because adding this as Generic Setup Profile in
503
          `profiles/default/structure` flushes the contents on every import.
504
    """
505
    setup = api.get_setup()
506
    pt = api.get_tool("portal_types")
507
    ti = pt.getTypeInfo(setup)
508
509
    # Disable content type filtering
510
    ti.filter_content_types = False
511
512
    # Tuples of ID, Title, FTI
513
    items = [
514
        ("dynamic_analysisspecs",  # ID
515
         "Dynamic Analysis Specifications",  # Title
516
         "DynamicAnalysisSpecs"),  # FTI
517
    ]
518
519
    for id, title, fti in items:
520
        obj = setup.get(id)
521
        if setup.get(id) is None:
522
            logger.info("Adding Setup Item for '{}'".format(id))
523
            setup.invokeFactory(fti, id, title=title)
524
        else:
525
            obj.setTitle(title)
526
            obj.reindexObject()
527
528
    # Enable content type filtering
529
    ti.filter_content_types = True
530