Aliased Content

by Daniel Marks on Apr 16, 2010
Filed Under:

One of the sites I'm currently working on has a content type called a "Report". The main purpose of the site is to display these reports, which are each contained within a folder-ish content type called a "Report Section". There are a number of Report Sections within the site.

This morning I was told that our client wanted each "Report" to be able to live in multiple sections. Aaaargh!

Initially we considered hard-coding the navigation menus so that the (limited number of) reports they wanted in multiple places would appear in the relevant report listings. However, this seemed like too much of a hack and didn't feel like the right solution. What we really wanted was the ability to create an Alias in one place that would point to a piece of content somewhere else, and in-turn behave as though it was the other piece of content.

I looked at the following two products but they weren't quite what I wanted:

Simple Alias collective.alias

Simple Alias was too simple for my needs, and Martin Aspeli's collective.alias did too much.

The Problem

I put together the following requirements:

  • It should be possible for the same piece of content to appear in multiple locations throughout the site
  • To a user, the content should appear to be in each distinct location
  • It should be very easy for content editors to add an Alias to existing content and have everything just work
  • It should only be possible to edit the original content. It shouldn't be possible to edit the same content in multiple locations

The use case is a relatively simple one.

The Solution

The solution I came up with was as follows:

New content type: AliasReport

Reference field: aliasedReport --> Report

Allow AliasReport to be added wherever it was possible to add Report.

Wherever we search the catalog for a Report, search for AliasReport too:

[meta_type=('Report', 'AliasReport')]

Point to a subclass of each View in configure.zcml:

<browser:page
     for="Products.ContentTypes.interfaces.IReport"
     class=".views.Report"
     name="report"
     template="templates/report_view.pt"
     permission="zope2.View"
     />

<browser:page
     for="Products.ContentTypes.interfaces.IAliasReport"
     class=".views.AliasReport"
     name="report"
     template="templates/report_view.pt"
     permission="zope2.View"
     />

Add the view, and define self.report as the actual Report content:

class Report(BrowserView):
   def __init__(self, context, request):
       self.context = context
       self.request = request
       self.report = context

class AliasReport(Report):
   def __init__(self, context, request):
       self.context = context
       self.request = request
       self.report = context.getAliasedReport()

Whenever we refer to an attribute or method of the context in the templates or views, instead refer to view/report. However leave context/absolute_url (and similar calls) so that the content appears in the Aliased place.

<h1 tal:content="context/document_title" />
<a tal:attributes="href string:${context/absolute_url}/buy">Buy this report</a> 

[http://site/section-one/fancy-report/buy]

<h1 tal:content="view/report/document_title" />
<a tal:attributes="href string:${context/absolute_url}/buy">Buy this report</a> 

[http://site/section-eighteen/fancy-report/buy]

And that's it. Content editors can add the Report content type into any Report Sections throughout the site. They can then add any number of Aliased Reports that point to Report content, and everything else will just work.

Extra Bit

In the first version I got working, instead of using view.report, I just redefined the context in the Aliased view class:

def __init__(self, context, request):
   self.context = context.getAliasedReport()

However it didn't feel "right" to be editing the content in the wrong place, so I decided to go with the above solution.

Filed under: ,

Commenting has now closed on this post.