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