[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>