include slightly modified copy of five.intid

git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@3685 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2010-01-14 12:12:30 +00:00
parent 50570a44f1
commit d1110c410e
20 changed files with 1246 additions and 0 deletions

336
z2/intid/README.txt Normal file
View file

@ -0,0 +1,336 @@
Usage
=====
First, let make sure the ofs utility provides the interface::
>>> from zope.app.intid.interfaces import IIntIds
>>> from five.intid import site
>>> import five.intid.tests as tests
>>> from zope.interface.verify import verifyObject
>>> from zope.component import getAllUtilitiesRegisteredFor
>>> from zope.app.component.hooks import setSite
>>> tests.setUp(self.app)
Content added before the utility won't be registered(until explicitly
called to). We'll set some up now for later
>>> tests.manage_addSimpleContent(self.folder, 'mycont1', "My Content")
>>> content1 = self.folder.mycont1
`five.intid.site` has convenience functions for adding, get and
removing an IntId utility: `add_intid`, `get_intid`, `del_intid`.
You can install the utility in a specific location::
>>> site.add_intids(self.folder)
>>> folder_intids = site.get_intids(self.folder)
>>> verifyObject(IIntIds, folder_intids)
True
You can tell `add_intids` to find the site root, and install there.
It will be available everywhere::
>>> site.add_intids(self.folder, findroot=True)
>>> root_intids = site.get_intids(self.app)
>>> root_intids
<...IntIds ...>
>>> folder_intids is root_intids
False
And finally, do a remove::
>>> site.del_intids(self.folder, findroot=True)
>>> site.get_intids(self.app)
Traceback (most recent call last):
...
ComponentLookupError: (<InterfaceClass zope.app.intid.interfaces.IIntIds>, '')
Before we look at intid events, we need to set the traversal
hook. Once we have done this, when we ask for all registered Intids,
we will get the utility from test folder::
>>> setSite(self.folder)
>>> tuple(getAllUtilitiesRegisteredFor(IIntIds))
(<...IntIds ...>,)
When we add content, event will be fired to add keyreference for said
objects the utilities (currently, our content and the utility are
registered)::
>>> from five.intid.lsm import USE_LSM
>>> tests.manage_addSimpleContent(self.folder, 'mycont2', "My Content")
>>> content2 = self.folder.mycont2
>>> intid = site.get_intids(self.folder)
>>> if USE_LSM:
... len(intid.items()) == 1
... else:
... len(intid.items()) == 2
True
Pre-existing content will raise a keyerror if passed to the intid
utility::
>>> intid.getId(content1)
Traceback (most recent call last):
...
KeyError: <SimpleContent at /test_folder_1_/mycont1>
We can call the keyreferences, and get the objects back::
>>> if USE_LSM:
... intid.items()[0][1]()
... else:
... intid.items()[1][1]()
<SimpleContent at /test_folder_1_/mycont2>
we can get an object's `intid` from the utility like so::
>>> ob_id = intid.getId(content2)
and get an object back like this::
>>> intid.getObject(ob_id)
<SimpleContent at /test_folder_1_/mycont2>
these objects are aquisition wrapped on retrieval::
>>> type(intid.getObject(ob_id))
<type 'ImplicitAcquirerWrapper'>
We can even turn an unwrapped object into a wrapped object by
resolving it from it's intid, also the intid utility should work
even if it is unwrapped::
>>> from Acquisition import aq_base
>>> resolved = intid.getObject(intid.getId(aq_base(content2)))
>>> type(resolved)
<type 'ImplicitAcquirerWrapper'>
>>> unwrapped = aq_base(intid)
>>> unwrapped.getObject(ob_id) == resolved
True
>>> unwrapped.getId(content2) == ob_id
True
When an object is added or removed, subscribers add it to the intid
utility, and fire an event is fired
(zope.app.intid.interfaces.IIntIdAddedEvent,
zope.app.intid.interfaces.IIntIdRemovedEvent respectively).
`five.intid` hooks up these events to redispatch as object events. The
tests hook up a simple subscriber to verify that the intid object
events are fired (these events are useful for catalogish tasks).
>>> tests.NOTIFIED[0]
'<SimpleContent at mycont2> <...IntIdAddedEvent instance at ...'
Registering and unregistering objects does not fire these events::
>>> tests.NOTIFIED[0] = "No change"
>>> uid = intid.register(content1)
>>> intid.getObject(uid)
<SimpleContent at /test_folder_1_/mycont1>
>>> tests.NOTIFIED[0]
'No change'
>>> intid.unregister(content1)
>>> intid.getObject(uid)
Traceback (most recent call last):
...
KeyError: ...
>>> tests.NOTIFIED[0]
'No change'
Renaming an object should not break the rewrapping of the object:
>>> self.setRoles(['Manager'])
>>> folder.mycont2.meta_type = 'Folder' # We need a metatype to move
>>> folder.manage_renameObject('mycont2','mycont_new')
>>> moved = intid.getObject(ob_id)
>>> moved
<SimpleContent at /test_folder_1_/mycont_new>
Nor should moving it:
>>> from OFS.Folder import manage_addFolder
>>> manage_addFolder(self.folder, 'folder2', "folder 2")
>>> cut = folder.manage_cutObjects(['mycont_new'])
>>> ignore = folder.folder2.manage_pasteObjects(cut)
>>> moved = intid.getObject(ob_id)
>>> moved
<SimpleContent at /test_folder_1_/folder2/mycont_new>
>>> moved.aq_parent
<Folder at /test_folder_1_/folder2>
Let's move it back:
>>> cut = folder.folder2.manage_cutObjects(['mycont_new'])
>>> ignore = folder.manage_pasteObjects(cut)
>>> folder.manage_renameObject('mycont_new','mycont2')
We can create an object without acquisition so we can be able to
add intid to it :
>>> from five.intid.tests import DemoPersistent
>>> demo1 = DemoPersistent()
>>> demo1.__parent__ = self.app
>>> from zope.event import notify
>>> from zope.app.container.contained import ObjectAddedEvent
>>> notify(ObjectAddedEvent(demo1))
>>> nowrappid = intid.getId(demo1)
>>> demo1 == intid.getObject(nowrappid)
True
This is a good time to take a look at keyreferences, the core part of
this system.
Key References in Zope2
=======================
Key references are hashable objects returned by IKeyReference. The
hash produced is a unique identifier for whatever the object is
referencing(another zodb object, a hook for sqlobject, etc).
object retrieval in intid occurs by calling a key reference. This
implementation is slightly different than the zope3 due to
acquisition.
The factories returned by IKeyReference must persist and this dictates
being especially careful about references to acquisition wrapped
objects as well as return acq wrapped objects as usually expected in
zope2.
>>> ref = intid.refs[ob_id]
>>> ref
<five.intid.keyreference.KeyReferenceToPersistent object at ...>
The reference object holds a reference to the unwrapped target object
and a property to fetch the app(also, not wrapped ie <type 'ImplicitAcquirerWrapper'>)::
>>> ref.object
<SimpleContent at mycont2>
>>> type(ref.object)
<class 'Products.Five.tests.testing.simplecontent.SimpleContent'>
>>> ref.root
<Application at >
Calling the reference object (or the property wrapped_object) will
return an acquisition object wrapped object (wrapped as it was
created)::
>>> ref.wrapped_object == ref()
True
>>> ref()
<SimpleContent at /test_folder_1_/mycont2>
>>> type(ref())
<type 'ImplicitAcquirerWrapper'>
The resolution mechanism tries its best to end up with the current
request at the end of the acquisition chain, just as it would be
under noraml circumstances::
>>> ref.wrapped_object.aq_chain[-1]
<ZPublisher.BaseRequest.RequestContainer object at ...>
The hash calculation is a combination of the database name and the
object's persistent object id(oid)::
>>> ref.dbname
'unnamed'
>>> hash((ref.dbname, ref.object._p_oid)) == hash(ref)
True
>>> tests.tearDown()
Acquisition Loops
=================
five.intid detects loops in acquisition chains in both aq_parent and
__parent__.
Setup a loop::
>>> import Acquisition
>>> class Acq(Acquisition.Acquirer): pass
>>> foo = Acq()
>>> foo.bar = Acq()
>>> foo.__parent__ = foo.bar
Looking for the root on an object with an acquisition loop will raise
an error::
>>> from five.intid import site
>>> site.get_root(foo.bar)
Traceback (most recent call last):
...
AttributeError: __parent__ loop found
Looking for the connection on an object with an acquisition loop will
simply return None::
>>> from five.intid import keyreference
>>> keyreference.connectionOfPersistent(foo.bar)
Unreferenceable
===============
Some objects implement IPersistent but are never actually persisted, or
contain references to such objects. Specifically, CMFCore directory views
contain FSObjects that are never persisted, and DirectoryViewSurrogates
that contain references to such objects. Because FSObjects are never actually
persisted, five.intid's assumption that it can add a
For such objects, the unreferenceable module provides no-op subcribers and
adapters to omit such objects from five.intid handling.
>>> from zope import interface, component
>>> from five.intid import unreferenceable
>>> from Products.CMFCore import FSPythonScript
>>> foo = FSPythonScript.FSPythonScript('foo', __file__)
>>> self.app._setObject('foo', foo)
'foo'
>>> keyref = unreferenceable.KeyReferenceNever(self.app.foo)
Traceback (most recent call last):
...
NotYet
>>> foo in self.app._p_jar._registered_objects
False
Objects with no id
==================
It is possible to attempt to get a key reference for an object that has not
yet been properly added to a container, but would otherwise have a path.
In this case, we raise the NotYet exception to let the calling code defer
as necessary, since the key reference would otherwise resolve the wrong
object (the parent, to be precise) from an incorrect path.
>>> from zope.app.keyreference.interfaces import IKeyReference
>>> from five.intid.keyreference import KeyReferenceToPersistent
>>> from zope.component import provideAdapter
>>> provideAdapter(KeyReferenceToPersistent)
>>> from OFS.SimpleItem import SimpleItem
>>> item = SimpleItem('').__of__(self.folder)
>>> '/'.join(item.getPhysicalPath())
'/test_folder_1_/'
>>> IKeyReference(item)
Traceback (most recent call last):
...
NotYet: <SimpleItem at >

1
z2/intid/__init__.py Normal file
View file

@ -0,0 +1 @@
#

51
z2/intid/base.zcml Normal file
View file

@ -0,0 +1,51 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<include package="zope.app.keyreference" />
<browser:page
name="index.html"
for="zope.app.intid.interfaces.IIntIds"
permission="five.ManageSite"
class=".browser.IntIdsView"
template="registrations.pt"
/>
<browser:page
name="populate"
for="zope.app.intid.interfaces.IIntIds"
permission="five.ManageSite"
class=".browser.IntIdsView"
attribute="populate"
/>
<browser:page
for="OFS.interfaces.IFolder"
class=".site.FiveIntIdsInstall"
permission="five.ManageSite"
template="install.pt"
name="install-intids.html"
/>
<browser:page
for="OFS.interfaces.IApplication"
class=".site.FiveIntIdsInstall"
permission="five.ManageSite"
template="install.pt"
name="install-intids.html"
/>
<subscriber
for="zope.app.intid.interfaces.IIntIdAddedEvent"
handler="zope.component.event.objectEventNotify"
/>
<subscriber
for="zope.app.intid.interfaces.IIntIdRemovedEvent"
handler="zope.component.event.objectEventNotify"
/>
<includeOverrides file="overrides.zcml" package="cybertools.z2.intid"/>
</configure>

5
z2/intid/browser.py Normal file
View file

@ -0,0 +1,5 @@
from zope.app.intid.browser import IntIdsView
from Products.Five import BrowserView
class FiveIntIdsView(IntIdsView, BrowserView):
""" utility view for five """

View file

@ -0,0 +1,49 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:zcml="http://namespaces.zope.org/zcml"
zcml:condition="installed Products.CMFCore">
<!-- DirectoryViews -->
<subscriber
handler=".unreferenceable.addIntIdSubscriber"
for="Products.CMFCore.interfaces.IDirectoryView
zope.app.container.interfaces.IObjectAddedEvent"
/>
<subscriber
handler=".unreferenceable.removeIntIdSubscriber"
for="Products.CMFCore.interfaces.IDirectoryView
zope.app.container.interfaces.IObjectRemovedEvent"
/>
<subscriber
handler=".unreferenceable.moveIntIdSubscriber"
for="Products.CMFCore.interfaces.IDirectoryView
zope.app.container.interfaces.IObjectMovedEvent"
/>
<adapter
factory=".unreferenceable.KeyReferenceNever"
for="Products.CMFCore.interfaces.IDirectoryView"
trusted="y"
/>
<!-- FSObject -->
<subscriber
handler=".unreferenceable.addIntIdSubscriber"
for="Products.CMFCore.FSObject.FSObject
zope.app.container.interfaces.IObjectAddedEvent"
/>
<subscriber
handler=".unreferenceable.removeIntIdSubscriber"
for="Products.CMFCore.FSObject.FSObject
zope.app.container.interfaces.IObjectRemovedEvent"
/>
<subscriber
handler=".unreferenceable.moveIntIdSubscriber"
for="Products.CMFCore.FSObject.FSObject
zope.app.container.interfaces.IObjectMovedEvent"
/>
<adapter
factory=".unreferenceable.KeyReferenceNever"
for="Products.CMFCore.FSObject.FSObject"
trusted="y"
/>
</configure>

15
z2/intid/configure.zcml Normal file
View file

@ -0,0 +1,15 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"
xmlns:zcml="http://namespaces.zope.org/zcml">
<!-- All Core Configuration -->
<include file="base.zcml" package="cybertools.z2.intid" />
<!-- Event Subscribers -->
<include file="subscriber.zcml" package="cybertools.z2.intid" />
<!-- CMFCore directoryview exceptions -->
<!--<include file="cmfdirectoryview.zcml" />-->
</configure>

15
z2/intid/install.pt Normal file
View file

@ -0,0 +1,15 @@
<html>
<head>
</head>
<body>
<div tal:condition="view/installed">the intids is installed</div>
<div>
<form method=POST tal:condition="not:view/installed">
<input type="submit"
value="Install intid utility"
name="install"
style="border: 2px solid #F99; background-color: red"/>
</form>
</div>
</body>
</html>

135
z2/intid/intid.py Normal file
View file

@ -0,0 +1,135 @@
from Globals import InitializeClass
from persistent import Persistent
from Acquisition import Explicit
from zope.app import zapi
from zope.app.intid import IntIds as z3IntIds
from zope.app.intid.interfaces import IIntIds
from zope.app.intid.interfaces import IntIdAddedEvent, IntIdRemovedEvent
from zope.app.container.interfaces import IObjectAddedEvent, IObjectRemovedEvent
from zope.app.keyreference.interfaces import IKeyReference, NotYet
from zope.event import notify
from zope.interface import implements
_marker = []
class IntIds(z3IntIds):
""" zope2ish intid utility """
implements(IIntIds)
meta_type="IntId Utility"
def __init__(self, id_=IIntIds.__name__):
self.id = self.__name__ = id_
super(IntIds, self).__init__()
def getId(self, ob=_marker):
# Compatibility with SimpleItem
if ob is _marker:
return self.__name__
return z3IntIds.getId(self, ob)
def register(self, ob):
key = IKeyReference(ob)
res = self.ids.get(key, None)
if res is not None:
return res
uid = self._generateId()
self.refs[uid] = key
self.ids[key] = uid
return uid
def unregister(self, ob):
key = IKeyReference(ob, None)
if key is None:
return
uid = self.ids[key]
del self.refs[uid]
del self.ids[key]
InitializeClass(IntIds)
class OFSIntIds(IntIds, Explicit):
"""Mixin acquisition for non-lsm sites"""
def manage_fixupOwnershipAfterAdd(self):
pass
def wl_isLocked(self):
return False
InitializeClass(OFSIntIds)
# @@ these are "sloppy" subscribers that let objects that have not
# been properly added to the db by
def addIntIdSubscriber(ob, event):
"""A subscriber to ObjectAddedEvent
Registers the object added in all unique id utilities and fires
an event for the catalogs.
"""
utilities = tuple(zapi.getAllUtilitiesRegisteredFor(IIntIds))
if utilities: # assert that there are any utilites
key = None
try:
key = IKeyReference(ob, None)
except NotYet:
pass
# Register only objects that adapt to key reference
if key is not None:
for utility in utilities:
utility.register(key)
# Notify the catalogs that this object was added.
notify(IntIdAddedEvent(ob, event))
def removeIntIdSubscriber(ob, event):
"""A subscriber to ObjectRemovedEvent
Removes the unique ids registered for the object in all the unique
id utilities.
"""
utilities = tuple(zapi.getAllUtilitiesRegisteredFor(IIntIds))
if utilities:
key = None
try:
key = IKeyReference(ob, None)
except NotYet: # @@ temporary fix
pass
# Register only objects that adapt to key reference
if key is not None:
# Notify the catalogs that this object is about to be removed.
notify(IntIdRemovedEvent(ob, event))
for utility in utilities:
try:
utility.unregister(key)
except KeyError:
pass
def moveIntIdSubscriber(ob, event):
"""A subscriber to ObjectMovedEvent
Updates the stored path for the object in all the unique
id utilities.
"""
if IObjectRemovedEvent.providedBy(event) or \
IObjectAddedEvent.providedBy(event):
return
utilities = tuple(zapi.getAllUtilitiesRegisteredFor(IIntIds))
if utilities:
key = None
try:
key = IKeyReference(ob, None)
except NotYet: # @@ temporary fix
pass
# Update objects that adapt to key reference
if key is not None:
for utility in utilities:
try:
uid = utility.getId(ob)
utility.refs[uid] = key
utility.ids[key] = uid
except KeyError:
pass

115
z2/intid/keyreference.py Normal file
View file

@ -0,0 +1,115 @@
from Acquisition import aq_base, aq_chain
from ZODB.interfaces import IConnection
from ZPublisher.BaseRequest import RequestContainer
from zExceptions import NotFound
from persistent import IPersistent
from zope.component import adapter, adapts
from zope.app.component.hooks import getSite
from zope.interface import implements, implementer
from zope.app.keyreference.interfaces import IKeyReference, NotYet
from zope.app.keyreference.persistent import KeyReferenceToPersistent
from site import get_root, aq_iter
from zope.app.container.interfaces import IObjectAddedEvent
@adapter(IPersistent)
@implementer(IConnection)
def connectionOfPersistent(obj):
""" zope2 cxn fetcher for wrapped items """
for parent in aq_iter(obj):
conn = getattr(parent, '_p_jar', None)
if conn is not None:
return conn
@adapter(IPersistent, IObjectAddedEvent)
def add_object_to_connection(ob, event):
"""Pre-add new objects to their persistence connection"""
connection = IConnection(ob, None)
if None is not connection:
connection.add(aq_base(ob))
class KeyReferenceToPersistent(KeyReferenceToPersistent):
"""a zope2ish implementation of keyreferences that unwraps objects
that have Acquisition wrappers
These references compare by _p_oids of the objects they reference.
@@ cache IConnection as a property and volative attr?
"""
implements(IKeyReference)
adapts(IPersistent)
key_type_id = 'five.intid.keyreference'
def __init__(self, wrapped_obj):
# make sure our object is wrapped by containment only
try:
self.path = '/'.join(wrapped_obj.getPhysicalPath())
except AttributeError:
self.path = None
# If the path ends with /, it means the object had an empty id.
# This means it's not yet added to the container, and so we have
# to defer.
if self.path is not None and self.path.endswith('/'):
raise NotYet(wrapped_obj)
self.object = aq_base(wrapped_obj)
connection = IConnection(wrapped_obj, None)
if not getattr(self.object, '_p_oid', None):
if connection is None:
raise NotYet(wrapped_obj)
connection.add(self.object)
try:
self.root_oid = get_root(wrapped_obj)._p_oid
except AttributeError:
# If the object is unwrapped we can try to use the Site from the
# threadlocal as our acquisition context, hopefully it's not
# something odd.
self.root_oid = get_root(getSite())._p_oid
self.oid = self.object._p_oid
self.dbname = connection.db().database_name
@property
def root(self):
return IConnection(self.object)[self.root_oid]
@property
def wrapped_object(self):
if self.path is None:
return self.object
try:
obj = self.root.unrestrictedTraverse(self.path)
except (NotFound, AttributeError,):
return self.object
chain = aq_chain(obj)
# Try to ensure we have a request at the acquisition root
# by using the one from getSite
if not len(chain) or not isinstance(chain[-1], RequestContainer):
site = getSite()
site_chain = aq_chain(site)
if len(site_chain) and isinstance(site_chain[-1],
RequestContainer):
req = site_chain[-1]
new_obj = req
# rebuld the chain with the request at the bottom
for item in reversed(chain):
new_obj = aq_base(item).__of__(new_obj)
obj = new_obj
return obj
def __call__(self):
return self.wrapped_object
def __hash__(self):
return hash((self.dbname,
self.object._p_oid,
))
def __cmp__(self, other):
if self.key_type_id == other.key_type_id:
return cmp((self.dbname,self.oid), (other.dbname, other.oid))
return cmp(self.key_type_id, other.key_type_id)

13
z2/intid/lsm.py Normal file
View file

@ -0,0 +1,13 @@
"""
five.localsitemanager/PersistentComponents compatibility support.
"""
try:
from five.localsitemanager import (
make_objectmanager_site as make_site, )
except ImportError:
USE_LSM = False
from Products.Five.site.localsite import (
enableLocalSiteHook as make_site, )
else:
USE_LSM = True

10
z2/intid/overrides.zcml Normal file
View file

@ -0,0 +1,10 @@
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
factory=".keyreference.KeyReferenceToPersistent"
trusted="y"
/>
<adapter factory=".keyreference.connectionOfPersistent" />
</configure>

24
z2/intid/registrations.pt Normal file
View file

@ -0,0 +1,24 @@
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
<div metal:define-macro="body">
<p i18n:translate=""><span i18n:name="count"
tal:replace="view/len" /> objects</p>
<table id="sortable" class="listing" summary="Content listing"
tal:condition="request/testing|nothing"
i18n:attributes="summary">
<tr><th i18n:translate="">ID</th><th
i18n:translate="">Object</th></tr>
<tr tal:repeat="row view/_items">
<td tal:content="python:row[0]" />
<td><a tal:content="python:row[1]"
tal:attributes="href python:row[1]" /></td>
</tr>
</table>
</div>
</div>
</body>
</html>

133
z2/intid/site.py Normal file
View file

@ -0,0 +1,133 @@
from Acquisition import aq_base, aq_chain
from Products.Five import BrowserView
from zope.app.intid.interfaces import IIntIds
from zope.app.component.hooks import setSite, setHooks
from zope.app.component.interfaces import ISite
from zope.component.interfaces import ComponentLookupError
from zope.component import getUtility, getSiteManager
from OFS.interfaces import IApplication
from intid import IntIds, OFSIntIds
from lsm import make_site, USE_LSM
from utils import aq_iter
class FiveIntIdsInstall(BrowserView):
@property
def context(self):
return self._context[0]
def __init__(self, context, request):
self._context = context,
self.request = request
doinstall = self.request.get('install', None)
if doinstall:
self.install()
def install(self):
add_intids(self.context, findroot=False)
@property
def installed(self):
installed = False
try:
intids = getUtility(IIntIds)
if intids is not None:
if USE_LSM:
sm = self.context.getSiteManager()
if 'intids' in sm.objectIds():
installed = True
else:
installed = True
except ComponentLookupError, e:
pass
return installed
def initializeSite(site, sethook=False, **kw):
make_site(site)
if sethook:
setHooks()
setSite(site)
def xx_get_root(app):
for parent in aq_iter(app, error=AttributeError):
if IApplication.providedBy(parent):
return parent
raise AttributeError, 'No application found'
def get_root(obj):
for p in aq_chain(obj):
if IApplication.providedBy(p):
return p
raise AttributeError, 'No application found'
def addUtility(site, interface, klass, name='', ofs_name='', findroot=True):
"""
add local utility in zope2
"""
app = site
if findroot:
app = get_root(site)
# If we have the zope Application and the utility is not yet
# registered, then register it.
assert app is not None, TypeError("app is None")
if not ISite.providedBy(app):
initializeSite(app, sethook=False)
sm = app.getSiteManager()
obj = None
if USE_LSM:
# Try to get the utility from OFS directly in case it is
# stored, but not registered
ofs_name = ofs_name or name
obj = getattr(aq_base(sm), ofs_name, None)
if sm.queryUtility(interface,
name=name,
default=None) is None:
# Register the utility if it is not yet registered
if obj is None:
if name:
obj = klass(name)
else:
obj = klass()
if USE_LSM:
sm.registerUtility(provided=interface, component=obj,
name=name)
else:
#sm.registerUtility(interface, obj, name=name) ###hm
sm.registerUtility(provided=interface, component=obj,
name=name)
elif USE_LSM and obj is None:
# Get the utility if registered, but not yet stored in the LSM
obj = sm.queryUtility(interface, name=name)
# Make sure we store the utility permanently in the OFS so we don't loose
# intids on uninstall
if USE_LSM:
if ofs_name and ofs_name not in sm.objectIds():
sm._setObject(ofs_name, aq_base(obj), set_owner=False,
suppress_events=True)
from intid import IIntIds, OFSIntIds
from zope.component import getUtility
def add_intids(site, findroot=False):
if USE_LSM:
klass = IntIds
else:
klass = OFSIntIds
addUtility(site, IIntIds, klass, ofs_name='intids', findroot=findroot)
def get_intids(context=None):
return getUtility(IIntIds, context=context)
def del_intids(context=None, findroot=False):
if findroot:
context = get_root(context)
utility = get_intids(context)
if USE_LSM:
getSiteManager(context).unregisterUtility(component=utility,
provided=IIntIds)
else:
parent = utility.aq_parent
parent.manage_delObjects([utility.__name__])

18
z2/intid/subscriber.zcml Normal file
View file

@ -0,0 +1,18 @@
<configure xmlns="http://namespaces.zope.org/zope">
<subscriber
handler=".intid.addIntIdSubscriber"
for="persistent.IPersistent
zope.app.container.interfaces.IObjectAddedEvent"
/>
<subscriber
handler=".intid.removeIntIdSubscriber"
for="persistent.IPersistent
zope.app.container.interfaces.IObjectRemovedEvent"
/>
<subscriber
handler=".intid.moveIntIdSubscriber"
for="OFS.interfaces.ITraversable
zope.app.container.interfaces.IObjectMovedEvent"
/>
</configure>

16
z2/intid/test.zcml Normal file
View file

@ -0,0 +1,16 @@
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<include file="subscriber.zcml" />
<include file="cmfdirectoryview.zcml" />
<include package="zope.app.keyreference" />
<subscriber
handler=".tests.setNotified"
for="zope.app.intid.interfaces.IIntIdAddedEvent"
/>
<subscriber
handler=".tests.setNotified"
for="zope.app.intid.interfaces.IIntIdRemovedEvent"
/>
<includeOverrides file="overrides.zcml" package="five.intid"/>
</configure>

168
z2/intid/tracking.txt Normal file
View file

@ -0,0 +1,168 @@
Tracking Object Additions, Deletions, and Moves
===============================================
Unique ID utilities track object add moves. Let's look at an
example. First, we'll create a unique Id utility:
>>> print http(r"""
... POST /++etc++site/default/@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Referer: http://localhost:8081/++etc++site/default/@@contents.html
...
... type_name=BrowserAdd__zope.app.intid.IntIds&new_value=""")
HTTP/1.1 303 See Other
...
Location: http://localhost/++etc++site/default/IntIds/@@registration.html
...
>>> print http(r"""
... POST /++etc++site/default/IntIds/addRegistration.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Referer: http://localhost:8081/++etc++site/default/IntIds/
... Content-Type: multipart/form-data; boundary=----------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
...
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.name"
...
... IntIds
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.provided"
...
... zope.app.intid.interfaces.IIntIds
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.provided-empty-marker"
...
... 1
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.status"
...
... Active
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.status-empty-marker"
...
... 1
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.permission"
...
...
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="field.permission-empty-marker"
...
... 1
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Add
... ------------CedQTrEQIEPbgfYhvcITAhQi2aJdgu3tYfJ0WYQmkpLQTt6OTOpd5GJ--
... """)
HTTP/1.1 303 See Other
...
Location: @@SelectedManagementView.html
...
Now, we'll add a few folders:
>>> print http(r"""
... POST /@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Content-Length: 64
... Content-Type: application/x-www-form-urlencoded
...
... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f1""")
HTTP/1.1 303 See Other
...
>>> print http(r"""
... POST /f1/@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Content-Length: 64
... Content-Type: application/x-www-form-urlencoded
...
... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f1""")
HTTP/1.1 303 See Other
...
>>> print http(r"""
... POST /f1/@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Content-Length: 64
... Content-Type: application/x-www-form-urlencoded
...
... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f2""")
HTTP/1.1 303 See Other
...
>>> print http(r"""
... POST /f1/f1/@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Content-Length: 64
... Content-Type: application/x-www-form-urlencoded
...
... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f1""")
HTTP/1.1 303 See Other
...
Now, if we look at the index page for the unique id utility, we'll see
the objects we added:
>>> print http(r"""
... GET /++etc++site/default/IntIds/@@index.html?testing=1 HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Referer: http://localhost:8081/++etc++site/default/@@contents.html
... """)
HTTP/1.1 200 Ok
...4 objects...
.../f1...
.../f1/f1...
.../f1/f2...
.../f1/f1/f1...
If we move the top object:
>>> print http(r"""
... POST /@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Content-Length: 40
... Content-Type: application/x-www-form-urlencoded
... Referer: http://localhost:8081/@@contents.html
...
... new_value%3Alist=f2&rename_ids%3Alist=f1""")
HTTP/1.1 303 See Other
...
We'll see that reflected in the utility:
>>> print http(r"""
... GET /++etc++site/default/IntIds/@@index.html?testing=1 HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Referer: http://localhost:8081/++etc++site/default/@@contents.html
... """)
HTTP/1.1 200 Ok
...4 objects...
.../f2...
.../f2/f1...
.../f2/f2...
.../f2/f1/f1...
And if we delete the top object:
>>> print http(r"""
... POST /@@contents.html HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Content-Length: 44
... Content-Type: application/x-www-form-urlencoded
... Referer: http://localhost:8081/@@contents.html
...
... ids%3Alist=f2&container_delete_button=Delete""")
HTTP/1.1 303 See Other
...
all of the objects will go away:
>>> print http(r"""
... GET /++etc++site/default/IntIds/@@index.html?testing=1 HTTP/1.1
... Authorization: Basic mgr:mgrpw
... Referer: http://localhost:8081/++etc++site/default/@@contents.html
... """)
HTTP/1.1 200 Ok
...0 objects...

View file

@ -0,0 +1,31 @@
# Sometimes persistent classes are never meant to be persisted. The most
# common example are CMFCore directory views and filesystem objects.
# Register specific handlers that are no-ops to circumvent
from zope.interface import implements
from zope.app.keyreference.interfaces import IKeyReference, NotYet
def addIntIdSubscriber(ob, event):
return
def removeIntIdSubscriber(ob, event):
return
def moveIntIdSubscriber(ob, event):
return
class KeyReferenceNever(object):
"""A keyreference that is never ready"""
implements(IKeyReference)
key_type_id = 'five.intid.cmfexceptions.keyreference'
def __init__(self, obj):
raise NotYet()
def __call__(self):
return None
def __cmp__(self, other):
if self.key_type_id == other.key_type_id:
return cmp(self, other)
return cmp(self.key_type_id, other.key_type_id)

23
z2/intid/utils.py Normal file
View file

@ -0,0 +1,23 @@
from Acquisition import aq_base, aq_inner, IAcquirer
def aq_iter(obj, error=None):
if not (IAcquirer.providedBy(obj) or hasattr(obj, '__parent__')):
raise TypeError("%s not acquisition wrapped" %obj)
# adapted from alecm's 'listen'
seen = set()
# get the inner-most wrapper (maybe save some cycles, and prevent
# bogus loop detection)
cur = aq_inner(obj)
while cur is not None:
yield cur
seen.add(id(aq_base(cur)))
cur = getattr(cur, 'aq_parent', getattr(cur, '__parent__', None))
if id(aq_base(cur)) in seen:
# avoid loops resulting from acquisition-less views
# whose __parent__ points to
# the context whose aq_parent points to the view
if error is not None:
raise error, '__parent__ loop found'
break

28
z2/intid/xx_ftests.py Normal file
View file

@ -0,0 +1,28 @@
##############################################################################
#
# Copyright (c) 2004 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Int Id Utility Functional Tests
$Id$
"""
import unittest
from zope.app.testing import functional
def test_suite():
return unittest.TestSuite((
functional.FunctionalDocFileSuite('tracking.txt'),
))
if __name__ == '__main__':
unittest.main(defaultTest='test_suite')

60
z2/intid/xx_tests.py Normal file
View file

@ -0,0 +1,60 @@
import doctest
from zope.app.testing import placelesssetup
from persistent import Persistent
from zope.app.component.hooks import setHooks, setSite
from Products.Five.tests.testing.simplecontent import (
SimpleContent,
ISimpleContent,
manage_addSimpleContent,
)
from Products.Five import zcml
from five.intid.lsm import USE_LSM
from five.intid import site
optionflags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
class DemoPersistent(Persistent):
""" Demo persistent object """
test = 'test object'
__name__ = 'Test Object'
NOTIFIED=[None]
def setNotified(event):
NOTIFIED[0] = "%s %s" %(event.object, event)
def setUp(app):
# enable zcml and site hooks
placelesssetup.setUp()
import Products.Five
from five import intid
zcml.load_config('meta.zcml', Products.Five)
zcml.load_config('configure.zcml', Products.Five)
zcml.load_config('test.zcml', intid)
if not USE_LSM:
# monkey in our hooks
from Products.Five.site.metaconfigure import classSiteHook
from Products.Five.site.localsite import FiveSite
from zope.interface import classImplements
from zope.app.component.interfaces import IPossibleSite
klass = app.__class__
classSiteHook(klass, FiveSite)
classImplements(klass, IPossibleSite)
setHooks()
def tearDown():
placelesssetup.tearDown()
def test_suite():
import unittest
from Testing.ZopeTestCase import FunctionalDocFileSuite
from zope.testing.doctest import DocTestSuite
integration = FunctionalDocFileSuite(
'README.txt',
package='five.intid',
optionflags=optionflags
)
return unittest.TestSuite((integration,))