Passed
Push — 2.x ( 11b396...29276a )
by Ramon
05:23
created

senaite.core.setuphandlers   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 607
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 64
eloc 381
dl 0
loc 607
rs 3.28
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A HiddenProfiles.getNonInstallableProfiles() 0 22 1

20 Functions

Rating   Name   Duplication   Size   Complexity  
A setup_form_controller_more_action() 0 15 1
A post_install() 0 18 1
A add_catalog_index() 0 12 2
A install() 0 47 4
C setup_catalogs_order() 0 35 10
B setup_other_catalogs() 0 28 7
A add_catalog_column() 0 10 2
A setup_content_structure() 0 6 1
A setup_auditlog_catalog_mappings() 0 23 5
A add_dexterity_items() 0 13 4
A add_senaite_setup() 0 14 1
A add_dexterity_portal_items() 0 19 1
A setup_catalog_mappings() 0 12 3
A remove_default_content() 0 10 3
A setup_markup_schema() 0 10 2
A reindex_catalog_index() 0 7 1
A add_dexterity_setup_items() 0 22 1
D setup_core_catalogs() 0 57 12
A _run_import_step() 0 7 1
A pre_install() 0 8 1

How to fix   Complexity   

Complexity

Complex classes like senaite.core.setuphandlers often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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-2021 by it's authors.
19
# Some rights reserved, see README and LICENSE.
20
21
from Acquisition import aq_base
22
from bika.lims import api
23
from bika.lims.setuphandlers import reindex_content_structure
24
from bika.lims.setuphandlers import setup_form_controller_actions
25
from bika.lims.setuphandlers import setup_groups
26
from plone.dexterity.fti import DexterityFTI
27
from plone.registry.interfaces import IRegistry
28
from Products.CMFPlone.utils import get_installer
29
from Products.GenericSetup.utils import _resolveDottedName
30
from senaite.core import logger
31
from senaite.core.api.catalog import add_column
32
from senaite.core.api.catalog import add_index
33
from senaite.core.api.catalog import get_columns
34
from senaite.core.api.catalog import get_indexes
35
from senaite.core.api.catalog import reindex_index
36
from senaite.core.catalog import ANALYSIS_CATALOG
37
from senaite.core.catalog import AUDITLOG_CATALOG
38
from senaite.core.catalog import AUTOIMPORTLOG_CATALOG
39
from senaite.core.catalog import REPORT_CATALOG
40
from senaite.core.catalog import SAMPLE_CATALOG
41
from senaite.core.catalog import SENAITE_CATALOG
42
from senaite.core.catalog import SETUP_CATALOG
43
from senaite.core.catalog import WORKSHEET_CATALOG
44
from senaite.core.catalog import AnalysisCatalog
45
from senaite.core.catalog import AuditlogCatalog
46
from senaite.core.catalog import AutoImportLogCatalog
47
from senaite.core.catalog import ReportCatalog
48
from senaite.core.catalog import SampleCatalog
49
from senaite.core.catalog import SenaiteCatalog
50
from senaite.core.catalog import SetupCatalog
51
from senaite.core.catalog import WorksheetCatalog
52
from senaite.core.config import PROFILE_ID
53
from senaite.core.upgrade.utils import temporary_allow_type
54
from zope.component import getUtility
55
from zope.interface import implementer
56
57
try:
58
    from Products.CMFPlone.interfaces import IMarkupSchema
59
    from Products.CMFPlone.interfaces import INonInstallable
60
except ImportError:
61
    from zope.interface import Interface
62
    IMarkupSchema = None
63
64
    class INonInstallable(Interface):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Interface does not seem to be defined.
Loading history...
65
        pass
66
67
PORTAL_CATALOG = "portal_catalog"
68
69
70
@implementer(INonInstallable)
71
class HiddenProfiles(object):
72
    def getNonInstallableProfiles(self):
73
        """Hide all profiles from site-creation and quickinstaller (not ZMI)"""
74
        return [
75
            # Leave visible to allow upgrade via the Plone Add-on controlpanel
76
            # "bika.lims:default",
77
78
            # hide install profiles that come with Plone
79
            "Products.CMFPlacefulWorkflow:CMFPlacefulWorkflow",
80
            "Products.CMFPlacefulWorkflow:base",
81
            "Products.CMFPlacefulWorkflow:uninstall",
82
            "Products.DataGridField:default",
83
            "Products.DataGridField:example",
84
            "Products.TextIndexNG3:default",
85
            "archetypes.multilingual:default",
86
            "archetypes.referencebrowserwidget:default",
87
            "collective.js.jqueryui:default"
88
            "plone.app.iterate:default",
89
            "plone.app.iterate:plone.app.iterate",
90
            "plone.app.iterate:test",
91
            "plone.app.iterate:uninstall",
92
            "plone.app.jquery:default",
93
            "plonetheme.barceloneta:default",
94
        ]
95
96
97
CONTENTS_TO_DELETE = (
98
    # List of items to delete
99
    "Members",
100
    "news",
101
    "events",
102
)
103
104
CATALOGS = (
105
    AnalysisCatalog,
106
    AuditlogCatalog,
107
    AutoImportLogCatalog,
108
    SampleCatalog,
109
    SenaiteCatalog,
110
    SetupCatalog,
111
    WorksheetCatalog,
112
    ReportCatalog,
113
)
114
115
INDEXES = (
116
    # catalog, id, indexed attribute, type
117
    (PORTAL_CATALOG, "Analyst", "", "FieldIndex"),
118
    (PORTAL_CATALOG, "analysisRequestTemplates", "", "FieldIndex"),
119
    (PORTAL_CATALOG, "getFullname", "", "FieldIndex"),
120
    (PORTAL_CATALOG, "getName", "", "FieldIndex"),
121
    (PORTAL_CATALOG, "getParentUID", "", "FieldIndex"),
122
    (PORTAL_CATALOG, "getUsername", "", "FieldIndex"),
123
    (PORTAL_CATALOG, "is_active", "", "BooleanIndex"),
124
    (PORTAL_CATALOG, "path", "getPhysicalPath", "ExtendedPathIndex"),
125
    (PORTAL_CATALOG, "review_state", "", "FieldIndex"),
126
    (PORTAL_CATALOG, "sample_uid", "", "KeywordIndex"),
127
    (PORTAL_CATALOG, "title", "", "FieldIndex"),
128
)
129
130
COLUMNS = (
131
    # catalog, column name
132
    (PORTAL_CATALOG, "analysisRequestTemplates"),
133
    (PORTAL_CATALOG, "review_state"),
134
    (PORTAL_CATALOG, "getClientID"),
135
    (PORTAL_CATALOG, "Analyst"),
136
)
137
138
CATALOG_MAPPINGS = (
139
    # portal_type, catalog_ids
140
    ("ARReport", [REPORT_CATALOG, PORTAL_CATALOG]),
141
    ("ARTemplate", [SETUP_CATALOG, PORTAL_CATALOG]),
142
    ("Analysis", [ANALYSIS_CATALOG]),
143
    ("AnalysisCategory", [SETUP_CATALOG, PORTAL_CATALOG]),
144
    ("AnalysisProfile", [SETUP_CATALOG, PORTAL_CATALOG]),
145
    ("AnalysisRequest", [SAMPLE_CATALOG]),
146
    ("AnalysisService", [SETUP_CATALOG, PORTAL_CATALOG]),
147
    ("AnalysisSpec", [SETUP_CATALOG, PORTAL_CATALOG]),
148
    ("Attachment", [SENAITE_CATALOG, PORTAL_CATALOG]),
149
    ("AttachmentType", [SETUP_CATALOG, PORTAL_CATALOG]),
150
    ("AutoImportLog", [AUTOIMPORTLOG_CATALOG, PORTAL_CATALOG]),
151
    ("Batch", [SENAITE_CATALOG, PORTAL_CATALOG]),
152
    ("BatchLabel", [SETUP_CATALOG, PORTAL_CATALOG]),
153
    ("Calculation", [SETUP_CATALOG, PORTAL_CATALOG]),
154
    ("Client", [PORTAL_CATALOG]),
155
    ("Contact", [PORTAL_CATALOG]),
156
    ("Container", [SETUP_CATALOG, PORTAL_CATALOG]),
157
    ("ContainerType", [SETUP_CATALOG, PORTAL_CATALOG]),
158
    ("Department", [SETUP_CATALOG, PORTAL_CATALOG]),
159
    ("DuplicateAnalysis", [ANALYSIS_CATALOG, PORTAL_CATALOG]),
160
    ("Instrument", [SETUP_CATALOG, PORTAL_CATALOG]),
161
    ("InstrumentCalibration", [SETUP_CATALOG, PORTAL_CATALOG]),
162
    ("InstrumentCertification", [SETUP_CATALOG, PORTAL_CATALOG]),
163
    ("InstrumentLocation", [SETUP_CATALOG, PORTAL_CATALOG]),
164
    ("InstrumentMaintenanceTask", [SETUP_CATALOG, PORTAL_CATALOG]),
165
    ("InstrumentScheduledTask", [SETUP_CATALOG, PORTAL_CATALOG]),
166
    ("InstrumentType", [SETUP_CATALOG, PORTAL_CATALOG]),
167
    ("InstrumentValidation", [SETUP_CATALOG, PORTAL_CATALOG]),
168
    ("Invoice", [SENAITE_CATALOG, PORTAL_CATALOG]),
169
    ("LabContact", [SETUP_CATALOG, PORTAL_CATALOG]),
170
    ("LabProduct", [SETUP_CATALOG, PORTAL_CATALOG]),
171
    ("Laboratory", [SETUP_CATALOG, PORTAL_CATALOG]),
172
    ("Manufacturer", [SETUP_CATALOG, PORTAL_CATALOG]),
173
    ("Method", [SETUP_CATALOG, PORTAL_CATALOG]),
174
    ("Multifile", [SETUP_CATALOG, PORTAL_CATALOG]),
175
    ("Preservation", [SETUP_CATALOG, PORTAL_CATALOG]),
176
    ("Pricelist", [SETUP_CATALOG, PORTAL_CATALOG]),
177
    ("ReferenceAnalysis", [ANALYSIS_CATALOG, PORTAL_CATALOG]),
178
    ("ReferenceDefinition", [SETUP_CATALOG, PORTAL_CATALOG]),
179
    ("ReferenceSample", [SENAITE_CATALOG, PORTAL_CATALOG]),
180
    ("RejectAnalysis", [ANALYSIS_CATALOG, PORTAL_CATALOG]),
181
    ("Report", [REPORT_CATALOG, PORTAL_CATALOG]),
182
    ("SampleCondition", [SETUP_CATALOG, PORTAL_CATALOG]),
183
    ("SampleMatrix", [SETUP_CATALOG, PORTAL_CATALOG]),
184
    ("SamplePoint", [SETUP_CATALOG, PORTAL_CATALOG]),
185
    ("SampleType", [SETUP_CATALOG, PORTAL_CATALOG]),
186
    ("SamplingDeviation", [SETUP_CATALOG, PORTAL_CATALOG]),
187
    ("StorageLocation", [SETUP_CATALOG, PORTAL_CATALOG]),
188
    ("SubGroup", [SETUP_CATALOG, PORTAL_CATALOG]),
189
    ("Supplier", [SETUP_CATALOG, PORTAL_CATALOG]),
190
    ("SupplierContact", [SETUP_CATALOG, PORTAL_CATALOG]),
191
    ("Worksheet", [WORKSHEET_CATALOG, PORTAL_CATALOG]),
192
    ("WorksheetTemplate", [SETUP_CATALOG, PORTAL_CATALOG]),
193
)
194
195
196
def install(context):
197
    """Install handler
198
    """
199
    if context.readDataFile("senaite.core.txt") is None:
200
        return
201
202
    logger.info("SENAITE CORE install handler [BEGIN]")
203
    portal = context.getSite()
204
205
    # Run required import steps
206
    _run_import_step(portal, "skins")
207
    _run_import_step(portal, "browserlayer")
208
    _run_import_step(portal, "rolemap")
209
    _run_import_step(portal, "typeinfo")
210
    _run_import_step(portal, "factorytool")
211
    _run_import_step(portal, "workflow", "profile-senaite.core:default")
212
    _run_import_step(portal, "typeinfo", "profile-senaite.core:default")
213
214
    # skip installers if already installed
215
    qi = get_installer(portal)
216
    profiles = ["bika.lims", "senaite.core"]
217
    if any(map(lambda p: qi.is_product_installed(p), profiles)):
0 ignored issues
show
introduced by
The variable qi does not seem to be defined for all execution paths.
Loading history...
218
        logger.info("SENAITE CORE already installed [SKIP]")
219
        return
220
221
    # Run Installers
222
    setup_groups(portal)
223
    remove_default_content(portal)
224
    # setup catalogs
225
    setup_core_catalogs(portal)
226
    setup_other_catalogs(portal)
227
    setup_catalog_mappings(portal)
228
    setup_auditlog_catalog_mappings(portal)
229
    setup_catalogs_order(portal)
230
    setup_content_structure(portal)
231
    add_senaite_setup(portal)
232
    add_dexterity_portal_items(portal)
233
    add_dexterity_setup_items(portal)
234
235
    # Set CMF Form actions
236
    setup_form_controller_actions(portal)
237
    setup_form_controller_more_action(portal)
238
239
    # Setup markup default and allowed schemas
240
    setup_markup_schema(portal)
241
242
    logger.info("SENAITE CORE install handler [DONE]")
243
244
245
def add_dexterity_setup_items(portal):
246
    """Adds the Dexterity Container in the Setup Folder
247
248
    N.B.: We do this in code, because adding this as Generic Setup Profile in
249
          `profiles/default/structure` flushes the contents on every import.
250
    """
251
    # Tuples of ID, Title, FTI
252
    items = [
253
        ("dynamic_analysisspecs",  # ID
254
         "Dynamic Analysis Specifications",  # Title
255
         "DynamicAnalysisSpecs"),  # FTI
256
257
        ("interpretation_templates",
258
         "Interpretation Templates",
259
         "InterpretationTemplates"),
260
261
        ("sample_containers",
262
         "Sample Containers",
263
         "SampleContainers")
264
    ]
265
    setup = api.get_setup()
266
    add_dexterity_items(setup, items)
267
268
269
def add_senaite_setup(portal):
270
    """Add the new SENAITE Setup container
271
    """
272
    items = [
273
        # ID, Title, FTI
274
        ("setup", "SENAITE Setup", "Setup"),
275
    ]
276
    add_dexterity_items(portal, items)
277
278
    # Move Setup at the beginning
279
    portal.moveObjectToPosition("setup", 0)
280
281
    # Reindex order
282
    portal.plone_utils.reindexOnReorder(portal)
283
284
285
def add_dexterity_portal_items(portal):
286
    """Adds the Dexterity Container in the Site folder
287
288
    N.B.: We do this in code, because adding this as Generic Setup Profile in
289
          `profiles/default/structure` flushes the contents on every import.
290
    """
291
    # Tuples of ID, Title, FTI
292
    items = [
293
        # ID, Title, FTI
294
        ("samples", "Samples", "Samples"),
295
    ]
296
    add_dexterity_items(portal, items)
297
298
    # Move Samples after Clients nav item
299
    position = portal.getObjectPosition("clients")
300
    portal.moveObjectToPosition("samples", position + 1)
301
302
    # Reindex order
303
    portal.plone_utils.reindexOnReorder(portal)
304
305
306
def add_dexterity_items(container, items):
307
    """Adds a dexterity item, usually a folder in the container
308
    :param container: container of the items to add
309
    :param items: tuple of Id, Title, FTI
310
    """
311
    for id, title, fti in items:
312
        obj = container.get(id)
313
        if obj is None:
314
            with temporary_allow_type(container, fti) as ct:
315
                obj = api.create(ct, fti, id=id, title=title)
316
        else:
317
            obj.setTitle(title)
318
        obj.reindexObject()
319
320
321
def setup_core_catalogs(portal, catalog_classes=None, reindex=True):
322
    """Setup core catalogs
323
    """
324
    logger.info("*** Setup core catalogs ***")
325
    at = api.get_tool("archetype_tool")
326
327
    # allow add-ons to use this handler with own catalogs
328
    if catalog_classes is None:
329
        catalog_classes = CATALOGS
330
331
    # contains tuples of (catalog, index) pairs
332
    to_reindex = []
333
334
    for cls in catalog_classes:
335
        module = _resolveDottedName(cls.__module__)
336
337
        # get the required attributes from the module
338
        catalog_id = module.CATALOG_ID
339
        catalog_indexes = module.INDEXES
340
        catalog_columns = module.COLUMNS
341
        catalog_types = module.TYPES
342
343
        catalog = getattr(aq_base(portal), catalog_id, None)
344
        if catalog is None:
345
            catalog = cls()
346
            catalog._setId(catalog_id)
347
            portal._setObject(catalog_id, catalog)
348
349
        # catalog indexes
350
        for idx_id, idx_attr, idx_type in catalog_indexes:
351
            if add_catalog_index(catalog, idx_id, idx_attr, idx_type):
352
                to_reindex.append((catalog, idx_id))
353
            else:
354
                continue
355
356
        # catalog columns
357
        for column in catalog_columns:
358
            add_catalog_column(catalog, column)
359
360
        if not reindex:
361
            logger.info("*** Skipping reindex of new indexes")
362
            return
363
364
        # map allowed types to this catalog in archetype_tool
365
        for portal_type in catalog_types:
366
            # check existing catalogs
367
            catalogs = at.getCatalogsByType(portal_type)
368
            if catalog not in catalogs:
369
                existing = list(map(lambda c: c.getId(), catalogs))
370
                new_catalogs = existing + [catalog_id]
371
                at.setCatalogsByType(portal_type, new_catalogs)
372
                logger.info("*** Mapped catalog '%s' for type '%s'"
373
                            % (catalog_id, portal_type))
374
375
    # reindex new indexes
376
    for catalog, idx_id in to_reindex:
377
        reindex_catalog_index(catalog, idx_id)
378
379
380
def setup_other_catalogs(portal, indexes=None, columns=None):
381
    logger.info("*** Setup other catalogs ***")
382
383
    # contains tuples of (catalog, index) pairs
384
    to_reindex = []
385
386
    # allow add-ons to use this handler with other index/column definitions
387
    if indexes is None:
388
        indexes = INDEXES
389
    if columns is None:
390
        columns = COLUMNS
391
392
    # catalog indexes
393
    for catalog, idx_id, idx_attr, idx_type in indexes:
394
        catalog = api.get_tool(catalog)
395
        if add_catalog_index(catalog, idx_id, idx_attr, idx_type):
396
            to_reindex.append((catalog, idx_id))
397
        else:
398
            continue
399
400
    # catalog columns
401
    for catalog, column in columns:
402
        catalog = api.get_tool(catalog)
403
        add_catalog_column(catalog, column)
404
405
    # reindex new indexes
406
    for catalog, idx_id in to_reindex:
407
        reindex_catalog_index(catalog, idx_id)
408
409
410
def reindex_catalog_index(catalog, index):
411
    catalog_id = catalog.id
412
    logger.info("*** Indexing new index '%s' in '%s' ..."
413
                % (index, catalog_id))
414
    reindex_index(catalog, index)
415
    logger.info("*** Indexing new index '%s' in '%s' [DONE]"
416
                % (index, catalog_id))
417
418
419
def add_catalog_index(catalog, idx_id, idx_attr, idx_type):
420
    indexes = get_indexes(catalog)
421
    # check if the index exists
422
    if idx_id in indexes:
423
        logger.info("*** %s '%s' already in catalog '%s'"
424
                    % (idx_type, idx_id, catalog.id))
425
        return False
426
    # create the index
427
    add_index(catalog, idx_id, idx_type, indexed_attrs=idx_attr)
428
    logger.info("*** Added %s '%s' for catalog '%s'"
429
                % (idx_type, idx_id, catalog.id))
430
    return True
431
432
433
def add_catalog_column(catalog, column):
434
    columns = get_columns(catalog)
435
    if column in columns:
436
        logger.info("*** Column '%s' already in catalog '%s'"
437
                    % (column, catalog.id))
438
        return False
439
    add_column(catalog, column)
440
    logger.info("*** Added column '%s' to catalog '%s'"
441
                % (column, catalog.id))
442
    return True
443
444
445
def setup_catalog_mappings(portal, catalog_mappings=None):
446
    """Setup portal_type -> catalog mappings
447
    """
448
    logger.info("*** Setup Catalog Mappings ***")
449
450
    # allow add-ons to use this handler with own mappings
451
    if catalog_mappings is None:
452
        catalog_mappings = CATALOG_MAPPINGS
453
454
    at = api.get_tool("archetype_tool")
455
    for portal_type, catalogs in catalog_mappings:
456
        at.setCatalogsByType(portal_type, catalogs)
457
458
459
def setup_auditlog_catalog_mappings(portal):
460
    """Map auditlog catalog to all AT content types
461
    """
462
    at = api.get_tool("archetype_tool")
463
    pt = api.get_tool("portal_types")
464
    portal_types = pt.listContentTypes()
465
466
    # map all known AT types to the auditlog catalog
467
    auditlog_catalog = api.get_tool(AUDITLOG_CATALOG)
468
    for portal_type in portal_types:
469
470
        # Do not map DX types into archetypes tool
471
        fti = pt.getTypeInfo(portal_type)
472
        if isinstance(fti, DexterityFTI):
473
            continue
474
475
        catalogs = at.getCatalogsByType(portal_type)
476
        if auditlog_catalog not in catalogs:
477
            existing_catalogs = list(map(lambda c: c.getId(), catalogs))
478
            new_catalogs = existing_catalogs + [AUDITLOG_CATALOG]
479
            at.setCatalogsByType(portal_type, new_catalogs)
480
            logger.info("*** Adding catalog '{}' for '{}'".format(
481
                AUDITLOG_CATALOG, portal_type))
482
483
484
def setup_catalogs_order(portal):
485
    """Ensures the order of catalogs portal types are bound to is correct
486
    This is required because senaite.app.supermodel uses the first catalog
487
    the portal type is associated with when retrieving brains
488
    """
489
    logger.info("Setup Catalogs order ...")
490
491
    def sort_catalogs(id1, id2):
492
        if id1 == id2:
493
            return 0
494
495
        # Audit-log catalog is always the last!
496
        if id1 == AUDITLOG_CATALOG:
497
            return 1
498
        if id2 == AUDITLOG_CATALOG:
499
            return -1
500
501
        # Catalogs sorted, senaite_* always first
502
        senaite = map(lambda cat_id: cat_id.startswith("senaite_"), [id1, id2])
503
        if not all(senaite) and any(senaite):
504
            # Item starting with senaite always gets max priority
505
            if id1.startswith("senaite_"):
506
                return -1
507
            return 1
508
509
        if id1 < id2:
510
            return -1
511
        return 1
512
513
    at = api.get_tool("archetype_tool")
514
    for portal_type, catalogs in at.listCatalogs().items():
515
        sorted_catalogs = sorted(catalogs, cmp=sort_catalogs)
516
        at.setCatalogsByType(portal_type, sorted_catalogs)
517
518
    logger.info("Setup Catalogs order [DONE]")
519
520
521
def remove_default_content(portal):
522
    """Remove default Plone contents
523
    """
524
    logger.info("*** Remove Default Content ***")
525
526
    # Get the list of object ids for portal
527
    object_ids = portal.objectIds()
528
    delete_ids = filter(lambda id: id in object_ids, CONTENTS_TO_DELETE)
529
    if delete_ids:
530
        portal.manage_delObjects(ids=list(delete_ids))
531
532
533
def setup_content_structure(portal):
534
    """Install the base content structure
535
    """
536
    logger.info("*** Install SENAITE Content Types ***")
537
    _run_import_step(portal, "content")
538
    reindex_content_structure(portal)
539
540
541
def setup_form_controller_more_action(portal):
542
    """Install form controller actions for ported record widgets
543
544
    Code taken from Products.ATExtensions
545
    """
546
    logger.info("*** Install SENAITE Form Controller Actions ***")
547
    pfc = portal.portal_form_controller
548
    pfc.addFormValidators(
549
        "base_edit", "", "more", "")
550
    pfc.addFormAction(
551
        "base_edit", "success", "", "more", "traverse_to", "string:more_edit")
552
    pfc.addFormValidators(
553
        "atct_edit", "", "more", "",)
554
    pfc.addFormAction(
555
        "atct_edit", "success", "", "more", "traverse_to", "string:more_edit")
556
557
558
def _run_import_step(portal, name, profile="profile-bika.lims:default"):
559
    """Helper to install a GS import step from the given profile
560
    """
561
    logger.info("*** Running import step '{}' from profile '{}' ***"
562
                .format(name, profile))
563
    setup = portal.portal_setup
564
    setup.runImportStepFromProfile(profile, name)
565
566
567
def pre_install(portal_setup):
568
    """Runs berfore the first import step of the *default* profile
569
570
    This handler is registered as a *pre_handler* in the generic setup profile
571
572
    :param portal_setup: SetupTool
573
    """
574
    logger.info("SENAITE CORE pre-install handler")
575
576
577
def post_install(portal_setup):
578
    """Runs after the last import step of the *default* profile
579
580
    This handler is registered as a *post_handler* in the generic setup profile
581
582
    :param portal_setup: SetupTool
583
    """
584
    logger.info("SENAITE CORE post install handler [BEGIN]")
585
586
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
587
    profile_id = PROFILE_ID
588
    context = portal_setup._getImportContext(profile_id)
589
    portal = context.getSite()
590
591
    # always apply the skins profile last to ensure our layers are first
592
    _run_import_step(portal, "skins", profile=profile_id)
593
594
    logger.info("SENAITE CORE post install handler [DONE]")
595
596
597
def setup_markup_schema(portal):
598
    """Sets the default and allowed markup schemas for RichText widgets
599
    """
600
    if not IMarkupSchema:
601
        return
602
603
    registry = getUtility(IRegistry, context=portal)
604
    settings = registry.forInterface(IMarkupSchema, prefix='plone')
605
    settings.default_type = u"text/html"
606
    settings.allowed_types = ("text/html", )
607