Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Multiple Catalogs for Dexterity Contents #1489

Merged
merged 5 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Changelog

**Added**

- #1489 Support Multiple Catalogs for Dexterity Contents
- #1481 Filter Templates field when Sample Type is selected in Sample Add form
- #1483 Added Accredited symbol in Analyses listings
- #1466 Support for "readonly" and "hidden" visibility modes in ReferenceWidget
Expand Down
92 changes: 92 additions & 0 deletions bika/lims/catalog/catalog_multiplex_processor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*-

from Acquisition import aq_base
from bika.lims import api
from bika.lims import logger
from bika.lims.config import USE_COLLECTIVE_INDEXING
from bika.lims.interfaces import IMultiCatalogBehavior
from zope.interface import implements

if USE_COLLECTIVE_INDEXING:
from collective.indexing.interfaces import IIndexQueueProcessor

REQUIRED_CATALOGS = [
"auditlog_catalog",
]


class CatalogMultiplexProcessor(object):
"""A catalog multiplex processor
"""
if USE_COLLECTIVE_INDEXING:
implements(IIndexQueueProcessor)

def get_catalogs_for(self, obj):
catalogs = getattr(obj, "_catalogs", [])
for rc in REQUIRED_CATALOGS:
if rc in catalogs:
continue
catalogs.append(rc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the following be easier?:

catalogs = getattr(obj, "_catalogs", []) + REQUIRED_CATALOGS
catalogs = list(set(catalogs))
return map(api.get_tool, catalogs)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the current approach is more readable and performant, because in your approach the list hast to be iterated multiple times to generate first a set and convert it then to a list.
But indeed, it would be interesting what approach is faster...

... 5 minutes later ...

Here comes the proof:

REQUIRED_CATALOGS = [
    "auditlog_catalog",
]

def v1(obj):
    catalogs = getattr(obj, "_catalogs", [])
    for rc in REQUIRED_CATALOGS:
        if rc in catalogs:
            continue
        catalogs.append(rc)
    return catalogs

def v2(obj):
     catalogs = getattr(obj, "_catalogs", []) + REQUIRED_CATALOGS
     catalogs = list(set(catalogs))
     return catalogs

class Content(object):
     _catalogs = ["setup_catalog"]

obj = Content()

if __name__ == '__main__':
    from timeit import timeit
    print timeit("v1(obj)", setup="from __main__ import obj, v1")
    print timeit("v2(obj)", setup="from __main__ import obj, v2")

Output:

0.572149038315
0.9786028862

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pheew!

return map(api.get_tool, catalogs)

def supports_multi_catalogs(self, obj):
"""Check if the Multi Catalog Behavior is enabled
"""
if IMultiCatalogBehavior(obj, None) is None:
return False
return True

def index(self, obj, attributes=None):
if attributes is None:
attributes = []

if not self.supports_multi_catalogs(obj):
return

catalogs = self.get_catalogs_for(obj)
url = api.get_path(obj)

for catalog in catalogs:
logger.info(
"CatalogMultiplexProcessor::indexObject:catalog={} url={}"
.format(catalog.id, url))
# We want the intersection of the catalogs idxs
# and the incoming list.
indexes = set(catalog.indexes()).intersection(attributes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If attributes have been manually specified, but no matches with current catalog are found, the object will be reindexed for all indexes. I would rather skip this case for better performance:

indexes = set(catalog.indexes()).intersection(attributes)
if attributes and not indexes:
    continue

logger.info(
    "CatalogMultiplexProcessor::indexObject:catalog={} url={}"
    .format(catalog.id, url))
catalog.catalog_object(obj, url, idxs=list(indexes))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, will change that

# Skip reindexing if no indexes match
if attributes and not indexes:
continue
# recatalog the object
catalog.catalog_object(obj, url, idxs=list(indexes))

def reindex(self, obj, attributes=None):
self.index(obj, attributes)

def unindex(self, obj):
wrapped_obj = obj
if aq_base(obj).__class__.__name__ == "PathWrapper":
# Could be a PathWrapper object from collective.indexing.
obj = obj.context

if not self.supports_multi_catalogs(obj):
return

catalogs = self.get_catalogs_for(obj)
# get the old path from the wrapped object
url = api.get_path(wrapped_obj)

for catalog in catalogs:
if catalog._catalog.uids.get(url, None) is not None:
logger.info(
"CatalogMultiplexProcessor::unindex:catalog={} url={}"
.format(catalog.id, url))
catalog.uncatalog_object(url)

def begin(self):
pass

def commit(self):
pass

def abort(self):
pass
21 changes: 21 additions & 0 deletions bika/lims/catalog/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml"
i18n_domain="senaite.core">

<!-- Catalog Indexes -->
<include package=".indexers" />

<!-- N.B. `IPortalCatalogQueueProcessor` will be included in
`Products.CMFCore` in Plone 5 and can be removed then -->
<include
zcml:condition="installed collective.indexing"
package="collective.indexing" />

<utility
zcml:condition="installed collective.indexing"
factory=".catalog_multiplex_processor.CatalogMultiplexProcessor"
provides="collective.indexing.indexer.IPortalCatalogQueueProcessor"
name="catalogmultiplex"
/>

</configure>
8 changes: 8 additions & 0 deletions bika/lims/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
from bika.lims.utils import t # noqa
from bika.lims.permissions import * # noqa

try:
import collective.indexing
collective.indexing # noqa
except ImportError:
USE_COLLECTIVE_INDEXING = False
else:
USE_COLLECTIVE_INDEXING = True


PROJECTNAME = "bika.lims"

Expand Down
9 changes: 8 additions & 1 deletion bika/lims/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

<include package=".adapters" />
<include package=".browser" />
<include package=".catalog.indexers" />
<include package=".catalog" />
<include package=".content" />
<include package=".controlpanel" />
<include package=".exportimport" />
Expand All @@ -51,6 +51,13 @@
provides="bika.lims.interfaces.IAutoGenerateID"
/>

<!-- Multicatalog Behavior for Dexterity based contents -->
<plone:behavior
title="Multi Catalog Behavior for Dexterity Contents"
description="Catalog Dexterity contents in multiple catalogs"
provides="bika.lims.interfaces.IMultiCatalogBehavior"
/>

<!-- jquery redirects here when values are entered into 'document' context (barcodes) -->
<browser:page
for="*"
Expand Down
5 changes: 5 additions & 0 deletions bika/lims/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ class IAutoGenerateID(Interface):
"""


class IMultiCatalogBehavior(Interface):
"""Support multiple catalogs for Dexterity contents
"""


class IActionHandlerPool(Interface):
"""Marker interface for the ActionHandlerPool utility
"""
Expand Down
3 changes: 3 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@
'senaite.impress>=1.2.0',
# Python 2/3 compatibility library: https://six.readthedocs.io/
'six',
# Needed for `IPortalCatalogQueueProcessor`, which will be included in
# `Products.CMFCore` in Plone 5. Remove after we are on Plone 5!
'collective.indexing',
],
extras_require={
'test': [
Expand Down