You are here: Home Articles collective.alias: A transparent alias for Plone
Navigation
OpenID Log in

 

collective.alias: A transparent alias for Plone

by Martin Aspeli last modified Aug 02, 2009 09:57 AM

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. :)

Document Actions

Good work :)

Posted by http://www.netsight.co.uk/people/matth at Aug 02, 2009 03:45 PM
Good work on this. I tried to do pretty much the same thing a few years back. It drove me nearly insane. It went into production, but there was no way I was going to unleash it on a wider audience. The simple case of a single item wasn't too difficult, but then things like Folders became much harder. Things like breadcrumbs started to make no sense. Also what to do with search results in the catalog? Do you show both items? Do they have the same weight, etc? I also then had the human element of things like a user then changing one item not realising that they were affecting the content item in another place.

-Matt

Good work :)

Posted by Martin Aspeli at Aug 02, 2009 08:33 PM
Right. :)

I think I've got the Folder use case more or less right, at least as right as Zope will let you.

For searching, they are catalogued separately and will both show up. Whether that's an issue or not depends on your use case, I suppose, and how people are searching. Maybe for the default, site-wide search, you could add a parameter that excludes aliases, by indexing the isAlias attribute.

Breadcrumbs should work fine - they're like any other item.

We disallow (or at least try to disallow) editing the alias at all. Only the original item can be edited.

Martin

Good work :)

Posted by http://www.netsight.co.uk/people/matth at Aug 03, 2009 06:01 AM
OK, so for folders... if I view a folderish alias, it is shown in the context of its location, not the original item right? What about content in that folderish alias? What about if there is a folder within that folder? eg root -> folderish-alias -> folder -> folder -> content

Does it all show up in the context of the alias, or do you suddenly end up jumping back to the context of the original item when navigating? The problem I had was whilst in fact Zope's acquisition made this all fairly easy, all of the contents views using the catalog etc never worked quite right.

I still take my hat off to you as being a very brave, and probably insane guy ;)

-Matt

Good work :)

Posted by http://witsch.zitc.de/ at Aug 03, 2009 08:36 AM
yeah, everybody's tried it before i guess (and lost :)). i hope you got it right, martin! :)

Very interresting package

Posted by http://www.openidfrance.fr/encolpe at Aug 03, 2009 02:06 PM
I put it in my shortlist

How about proxy objects?

Posted by http://neaj.myopenid.com/ at Aug 17, 2009 10:19 AM
Sounds like this could serve the proxy object approach to translation as well as the top-level folders one :-)
Plone Book
Professional Plone 4 Development

I am the author of a book called Professional Plone Development. You can read more about it here.

About this site

This Plone site is kindly hosted by: 

Six Feet Up