[Zope3-checkins] CVS: Zope3/src/zope/app/catalog - __init__.py:1.1.2.1 catalog.py:1.1.2.1 catalog.txt:1.1.2.1 configure.zcml:1.1.2.1

Anthony Baxter anthony@interlink.com.au
Sat, 12 Jul 2003 02:15:18 -0400


Update of /cvs-repository/Zope3/src/zope/app/catalog
In directory cvs.zope.org:/tmp/cvs-serv26101/catalog

Added Files:
      Tag: melb-2003-content-catalog-branch
	__init__.py catalog.py catalog.txt configure.zcml 
Log Message:
first cut at catalogs. There are both content-space catalogs and utility-space
catalogs - in zope/app/catalog/catalog.txt is an example of creating the 
latter.

Next up is to re-work the Catalog<->Index interface. If you create 
catalogs now, you'll need to throw them away when those changes land
(soon). Note also that the search interface at the moment is a little
bit primitive - you call 'searchResults(key=value, key2=value2)'.



=== Added File Zope3/src/zope/app/catalog/__init__.py ===


=== Added File Zope3/src/zope/app/catalog/catalog.py ===
from persistence import Persistent
from persistence.dict import PersistentDict
from zope.interface import implements
from zope.context import ContextMethod
from zope.app.zapi import getService
from zope.app.services.servicenames import HubIds
from zope.exceptions import NotFoundError
from zope.app.interfaces.services.registration import IRegisterable
from zope.app.interfaces.event import ISubscriber
from zope.app.interfaces.container import IDeleteNotifiable
from zope.app.interfaces.annotation import IAttributeAnnotatable
from zope.app.interfaces.services.utility import ILocalUtility

from zope.app.interfaces.container import IDeleteNotifiable, IAddNotifiable
from zope.app.interfaces.container import IContainer

from zope.app.container.sample import SampleContainer

# gods save us from 5-deep nested pkgs
import zope.app.interfaces.services.hub as Hub

import time

from zope.app.interfaces.catalog.catalog import ICatalogView, ICatalog

class Catalog(Persistent, SampleContainer):

    implements(ICatalog, ISubscriber, IDeleteNotifiable, 
               IAddNotifiable, IContainer, IAttributeAnnotatable)

    _subscribed = False
    _channel = None

    def _newContainerData(self):
        return PersistentDict()

    def getSubscribed(self): 
        return self._subscribed

    def afterAddHook(wrapped_self, object, container):
        wrapped_self.subscribeEvents(update=True)
    afterAddHook = ContextMethod(afterAddHook)

    def beforeDeleteHook(wrapped_self, object, container):
        " be nice, unsub ourselves in this case "
        if wrapped_self._subscribed:
            wrapped_self.unsubscribeEvents()
    beforeDeleteHook = ContextMethod(beforeDeleteHook)

    def clearIndexes(self):
        pass

    def updateIndexes(self):
        pass

    def subscribeEvents(wrapped_self, update=True):
        if wrapped_self._subscribed: 
            raise ValueError, "Already subscribed"
        wrapped_self._subscribed = True
        objectHub = getService(wrapped_self, HubIds) 
        objectHub.subscribe(wrapped_self, Hub.IRegistrationHubEvent)
        objectHub.subscribe(wrapped_self, Hub.IObjectModifiedHubEvent)
        wrapped_self._channel = objectHub
        if update:
            allobjs = objectHub.iterObjectRegistrations()
            # pass through the big list to the indexes. bleah.

    subscribeEvents = ContextMethod(subscribeEvents)

    def unsubscribeEvents(wrapped_self):
        if not wrapped_self._subscribed: 
            raise ValueError, "Already unsubscribed"
        wrapped_self._subscribed = False
        wrapped_self._channel = None
        objectHub = getService(wrapped_self, HubIds) 
        try:
            objectHub.unsubscribe(wrapped_self, Hub.IRegistrationHubEvent)
            objectHub.unsubscribe(wrapped_self, Hub.IObjectModifiedHubEvent)
        except NotFoundError:
            # we're not subscribed. bah.
            pass

    unsubscribeEvents = ContextMethod(unsubscribeEvents)

    def notify(wrapped_self, event):
        "somebody loves us! indexes api sucks, we just pass on the event"

        indexes = wrapped_self.values()
        if (Hub.IObjectRegisteredHubEvent.isImplementedBy(event) or
            Hub.IObjectModifiedHubEvent.isImplementedBy(event)):
            addobj = event.object
            print "got a new object! it's a ", addobj, event.hubid
        elif Hub.IObjectUnregisteredHubEvent.isImplementedBy(event):
            delobj = event.object
            print "got a del object! it's a ", delobj, event.hubid
            [ index.notify(event) for index in indexes ]
        for index in indexes:
            try:
                index.notify(event)
            except:
                pass

    notify = ContextMethod(notify)

    def searchResults(wrapped_self, **searchterms):
        from zodb.btrees.IIBTree import intersection
        pendingResults = None
        for key, value in searchterms.items():
            index = wrapped_self.get(key)
            if not index: 
                raise ValueError, "no such index %s"%(key)
            results = index.search(value)
            if pendingResults is None:
                pendingResults = results
            else:
                pendingResults = intersection(pendingResults, results)
            if not pendingResults:
                # nothing left, short-circuit
                break
        # Next we turn the IISet of hubids into an IISet of objects
        results = [ wrapped_self._channel.getObject(x) for x in pendingResults ]
        return results

        searchResults = ContextMethod(searchResults)

class CatalogUtility(Catalog):
    implements (ILocalUtility)


=== Added File Zope3/src/zope/app/catalog/catalog.txt ===
Adding a site catalog:

The following presupposes that the ObjectHub is installed and the
Registration object for the object hub is installed and active, so the
ObjectHub can pass events on to the catalogs/indexes.

This also presupposes a product called "blog", which allows you to create
content objects - this should apply equally to any other content objects
in your own zope install, presuming they have attributes with values ;)

Add Utility Service to ++etc++site.  Make sure it's marked "Active".

Add a new folder to ++etc++site to keep things clean, called 'searches'.

Go to /++etc++site/searches, the new folder

Add a Catalog, called 'blogCatalog'.  This will take you to a "New Utility
Registration" page.  Enter a name of 'blogCatalog' (this is the name you
will use to find the utility via getUtility()), a provided interface of
"ICatalogQuery" (the interface we implement for a queryable object), a
permission of zope.View, and make it active.

Now we have a utility that implements ICatalogQuery named 'blogCatalog'.
Look in ++etc++site, Utility service, see that it's registered.

Make the blogCatalog have a fieldindex for 'author' - click on the blogCatalog
object, select the "Indexes" tab, and add a Field Index.  Interface can be
zope.interface.Interface, field name should be 'author'.

Add a blog object with an author field to the content space.

Now we add a search interface:

Add the Views Service to ++etc++site.  Make sure it's marked "Active".

Add a module to ++etc++services, called 'module'.
Insert code:

"""
from zope.app import zapi 
from zope.app.catalog.interfaces import ICatalogQuery

class CatalogView: 
    def search(self): 
        request = self.request 
        catalog = zapi.getUtility(self.context,ICatalogQuery, 
            name='blogCatalog') 
        terms = request['terms'] 
        return catalog.searchResults(author=terms)
"""

The "name" in the getUtility call is the name you gave the catalog utility
when you added it.

Go to ++etc++site/searches, add a page folder , 'pageFolder', click on it and
go to Default Registration tab. Dotted name of factory is module.CatalogView

Add a page, 'search' to etc/searches/pageFolder.
This, in it's simplest form, is
'<span tal:replace="view/search"></span>'

You can now access http://$ZOPE:$PORT/search?terms=authorname
Where search is the name of the page in the pageFolder, authorname is the
author name you wish to search for.


=== Added File Zope3/src/zope/app/catalog/configure.zcml ===
<zopeConfigure xmlns="http://namespaces.zope.org/zope"
               xmlns:browser="http://namespaces.zope.org/browser"
>


<content class=".catalog.Catalog">
  <implements 
           interface="zope.app.interfaces.annotation.IAttributeAnnotatable"/>
  <factory id="zope.app.catalog" 
           permission="zope.ManageContent"/>
  <require interface="zope.app.interfaces.catalog.catalog.ICatalogView"
           permission="zope.View" />
  <require interface="zope.app.interfaces.catalog.catalog.ICatalogQuery"
           permission="zope.Public" />
  <require interface="zope.app.interfaces.catalog.catalog.ICatalogEdit"
           permission="zope.ManageContent" />
  <require interface="zope.app.interfaces.container.IContainer"
           permission="zope.ManageContent" />
</content>

<content class=".catalog.CatalogUtility">
  <factory id="zope.app.catalogutility" 
           permission="zope.ManageContent"/>
  <require interface="zope.app.interfaces.catalog.catalog.ICatalogView"
           permission="zope.View"/>
  <require interface="zope.app.interfaces.catalog.catalog.ICatalogQuery"
           permission="zope.View"/>
  <require interface="zope.app.interfaces.catalog.catalog.ICatalogEdit"
           permission="zope.ManageContent"/>
  <require interface="zope.app.interfaces.container.IContainer"
           permission="zope.ManageContent"/>
</content>

</zopeConfigure>