Passed
Pull Request — 2.x (#1872)
by Ramon
04:55
created

remove_default_content()   A

Complexity

Conditions 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
rs 10
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-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, indexes=None, columns=None, 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
    # allow add-ons to use this handler with other index/column definitions
276
    if indexes is None:
277
        indexes = INDEXES
278
    if columns is None:
279
        columns = COLUMNS
280
281
    # catalog indexes
282
    for catalog, idx_id, idx_attr, idx_type in indexes:
283
        catalog = api.get_tool(catalog)
284
        if add_catalog_index(catalog, idx_id, idx_attr, idx_type):
285
            to_reindex.append((catalog, idx_id))
286
        else:
287
            continue
288
289
    # catalog columns
290
    for catalog, column in columns:
291
        catalog = api.get_tool(catalog)
292
        if add_catalog_column(catalog, column):
293
            refresh_catalog = True
294
        else:
295
            continue
296
297
    # reindex new indexes
298
    for catalog, idx_id in to_reindex:
299
        reindex_catalog_index(catalog, idx_id)
300
301
    # refresh
302
    if refresh_catalog:
303
        catalog.refreshCatalog()
0 ignored issues
show
introduced by
The variable catalog does not seem to be defined in case the for loop on line 282 is not entered. Are you sure this can never be the case?
Loading history...
304
305
306
def reindex_catalog_index(catalog, index):
307
    catalog_id = catalog.id
308
    logger.info("*** Indexing new index '%s' in '%s' ..."
309
                % (index, catalog_id))
310
    reindex_index(catalog, index)
311
    logger.info("*** Indexing new index '%s' in '%s' [DONE]"
312
                % (index, catalog_id))
313
314
315
def add_catalog_index(catalog, idx_id, idx_attr, idx_type):
316
    indexes = get_indexes(catalog)
317
    # check if the index exists
318
    if idx_id in indexes:
319
        logger.info("*** %s '%s' already in catalog '%s'"
320
                    % (idx_type, idx_id, catalog.id))
321
        return False
322
    # create the index
323
    add_index(catalog, idx_id, idx_type, indexed_attrs=idx_attr)
324
    logger.info("*** Added %s '%s' for catalog '%s'"
325
                % (idx_type, idx_id, catalog.id))
326
    return True
327
328
329
def add_catalog_column(catalog, column):
330
    columns = get_columns(catalog)
331
    if column in columns:
332
        logger.info("*** Column '%s' already in catalog '%s'"
333
                    % (column, catalog.id))
334
        return False
335
    add_column(catalog, column)
336
    logger.info("*** Added column '%s' to catalog '%s'"
337
                % (column, catalog.id))
338
    return True
339
340
341
def setup_catalog_mappings(portal):
342
    """Setup portal_type -> catalog mappings
343
    """
344
    logger.info("*** Setup Catalog Mappings ***")
345
346
    at = api.get_tool("archetype_tool")
347
    for portal_type, catalogs in CATALOG_MAPPINGS:
348
        at.setCatalogsByType(portal_type, catalogs)
349
350
351
def setup_auditlog_catalog_mappings(portal):
352
    """Map auditlog catalog to all AT content types
353
    """
354
    at = api.get_tool("archetype_tool")
355
    pt = api.get_tool("portal_types")
356
    portal_types = pt.listContentTypes()
357
358
    # map all known types to the auditlog catalog
359
    auditlog_catalog = api.get_tool(AUDITLOG_CATALOG)
360
    for portal_type in portal_types:
361
        catalogs = at.getCatalogsByType(portal_type)
362
        if auditlog_catalog not in catalogs:
363
            existing_catalogs = list(map(lambda c: c.getId(), catalogs))
364
            new_catalogs = existing_catalogs + [AUDITLOG_CATALOG]
365
            at.setCatalogsByType(portal_type, new_catalogs)
366
            logger.info("*** Adding catalog '{}' for '{}'".format(
367
                AUDITLOG_CATALOG, portal_type))
368
369
370
def remove_default_content(portal):
371
    """Remove default Plone contents
372
    """
373
    logger.info("*** Remove Default Content ***")
374
375
    # Get the list of object ids for portal
376
    object_ids = portal.objectIds()
377
    delete_ids = filter(lambda id: id in object_ids, CONTENTS_TO_DELETE)
378
    if delete_ids:
379
        portal.manage_delObjects(ids=list(delete_ids))
380
381
382
def setup_content_structure(portal):
383
    """Install the base content structure
384
    """
385
    logger.info("*** Install SENAITE Content Types ***")
386
    _run_import_step(portal, "content")
387
    reindex_content_structure(portal)
388
389
390
def setup_form_controller_more_action(portal):
391
    """Install form controller actions for ported record widgets
392
393
    Code taken from Products.ATExtensions
394
    """
395
    logger.info("*** Install SENAITE Form Controller Actions ***")
396
    pfc = portal.portal_form_controller
397
    pfc.addFormValidators(
398
        "base_edit", "", "more", "")
399
    pfc.addFormAction(
400
        "base_edit", "success", "", "more", "traverse_to", "string:more_edit")
401
    pfc.addFormValidators(
402
        "atct_edit", "", "more", "",)
403
    pfc.addFormAction(
404
        "atct_edit", "success", "", "more", "traverse_to", "string:more_edit")
405
406
407
def _run_import_step(portal, name, profile="profile-bika.lims:default"):
408
    """Helper to install a GS import step from the given profile
409
    """
410
    logger.info("*** Running import step '{}' from profile '{}' ***"
411
                .format(name, profile))
412
    setup = portal.portal_setup
413
    setup.runImportStepFromProfile(profile, name)
414
415
416
def pre_install(portal_setup):
417
    """Runs berfore the first import step of the *default* profile
418
419
    This handler is registered as a *pre_handler* in the generic setup profile
420
421
    :param portal_setup: SetupTool
422
    """
423
    logger.info("SENAITE CORE pre-install handler [BEGIN]")
424
425
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
426
    profile_id = PROFILE_ID
427
    context = portal_setup._getImportContext(profile_id)
428
    portal = context.getSite()  # noqa
429
430
    logger.info("SENAITE CORE pre-install handler [DONE]")
431
432
433
def post_install(portal_setup):
434
    """Runs after the last import step of the *default* profile
435
436
    This handler is registered as a *post_handler* in the generic setup profile
437
438
    :param portal_setup: SetupTool
439
    """
440
    logger.info("SENAITE CORE post install handler [BEGIN]")
441
442
    # https://docs.plone.org/develop/addons/components/genericsetup.html#custom-installer-code-setuphandlers-py
443
    profile_id = PROFILE_ID
444
    context = portal_setup._getImportContext(profile_id)
445
    portal = context.getSite()  # noqa
446
447
    # always apply the skins profile last to ensure our layers are first
448
    _run_import_step(portal, "skins", profile=profile_id)
449
450
    logger.info("SENAITE CORE post install handler [DONE]")
451
452
453
def setup_markup_schema(portal):
454
    """Sets the default and allowed markup schemas for RichText widgets
455
    """
456
    if not IMarkupSchema:
457
        return
458
459
    registry = getUtility(IRegistry, context=portal)
460
    settings = registry.forInterface(IMarkupSchema, prefix='plone')
461
    settings.default_type = u"text/html"
462
    settings.allowed_types = ("text/html", )
463