Passed
Pull Request — 2.x (#1872)
by Ramon
05:26
created

senaite.core.setuphandlers   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 457
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
eloc 292
dl 0
loc 457
rs 8.8798
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
A install() 0 45 4
A setup_form_controller_more_action() 0 15 1
A add_catalog_index() 0 12 2
A post_install() 0 18 1
B setup_other_catalogs() 0 30 7
A add_catalog_column() 0 10 2
A setup_content_structure() 0 6 1
A setup_auditlog_catalog_mappings() 0 17 4
A setup_catalog_mappings() 0 8 2
A remove_default_content() 0 10 3
A setup_markup_schema() 0 10 2
A reindex_catalog_index() 0 7 1
C setup_core_catalogs() 0 53 11
A _run_import_step() 0 7 1
A pre_install() 0 15 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A HiddenProfiles.getNonInstallableProfiles() 0 22 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 add_dexterity_portal_items
24
from bika.lims.setuphandlers import add_dexterity_setup_items
25
from bika.lims.setuphandlers import reindex_content_structure
26
from bika.lims.setuphandlers import setup_form_controller_actions
27
from bika.lims.setuphandlers import setup_groups
28
from plone.registry.interfaces import IRegistry
29
from Products.CMFPlone.utils import get_installer
30
from Products.GenericSetup.utils import _resolveDottedName
31
from senaite.core import logger
32
from senaite.core.api.catalog import add_column
33
from senaite.core.api.catalog import add_index
34
from senaite.core.api.catalog import get_columns
35
from senaite.core.api.catalog import get_indexes
36
from senaite.core.api.catalog import reindex_index
37
from senaite.core.catalog import AUDITLOG_CATALOG
38
from senaite.core.catalog import AnalysisCatalog
39
from senaite.core.catalog import AuditlogCatalog
40
from senaite.core.catalog import AutoImportLogCatalog
41
from senaite.core.catalog import ReportCatalog
42
from senaite.core.catalog import SampleCatalog
43
from senaite.core.catalog import SenaiteCatalog
44
from senaite.core.catalog import SetupCatalog
45
from senaite.core.catalog import WorksheetCatalog
46
from senaite.core.config import PROFILE_ID
47
from zope.component import getUtility
48
from zope.interface import implementer
49
50
try:
51
    from Products.CMFPlone.interfaces import IMarkupSchema
52
    from Products.CMFPlone.interfaces import INonInstallable
53
except ImportError:
54
    from zope.interface import Interface
55
    IMarkupSchema = None
56
57
    class INonInstallable(Interface):
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable Interface does not seem to be defined.
Loading history...
58
        pass
59
60
61
@implementer(INonInstallable)
62
class HiddenProfiles(object):
63
    def getNonInstallableProfiles(self):
64
        """Hide all profiles from site-creation and quickinstaller (not ZMI)"""
65
        return [
66
            # Leave visible to allow upgrade via the Plone Add-on controlpanel
67
            # "bika.lims:default",
68
69
            # hide install profiles that come with Plone
70
            "Products.CMFPlacefulWorkflow:CMFPlacefulWorkflow",
71
            "Products.CMFPlacefulWorkflow:base",
72
            "Products.CMFPlacefulWorkflow:uninstall",
73
            "Products.DataGridField:default",
74
            "Products.DataGridField:example",
75
            "Products.TextIndexNG3:default",
76
            "archetypes.multilingual:default",
77
            "archetypes.referencebrowserwidget:default",
78
            "collective.js.jqueryui:default"
79
            "plone.app.iterate:default",
80
            "plone.app.iterate:plone.app.iterate",
81
            "plone.app.iterate:test",
82
            "plone.app.iterate:uninstall",
83
            "plone.app.jquery:default",
84
            "plonetheme.barceloneta:default",
85
        ]
86
87
88
CONTENTS_TO_DELETE = (
89
    # List of items to delete
90
    "Members",
91
    "news",
92
    "events",
93
)
94
95
CATALOGS = (
96
    AnalysisCatalog,
97
    AuditlogCatalog,
98
    AutoImportLogCatalog,
99
    SampleCatalog,
100
    SenaiteCatalog,
101
    SetupCatalog,
102
    WorksheetCatalog,
103
    ReportCatalog,
104
)
105
106
INDEXES = (
107
    # catalog, id, indexed attribute, type
108
    ("portal_catalog", "Analyst", "", "FieldIndex"),
109
    ("portal_catalog", "analysisRequestTemplates", "", "FieldIndex"),
110
    ("portal_catalog", "getFullname", "", "FieldIndex"),
111
    ("portal_catalog", "getName", "", "FieldIndex"),
112
    ("portal_catalog", "getParentUID", "", "FieldIndex"),
113
    ("portal_catalog", "getUsername", "", "FieldIndex"),
114
    ("portal_catalog", "is_active", "", "BooleanIndex"),
115
    ("portal_catalog", "path", "getPhysicalPath", "ExtendedPathIndex"),
116
    ("portal_catalog", "review_state", "", "FieldIndex"),
117
    ("portal_catalog", "sample_uid", "", "KeywordIndex"),
118
    ("portal_catalog", "title", "", "FieldIndex"),
119
)
120
121
COLUMNS = (
122
    # catalog, column name
123
    ("portal_catalog", "analysisRequestTemplates"),
124
    ("portal_catalog", "review_state"),
125
    ("portal_catalog", "getClientID"),
126
    ("portal_catalog", "Analyst"),
127
)
128
129
CATALOG_MAPPINGS = (
130
    # portal_type, catalog_ids
131
    ("ARTemplate", ["senaite_catalog_setup", "portal_catalog"]),
132
    ("AnalysisCategory", ["senaite_catalog_setup", "portal_catalog"]),
133
    ("AnalysisProfile", ["senaite_catalog_setup", "portal_catalog"]),
134
    ("AnalysisService", ["senaite_catalog_setup", "portal_catalog"]),
135
    ("AnalysisSpec", ["senaite_catalog_setup", "portal_catalog"]),
136
    ("Attachment", ["senaite_catalog", "portal_catalog"]),
137
    ("AttachmentType", ["senaite_catalog_setup", "portal_catalog"]),
138
    ("Batch", ["senaite_catalog", "portal_catalog"]),
139
    ("BatchLabel", ["senaite_catalog_setup", "portal_catalog"]),
140
    ("Calculation", ["senaite_catalog_setup", "portal_catalog"]),
141
    ("Container", ["senaite_catalog_setup", "portal_catalog"]),
142
    ("ContainerType", ["senaite_catalog_setup", "portal_catalog"]),
143
    ("Department", ["senaite_catalog_setup", "portal_catalog"]),
144
    ("Instrument", ["senaite_catalog_setup", "portal_catalog"]),
145
    ("InstrumentType", ["senaite_catalog_setup", "portal_catalog"]),
146
    ("LabContact", ["senaite_catalog_setup", "portal_catalog"]),
147
    ("LabProduct", ["senaite_catalog_setup", "portal_catalog"]),
148
    ("Manufacturer", ["senaite_catalog_setup", "portal_catalog"]),
149
    ("Method", ["senaite_catalog_setup", "portal_catalog"]),
150
    ("Multifile", ["senaite_catalog_setup", "portal_catalog"]),
151
    ("Preservation", ["senaite_catalog_setup", "portal_catalog"]),
152
    ("ReferenceDefinition", ["senaite_catalog_setup", "portal_catalog"]),
153
    ("ReferenceSample", ["senaite_catalog", "portal_catalog"]),
154
    ("SampleCondition", ["senaite_catalog_setup", "portal_catalog"]),
155
    ("SampleMatrix", ["senaite_catalog_setup", "portal_catalog"]),
156
    ("SamplePoint", ["senaite_catalog_setup", "portal_catalog"]),
157
    ("SampleType", ["senaite_catalog_setup", "portal_catalog"]),
158
    ("SamplingDeviation", ["senaite_catalog_setup", "portal_catalog"]),
159
    ("StorageLocation", ["senaite_catalog_setup", "portal_catalog"]),
160
    ("SubGroup", ["senaite_catalog_setup", "portal_catalog"]),
161
    ("Supplier", ["senaite_catalog_setup", "portal_catalog"]),
162
    ("WorksheetTemplate", ["senaite_catalog_setup", "portal_catalog"]),
163
)
164
165
166
def install(context):
167
    """Install handler
168
    """
169
    if context.readDataFile("senaite.core.txt") is None:
170
        return
171
172
    logger.info("SENAITE CORE install handler [BEGIN]")
173
    portal = context.getSite()
174
175
    # Run required import steps
176
    _run_import_step(portal, "skins")
177
    _run_import_step(portal, "browserlayer")
178
    _run_import_step(portal, "rolemap")
179
    _run_import_step(portal, "typeinfo")
180
    _run_import_step(portal, "factorytool")
181
    _run_import_step(portal, "workflow", "profile-senaite.core:default")
182
    _run_import_step(portal, "typeinfo", "profile-senaite.core:default")
183
184
    # skip installers if already installed
185
    qi = get_installer(portal)
186
    profiles = ["bika.lims", "senaite.core"]
187
    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...
188
        logger.info("SENAITE CORE already installed [SKIP]")
189
        return
190
191
    # Run Installers
192
    setup_groups(portal)
193
    remove_default_content(portal)
194
    # setup catalogs
195
    setup_core_catalogs(portal)
196
    setup_other_catalogs(portal)
197
    setup_catalog_mappings(portal)
198
    setup_auditlog_catalog_mappings(portal)
199
    setup_content_structure(portal)
200
    add_dexterity_portal_items(portal)
201
    add_dexterity_setup_items(portal)
202
203
    # Set CMF Form actions
204
    setup_form_controller_actions(portal)
205
    setup_form_controller_more_action(portal)
206
207
    # Setup markup default and allowed schemas
208
    setup_markup_schema(portal)
209
210
    logger.info("SENAITE CORE install handler [DONE]")
211
212
213
def setup_core_catalogs(portal, reindex=True):
214
    """Setup core catalogs
215
    """
216
    logger.info("*** Setup core catalogs ***")
217
    at = api.get_tool("archetype_tool")
218
219
    for cls in CATALOGS:
220
        module = _resolveDottedName(cls.__module__)
221
222
        # get the required attributes from the module
223
        catalog_id = module.CATALOG_ID
224
        catalog_indexes = module.INDEXES
225
        catalog_columns = module.COLUMNS
226
        catalog_types = module.TYPES
227
228
        catalog = getattr(aq_base(portal), catalog_id, None)
229
        if catalog is None:
230
            catalog = cls()
231
            catalog._setId(catalog_id)
232
            portal._setObject(catalog_id, catalog)
233
234
        # contains tuples of (catalog, index) pairs
235
        to_reindex = []
236
237
        # catalog indexes
238
        for idx_id, idx_attr, idx_type in catalog_indexes:
239
            if add_catalog_index(catalog, idx_id, idx_attr, idx_type):
240
                to_reindex.append((catalog, idx_id))
241
            else:
242
                continue
243
244
        # catalog columns
245
        for column in catalog_columns:
246
            add_catalog_column(catalog, column)
247
248
        if not reindex:
249
            logger.info("*** Skipping reindex of new indexes")
250
            return
251
252
        # map allowed types to this catalog in archetype_tool
253
        for portal_type in catalog_types:
254
            # check existing catalogs
255
            catalogs = at.getCatalogsByType(portal_type)
256
            if catalog not in catalogs:
257
                existing = list(map(lambda c: c.getId(), catalogs))
258
                new_catalogs = existing + [catalog_id]
259
                at.setCatalogsByType(portal_type, new_catalogs)
260
                logger.info("*** Mapped catalog '%s' for type '%s'"
261
                            % (catalog_id, portal_type))
262
263
    # reindex new indexes
264
    for catalog, idx_id in to_reindex:
0 ignored issues
show
introduced by
The variable to_reindex does not seem to be defined in case the for loop on line 219 is not entered. Are you sure this can never be the case?
Loading history...
265
        reindex_catalog_index(catalog, idx_id)
266
267
268
def setup_other_catalogs(portal, reindex=True):
269
    logger.info("*** Setup other catalogs ***")
270
271
    # contains tuples of (catalog, index) pairs
272
    to_reindex = []
273
    refresh_catalog = False
274
275
    # catalog indexes
276
    for catalog, idx_id, idx_attr, idx_type in INDEXES:
277
        catalog = api.get_tool(catalog)
278
        if add_catalog_index(catalog, idx_id, idx_attr, idx_type):
279
            to_reindex.append((catalog, idx_id))
280
        else:
281
            continue
282
283
    # catalog columns
284
    for catalog, column in COLUMNS:
285
        catalog = api.get_tool(catalog)
286
        if add_catalog_column(catalog, column):
287
            refresh_catalog = True
288
        else:
289
            continue
290
291
    # reindex new indexes
292
    for catalog, idx_id in to_reindex:
293
        reindex_catalog_index(catalog, idx_id)
294
295
    # refresh
296
    if refresh_catalog:
297
        catalog.refreshCatalog()
0 ignored issues
show
introduced by
The variable catalog does not seem to be defined in case the for loop on line 276 is not entered. Are you sure this can never be the case?
Loading history...
298
299
300
def reindex_catalog_index(catalog, index):
301
    catalog_id = catalog.id
302
    logger.info("*** Indexing new index '%s' in '%s' ..."
303
                % (index, catalog_id))
304
    reindex_index(catalog, index)
305
    logger.info("*** Indexing new index '%s' in '%s' [DONE]"
306
                % (index, catalog_id))
307
308
309
def add_catalog_index(catalog, idx_id, idx_attr, idx_type):
310
    indexes = get_indexes(catalog)
311
    # check if the index exists
312
    if idx_id in indexes:
313
        logger.info("*** %s '%s' already in catalog '%s'"
314
                    % (idx_type, idx_id, catalog.id))
315
        return False
316
    # create the index
317
    add_index(catalog, idx_id, idx_type, indexed_attrs=idx_attr)
318
    logger.info("*** Added %s '%s' for catalog '%s'"
319
                % (idx_type, idx_id, catalog.id))
320
    return True
321
322
323
def add_catalog_column(catalog, column):
324
    columns = get_columns(catalog)
325
    if column in columns:
326
        logger.info("*** Column '%s' already in catalog '%s'"
327
                    % (column, catalog.id))
328
        return False
329
    add_column(catalog, column)
330
    logger.info("*** Added column '%s' to catalog '%s'"
331
                % (column, catalog.id))
332
    return True
333
334
335
def setup_catalog_mappings(portal):
336
    """Setup portal_type -> catalog mappings
337
    """
338
    logger.info("*** Setup Catalog Mappings ***")
339
340
    at = api.get_tool("archetype_tool")
341
    for portal_type, catalogs in CATALOG_MAPPINGS:
342
        at.setCatalogsByType(portal_type, catalogs)
343
344
345
def setup_auditlog_catalog_mappings(portal):
346
    """Map auditlog catalog to all AT content types
347
    """
348
    at = api.get_tool("archetype_tool")
349
    pt = api.get_tool("portal_types")
350
    portal_types = pt.listContentTypes()
351
352
    # map all known types to the auditlog catalog
353
    auditlog_catalog = api.get_tool(AUDITLOG_CATALOG)
354
    for portal_type in portal_types:
355
        catalogs = at.getCatalogsByType(portal_type)
356
        if auditlog_catalog not in catalogs:
357
            existing_catalogs = list(map(lambda c: c.getId(), catalogs))
358
            new_catalogs = existing_catalogs + [AUDITLOG_CATALOG]
359
            at.setCatalogsByType(portal_type, new_catalogs)
360
            logger.info("*** Adding catalog '{}' for '{}'".format(
361
                AUDITLOG_CATALOG, portal_type))
362
363
364
def remove_default_content(portal):
365
    """Remove default Plone contents
366
    """
367
    logger.info("*** Remove Default Content ***")
368
369
    # Get the list of object ids for portal
370
    object_ids = portal.objectIds()
371
    delete_ids = filter(lambda id: id in object_ids, CONTENTS_TO_DELETE)
372
    if delete_ids:
373
        portal.manage_delObjects(ids=list(delete_ids))
374
375
376
def setup_content_structure(portal):
377
    """Install the base content structure
378
    """
379
    logger.info("*** Install SENAITE Content Types ***")
380
    _run_import_step(portal, "content")
381
    reindex_content_structure(portal)
382
383
384
def setup_form_controller_more_action(portal):
385
    """Install form controller actions for ported record widgets
386
387
    Code taken from Products.ATExtensions
388
    """
389
    logger.info("*** Install SENAITE Form Controller Actions ***")
390
    pfc = portal.portal_form_controller
391
    pfc.addFormValidators(
392
        "base_edit", "", "more", "")
393
    pfc.addFormAction(
394
        "base_edit", "success", "", "more", "traverse_to", "string:more_edit")
395
    pfc.addFormValidators(
396
        "atct_edit", "", "more", "",)
397
    pfc.addFormAction(
398
        "atct_edit", "success", "", "more", "traverse_to", "string:more_edit")
399
400
401
def _run_import_step(portal, name, profile="profile-bika.lims:default"):
402
    """Helper to install a GS import step from the given profile
403
    """
404
    logger.info("*** Running import step '{}' from profile '{}' ***"
405
                .format(name, profile))
406
    setup = portal.portal_setup
407
    setup.runImportStepFromProfile(profile, name)
408
409
410
def pre_install(portal_setup):
411
    """Runs berfore the first import step of the *default* profile
412
413
    This handler is registered as a *pre_handler* in the generic setup profile
414
415
    :param portal_setup: SetupTool
416
    """
417
    logger.info("SENAITE CORE pre-install handler [BEGIN]")
418
419
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
420
    profile_id = PROFILE_ID
421
    context = portal_setup._getImportContext(profile_id)
422
    portal = context.getSite()  # noqa
423
424
    logger.info("SENAITE CORE pre-install handler [DONE]")
425
426
427
def post_install(portal_setup):
428
    """Runs after the last import step of the *default* profile
429
430
    This handler is registered as a *post_handler* in the generic setup profile
431
432
    :param portal_setup: SetupTool
433
    """
434
    logger.info("SENAITE CORE post install handler [BEGIN]")
435
436
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
437
    profile_id = PROFILE_ID
438
    context = portal_setup._getImportContext(profile_id)
439
    portal = context.getSite()  # noqa
440
441
    # always apply the skins profile last to ensure our layers are first
442
    _run_import_step(portal, "skins", profile=profile_id)
443
444
    logger.info("SENAITE CORE post install handler [DONE]")
445
446
447
def setup_markup_schema(portal):
448
    """Sets the default and allowed markup schemas for RichText widgets
449
    """
450
    if not IMarkupSchema:
451
        return
452
453
    registry = getUtility(IRegistry, context=portal)
454
    settings = registry.forInterface(IMarkupSchema, prefix='plone')
455
    settings.default_type = u"text/html"
456
    settings.allowed_types = ("text/html", )
457