collective.alias: A transparent alias for Plone
It's evil, but in a good way
On and off over the past couple of weeks, I've been working on a product called collective.alias. This is an attempt to create a transparent alias content type for Plone in the vein of SimpleAlias.
The use case is simple: to be able to have the same content item in more than one place in Plone. In addition, I wanted to:
- Allow different 'display' menu templates to be chosen on the source and alias
- Allow the alias to have its own security, local roles and workflow state
- Allow the alias to have its own portlets
- Support aliasing of folderish content items in a meaningful way
- Support folderish-but-not-folder types like collections or RichDocument
- Transparently support custom views and viewlets
- Avoid the alias blowing up in one's face in real world scenarios. This was the hardest thing.
A few people told me I was crazy to do this. It's been tried before, of course, to varying degrees of success. I had a few reasons, including:
- Stubbornness.
- I wanted to do something a bit outlandish with Dexterity (yes, collective.alias uses Dexterity) to test the framework (it held up quite well).
- I wanted to understand exactly what goes into a CMF content type, warts and all.
- It's possibly a solution for supporting language-independent content in a multilingual setup with top-level language folders (like /en and /de)
- I think it's an important use case for Plone to be able to support, for example to support multi-publishing of content to various sites using something like Lineage.
The current release is 1.0a1. I've tried to make the code as clean as possible, and there's a decent amount of tests. The package has its own buildout to make it easy to run its tests. I hope others who have this need will help develop it further and work out any remaining kinks. With a bit more real-world use, I hope we can call it a beta. :)
How it all works?
In a word: __getattr__.
The Alias type contains a reference to the target (original) content item. It has a custom content class, which derives from a minimum of base classes. It overrides a few properties and methods that couldn't be solved in another way, and then implements __getattr__() to delegate most attributes to the target object.
This is quite simple for attributes, but for methods, there is an interesting problem. Let's say that a view calls context.getText(). A naive __getattr__ could return target.getText, which would then be called. The problem is that self in this method would refer to the target, not the alias, and so any attributes accessed here would be those of the target, not the alias. This means that, among other things, permissions (which are stored in attributes like _View_Permission) will not be treated correctly.
The solution to this problem is to re-bind the method to the alias:
if isinstance(aliased_attr, types.MethodType):
return types.MethodType(aliased_attr.im_func, self, type(self))
All good. Except if that method does super() or calls a method on a base class direclty, like BaseFolder.foo(self), Python will complain.
The way to fix this is rather evil. We have a custom __class__ property that does this:
@property
def __class__(self):
"""/me whistles and looks to the sky whilst walking slowly backwards,
hoping no-one noticed what I just did
"""
klass = getattr(self, '_v_class', None)
if klass is not None:
return klass
aliased = self._target
if aliased is None:
return Alias
self._v_class = klass = new.classobj('Alias', (Alias, aq_base(aliased).__class__), {})
return klass
After that, it gets better:
- There's a custom __providedBy__ adapter, which merges the interfaces provided by the target with those provided by the Alias class itself. This facilitates things like views or viewlets registered for the target's type. It also makes sure that the IAlias marker comes first, so that something registered explicitly for IAlias can override something registered for the target.
- We have a custom IAnnotations adapter that merges annotations from the target and those set directly on the source.
- We override the "content views" viewlet to limit the actions (tabs).
- There are various event handlers which manage an IHasAlias marker interface on the target object. This in turn is used to enable event handlers that re-index the alias when the target is modified, for example.
Anyway, it's been interesting, and I hope it's useful to someone. :)

Previous:
The cost of change
Follow me on 

Good work :)
-Matt