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

Anthony Baxter anthony@interlink.com.au
Sat, 12 Jul 2003 23:36:27 -0400


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

Added Files:
	__init__.py catalog.py catalog.txt configure.zcml 
Log Message:
Say hello to Mr. Catalog. 

Merge of the melb-2003-content-catalog-branch.

Currently, only FieldIndexes are hooked up to catalogs. Will be 
hooking up TextIndex next. Catalogs can be added as both Utilities 
(see zope/app/catalog/catalog.txt for details on doing this) and in
Content space.


=== Zope3/src/zope/app/catalog/__init__.py 1.1 => 1.2 ===


=== Zope3/src/zope/app/catalog/catalog.py 1.1 => 1.2 ===
--- /dev/null	Sat Jul 12 23:36:26 2003
+++ Zope3/src/zope/app/catalog/catalog.py	Sat Jul 12 23:35:51 2003
@@ -0,0 +1,143 @@
+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.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 IHub
+import zope.app.services.hub as Hub
+
+import time
+
+from zope.app.interfaces.catalog.catalog import ICatalogView, ICatalog
+
+class ResultSet:
+    "Lazily accessed set of objects"
+
+    def __init__(self, hubidset, hub):
+        self.hubidset = hubidset
+        self.hub = hub
+
+    def __len__(self):
+        return len(self.hubidset)
+
+    def __iter__(self):
+        for hubid in self.hubidset:
+            obj = self.hub.getObject(hubid)
+            yield obj
+
+
+class Catalog(Persistent, SampleContainer):
+
+    implements(ICatalog, ISubscriber, IDeleteNotifiable, 
+               IAddNotifiable, IContainer, IAttributeAnnotatable)
+
+    _subscribed = False
+
+    def _newContainerData(self):
+        return PersistentDict()
+
+    def getSubscribed(self): 
+        return self._subscribed
+
+    def afterAddHook(wrapped_self, object, container):
+        wrapped_self.subscribeEvents(update=False)
+    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):
+        for index in self.values():
+	    index.clear()
+
+    def updateIndexes(wrapped_self):
+	eventF = Hub.ObjectRegisteredHubEvent
+        objectHub = getService(wrapped_self, HubIds) 
+	allobj = objectHub.iterObjectRegistrations()
+	for location, hubid, wrapped_object in allobj:
+	    evt = eventF(objectHub, hubid, location, wrapped_object)
+	    for index in wrapped_self.values():
+		index.notify(evt)
+    updateIndexes = ContextMethod(updateIndexes)
+
+    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, IHub.IRegistrationHubEvent)
+        objectHub.subscribe(wrapped_self, IHub.IObjectModifiedHubEvent)
+        if update:
+            wrapped_self.updateIndexes()
+
+    subscribeEvents = ContextMethod(subscribeEvents)
+
+    def unsubscribeEvents(wrapped_self):
+        if not wrapped_self._subscribed: 
+            raise ValueError, "Already unsubscribed"
+        wrapped_self._subscribed = False
+        objectHub = getService(wrapped_self, HubIds) 
+        try:
+            objectHub.unsubscribe(wrapped_self, IHub.IRegistrationHubEvent)
+            objectHub.unsubscribe(wrapped_self, IHub.IObjectModifiedHubEvent)
+        except NotFoundError:
+            # we're not subscribed. bah.
+            pass
+
+    unsubscribeEvents = ContextMethod(unsubscribeEvents)
+
+    def notify(wrapped_self, event):
+        "objecthub is my friend!"
+
+        indexes = wrapped_self.values()
+        if (IHub.IObjectRegisteredHubEvent.isImplementedBy(event) or
+            IHub.IObjectModifiedHubEvent.isImplementedBy(event)):
+            addobj = event.object
+        elif IHub.IObjectUnregisteredHubEvent.isImplementedBy(event):
+            delobj = event.object
+        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 a generator of objects
+        objectHub = getService(wrapped_self, HubIds) 
+        results = ResultSet(pendingResults, objectHub)
+        return results
+    searchResults = ContextMethod(searchResults)
+
+class CatalogUtility(Catalog):
+    implements (ILocalUtility)


=== Zope3/src/zope/app/catalog/catalog.txt 1.1 => 1.2 ===
--- /dev/null	Sat Jul 12 23:36:26 2003
+++ Zope3/src/zope/app/catalog/catalog.txt	Sat Jul 12 23:35:51 2003
@@ -0,0 +1,64 @@
+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.


=== Zope3/src/zope/app/catalog/configure.zcml 1.1 => 1.2 ===
--- /dev/null	Sat Jul 12 23:36:26 2003
+++ Zope3/src/zope/app/catalog/configure.zcml	Sat Jul 12 23:35:51 2003
@@ -0,0 +1,34 @@
+<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>