Welcome, guest | Sign In | My Account | Store | Cart

Notice! PyPM is being replaced with the ActiveState Platform, which enhances PyPM’s build and deploy capabilities. Create your free Platform account to download ActivePython or customize Python with the packages you require and get automatic updates.

Download
ActivePython
INSTALL>
pypm install inquant.contentmirror.base

How to install inquant.contentmirror.base

  1. Download and install ActivePython
  2. Open Command Prompt
  3. Type pypm install inquant.contentmirror.base
 Python 2.7Python 3.2Python 3.3
Windows (32-bit)
0.3 Available View build log
Windows (64-bit)
0.3 Available View build log
Mac OS X (10.5+)
0.3 Available View build log
Linux (32-bit)
0.3 Available View build log
Linux (64-bit)
0.3 Available View build log
 
License
GPL
Dependencies
Lastest release
version 0.3 on Jan 5th, 2011

Abstract

This package is provides a way to have a instance of a Plone content mirrored transparently into one or more locations. To do so, we basically need to:

  • locate content to be mirrored on traversal
  • insert the located oject into the traversed context's acquisition chain

To do so, we'll implement a adapter for the Zope 3 traversal mechanism, do a lookup for content to be mirrored in this adapter, and insert the object in the adapted contexts acquisition chain.

Gotchas, Limitations

Because we do add the mirrored object like this, plone will eventually reindex the object multiple times (for example if one edits the object when appearing on a mirrored path). This has the following consequences:

searches -- on a search, the object is listed twice, because it's in the catalog twice. IMHO that's OK, because that's what we actually want, we want the object to appear in two places.

edit -- KSS edit and normal edit of mirrored content works as expected. The content will be reindexed on edit. Users which view the same object visible on another path will see the changes on page reload. Nothing unusual here.

removal, rename -- there it gets tricky. Because the object is in the twice, something like the folder_listing will show the object on mirrored paths despite it's no longer there. All we do is mirroring, not copying. I'll try to handle that with an event subscriber.

UID catalog -- because the object may be catalogued multiple times, the UID catalog will contain the UID multiple times. Furthermore, the catalog brains of the additional entries in the uid catalog will return None for a getObject(). The exact implications of this are unclear to me, that might well be an error in the UID catalog (why does it insert a UID twice in the first place?)

Basic Setup

some needed imports for this doctest:

>>> from zope import interface
>>> from zope import component
>>> from zope.app.testing import ztapi
>>> from zope.publisher.browser import TestRequest

Locating content

To locate content, we provide an interface:

>>> from inquant.contentmirror.base.interfaces import IMirrorContentLocator

We now can define an adapter which is able to locate content from somewhere else:

>>> class TestLocator(object):

System Message: ERROR/3 (<string>, line 72)

Inconsistent literal block quoting.

... def __init__(self, context): ... self.context = context ... def locate( self, name): ... return self.source.get(name)

So basically this adapter just has to return a object for a given name. Let's try that. We need to setup some plone content for this:

>>> _ = self.folder.invokeFactory("Folder", "src")
>>> _ = self.folder.src.invokeFactory("Document", "doc", title="Muha")
>>> _ = self.folder.invokeFactory("Folder", "target")

Now we can provide the adapter:

>>> from Products.ATContentTypes.content.folder import ATFolder
>>> ztapi.provideAdapter(ATFolder,

System Message: ERROR/3 (<string>, line 88)

Inconsistent literal block quoting.

... IMirrorContentLocator, TestLocator)

And look up the adapter:

>>> locator = IMirrorContentLocator(self.folder.target)
>>> locator.source = self.folder.src

now we can fetch the content by name:

>>> locator.locate("doc")

System Message: ERROR/3 (<string>, line 98)

Inconsistent literal block quoting.

<ATDocument at /plone/Members/test_user_1_/src/doc>

Ok, that worked.

Inserting (mirroring) content

Basically what we do is to strip the content to be mirrored from its acquisition context and insert it into the target context's acquisition chain. Lets try that:

>>> from Acquisition import aq_inner, aq_base, aq_chain
>>> obj = self.folder.src.doc
>>> aq_chain(obj)

System Message: ERROR/3 (<string>, line 112)

Inconsistent literal block quoting.

[<ATDocument at /plone/Members/test_user_1_/src/doc>, <ATFolder at /plone/Members/test_user_1_/src>, ...

We see, that obj has a normal acquisition chain, as one would expect. Next, we'll fake the acquisition chain such that obj_mirrored will appear to be below the target folder:

>>> obj_mirrored = aq_base(obj).__of__(self.folder.target)
>>> aq_chain(obj_mirrored)

System Message: ERROR/3 (<string>, line 120)

Inconsistent literal block quoting.

[<ATDocument at /plone/Members/test_user_1_/target/doc>, <ATFolder at /plone/Members/test_user_1_/target>, ...

Yay.

Traversing

Now all wa have to do is to provide a way to hook into Plone's object traversal mechanism, and to alter it such that we can return the mirrored object. The traverser we'll provide uses the IPublishTraverse interface, which is the Zope 3 way of doing it:

>>> from zope.publisher.interfaces import IPublishTraverse

Zope 2 used to use __bobo_traverse__ to traverse objects. Nowadays, traversal is done by providing a adapter to IPublishTraverse. The default traverser is DefaultPublishTraverse, which is defined in the Zope 2 publisher:

>>> from ZPublisher.BaseRequest import DefaultPublishTraverse

This adapter does eventually call __bobo_traverse__. Thus, there's no need to overwrite __bobo_traverse__ anymore. Yay.

Our special adapter for our mirror content woll do the following:

  • try to adapt the traversed context to IMirrorContentLocator, and

System Message: WARNING/2 (<string>, line 147)

Bullet list ends without a blank line; unexpected unindent.

locate a content for the currently traversed name

  • strip the located content object of its acquisition chain and insert it

System Message: WARNING/2 (<string>, line 150)

Bullet list ends without a blank line; unexpected unindent.

into the travesed context's acquisition chain, and return it

Ok, let's try it.

First, we need to create a IPublishTraverse adapter. Note that this is a multi adapter adapting a interface and a IHTTPRequest to IPublishTraverse:

>>> class MirrorTraverse(object):

System Message: ERROR/3 (<string>, line 159)

Inconsistent literal block quoting.

... def __init__(self,context,request): ... self.context = context ... self.request = request ... self.locator = IMirrorContentLocator(context) ... def publishTraverse(self, request, name): ... obj = locator.locate(name) ... return aq_base(aq_inner(obj)).__of__(self.context)

Now, we want to provide the adapter. We do NOT want to overwrite the default behavior, though. That's why we define a marker interface to adapt to IMirrorContentProvider. We provide the adapter:

>>> from inquant.contentmirror.base.interfaces import IMirrorContentProvider
>>> from zope.publisher.interfaces.http import IHTTPRequest
>>> ztapi.provideAdapter(

System Message: ERROR/3 (<string>, line 174)

Inconsistent literal block quoting.

... (IMirrorContentProvider,IHTTPRequest), ... IPublishTraverse, ... MirrorTraverse)

Now we should be able to traverse. To call up the adapter we need a test request, though:

>>> request = TestRequest()
>>> IHTTPRequest.providedBy(request)

System Message: ERROR/3 (<string>, line 183)

Inconsistent literal block quoting.

True

Query the ZCA for the adapter:

>>> traverser = component.getMultiAdapter(

System Message: ERROR/3 (<string>, line 188)

Inconsistent literal block quoting.

... (self.folder.target, request), IPublishTraverse ) Traceback (most recent call last): ... ComponentLookupError: ...

Ouch! Ah, we need to provide the IMirrorContentProvider first:

>>> interface.alsoProvides(self.folder.target, IMirrorContentProvider)
>>> IMirrorContentProvider.providedBy(self.folder.target)

System Message: ERROR/3 (<string>, line 198)

Inconsistent literal block quoting.

True

Try again:

>>> traverser = component.queryMultiAdapter(

System Message: ERROR/3 (<string>, line 203)

Inconsistent literal block quoting.

... (self.folder.target, request), IPublishTraverse )

A-haaa!

Unfortunately, for sake of this test, we need to patch in the source manually. In reality, the locator adapter would of course determine the source itself.:

>>> traverser.locator.source = self.folder.src

Now try to traverse:

>>> traverser.publishTraverse(request, "doc")

System Message: ERROR/3 (<string>, line 216)

Inconsistent literal block quoting.

<ATDocument at /plone/Members/test_user_1_/target/doc>

Yay! Note that the returned object seems to come from the target folder, but it is located in the src folder in reality.

Cleanup

Remove the adapter:

>>> gsm = component.getGlobalSiteManager()
>>> gsm.unregisterAdapter(

System Message: ERROR/3 (<string>, line 228)

Inconsistent literal block quoting.

... MirrorTraverse, ... (IMirrorContentProvider,IHTTPRequest), ... IPublishTraverse) True >>> gsm.unregisterAdapter(TestLocator, (ATFolder,), ... IMirrorContentLocator) True

Docutils System Messages

System Message: ERROR/3 (<string>, line 98); backlink

Unknown target name: "test_user_1".

System Message: ERROR/3 (<string>, line 112); backlink

Unknown target name: "test_user_1".

System Message: ERROR/3 (<string>, line 112); backlink

Unknown target name: "test_user_1".

System Message: ERROR/3 (<string>, line 120); backlink

Unknown target name: "test_user_1".

System Message: ERROR/3 (<string>, line 120); backlink

Unknown target name: "test_user_1".

System Message: ERROR/3 (<string>, line 216); backlink

Unknown target name: "test_user_1".

Subscribe to package updates

Last updated Jan 5th, 2011

Download Stats

Last month:3

What does the lock icon mean?

Builds marked with a lock icon are only available via PyPM to users with a current ActivePython Business Edition subscription.

Need custom builds or support?

ActivePython Enterprise Edition guarantees priority access to technical support, indemnification, expert consulting and quality-assured language builds.

Plan on re-distributing ActivePython?

Get re-distribution rights and eliminate legal risks with ActivePython OEM Edition.