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:
parent
50570a44f1
commit
d1110c410e
20 changed files with 1246 additions and 0 deletions
336
z2/intid/README.txt
Normal file
336
z2/intid/README.txt
Normal 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
1
z2/intid/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
#
|
51
z2/intid/base.zcml
Normal file
51
z2/intid/base.zcml
Normal 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
5
z2/intid/browser.py
Normal 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 """
|
49
z2/intid/cmfdirectoryview.zcml
Normal file
49
z2/intid/cmfdirectoryview.zcml
Normal 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
15
z2/intid/configure.zcml
Normal 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
15
z2/intid/install.pt
Normal 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
135
z2/intid/intid.py
Normal 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
115
z2/intid/keyreference.py
Normal 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
13
z2/intid/lsm.py
Normal 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
10
z2/intid/overrides.zcml
Normal 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
24
z2/intid/registrations.pt
Normal 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
133
z2/intid/site.py
Normal 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
18
z2/intid/subscriber.zcml
Normal 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
16
z2/intid/test.zcml
Normal 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
168
z2/intid/tracking.txt
Normal 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...
|
31
z2/intid/unreferenceable.py
Normal file
31
z2/intid/unreferenceable.py
Normal 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
23
z2/intid/utils.py
Normal 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
28
z2/intid/xx_ftests.py
Normal 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
60
z2/intid/xx_tests.py
Normal 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,))
|
Loading…
Add table
Reference in a new issue