Attention

This document was written for an unsupported version of Plone, Plone 2.5.x, and was last updated 1178 days ago.

For more information, see the version support policy.

To learn how to upgrade to the current version of Plone, read the upgrade manual.

Add indexing attributes to arbitrary content types

by Frank Bennett last modified Feb 28, 2009 08:31 PM
How to invoke the shadowy and mysterious ExtensibleIndexableObjectWrapper to do something totally useless.

Plone's portal_catalog is a very powerful mechanism for retrieving portions of object content under Plone. Not only are searches fast, the search return contains embedded metadata that in many cases make it unnecessary to fetch the object itself, which yields a further speed advantage. The indexes associated with an object need not be limited to the content of fields. Indexes can contain values computed by a method -- even values computed from other content or other catalogs. This yields tremendous flexibility in design on top of the speed advantages of catalog retrieval.

This is all very exciting, but as you begin exploring the varied and wonderful possibilities of computed index values, you must beware of the temptation to add index attributes to content types offered by products maintained (or not) by other people. Subclassing or hacking the code of someone else's product is messy, and commits you to a maintenance burden of unknown future cost. It is not something to undertake lightly.

As it happens, though, from Plone 2.1.x forward it is possible to add arbitrary index attributes to any Plone content type without touching the code of the product that supplies it, using an undocumented facility embedded in the Plone portal catalog. (A tip of the hat to optilude for flagging this issue, I hope this small contribution helps.) The function within the catalog that does this is called (as you might have expected) ExtensibleIndexableObjectWrapper, and the method used to invoke it has the only slightly less Teutonic appellation registerIndexableAttribute. [1] Despite the intimidating length of the names involved, extending the catalog to apply additional methods to some or all of the objects it contains is quite simple to do.

From Plone 3.3, the Extensible Indexable Object Wrapper can be used as an adapter. See Indexing and Searching - Custom indexing strategies to learn how.

The code to create global index attributes needs to be spliced in via the initialize method of a product. The first step is to choose an appropriate product (or create one), and in its __init__.py file, add a function like this (this goes outside of the initialize function statement):

def _notUseful(obj,portal,vars):
    """A silly method for indexing things in a meaningless way
    """
    if obj.getTypeInfo().getId() == 'Folder':
        raise AttributeError
    else:
        return True

What is going on here? First, note that the attribute takes three arguments: context, portal and vars. As I'm both lazy and ignorant, I'll going to play it safe and just repeat what the source code says about these:

obj
the indexable object
portal
the portal root object
vars
additional vars as a dict, used for workflow vars like review_state

Second, note the use of AttributeError in the method above. Any callables that you register will be invoked on every object that is placed in the catalog. If you want to skip objects of a particular type (or objects that have a particular interface, gender, national origin, hair color or sexual preference), raising AttributeError on those objects will cause them to be skipped over. The code in the sample method above is, of course, arbitrary; whatever value the method returns will be the value placed in the index.

Defining the method may give us a warm feeling, but it is not actually going to be applied to anything until it is registered in the catalog. This is done through the initialize method of the same __init__.py file, like this:

from Products.CMFPlone.CatalogTool import registerIndexableAttribute

def initialize(context):
    registerIndexableAttribute('definitelyNotHelpful',_notUseful)

Note that the function name in this example has been carefully chosen to avoid name clashes with attributes supplied by other products. If an object supplies a method with a name identical to our registered attribute, the catalog will prefer the one provided by the object itself; so if you want to be sure your new attribute gets invoked, be sure it is unique.

If you restart Zope and use the ZMI to create a field index named definitelyNotHelpful, tick its tick-box and press the Reindex button, every non-Folder object in the catalog will be indexed with the value True. Hurray.

And, ah, that's it.

Footnotes:

[1] Trivia buffs will be eager to learn that this pair, impressive though they may be, lose out in the Longest Function Names in Plone 2.5 rankings to removePloneSetupActionFromPortalMembership (weighing in at a robust 42 characters), and to the more or less incomprehensible runner-up, addTypesUseViewActionInListingsProperty (a plump but still ambulatory 39 characters).


Contribute

Something wrong or out of date? Anybody can edit or create a new article in the knowledge base. Simply create an account on this site, log in, and click the Edit button to contribute.