Here's a reasonable requirement of a website:
Pages can have attachments. Attachments are files associated with a page. They're listed when viewing the page.
In Plone, the upcoming Deco page layout system promises to make such pages very easy to create. But what are the options right now? If we're building a new Plone site today, how do we model attachments in a clean, easy-to-implement way that won't leave cruft when we go Deco?
Page attachment basics
Page attachments can help to settle visitors on pages with lots of downloadable files. The idea is that when a file is called an 'attachment' site visitors draw a parallel with the same concept in email. They recognise that the file belongs to its page and is an essential part of the page's content.
Of course the web has no notion of attachment. In HTML an attachment is a link to a file exactly like any other. The only difference is in how the links are named and presented together on the page. We might do something like:
<div id="attachments"> <h2>Attachments</h2> <ul> <li><a href="tyrannosaurus.pdf">Tyrannosaurus Full Report</a></li> <li><a href="stegosaurus.pdf">Stegosaurus Information Pack</a></li> </ul> </div>
With a little help from my Content Management System
In Plone we can build a page with attachments simply by uploading a couple of files as File objects, then linking to them from a Page with the attachments HTML as part of its body text. But writing such HTML by hand is laborious and error-prone. We ask our Content Management System for a little help:
Content authors can easily add, edit and delete attachments for a page. The system automatically lists those attachments when the page is viewed.
To do this, Plone needs to know which Files are the attachments of a given page - we need the system to model the attachment relationship.
Contain me not
Attachments are Files that belong to - are contained by - a Page. Is there a way to model that something contains a set of other somethings in Plone? Yes indeed. One of the first things you learn is that some pieces of content are containers - other items of content are stored inside them. But like an HTML page on the filesystem, the Page type in Plone is not a container. Alas we cannot model attachments as Files contained inside a Page.
Contain me do, with Deco
Yet exciting changes are afoot for Plone's Page type. The upcoming Deco page layout system will introduce a Page type that can contain Files and therefore can model attachments through containment. Great! (Note: the Page change is but one small part of Deco). The question is, how do we model attachments today, on a new site, in a way that works now and will be simple to switch to Deco, both technically and for a content author?
RichDocument blues
One solution is to install a product like RichDocument or PloneArticle to gain a page type that supports attachments. RichDocument has been useful for me in the past (once upon a time I was disabling Page in favour of RichDocument by default on new sites) but I've become wary of this approach.
- RichDocument's attachment controls are basic. For example, authors can't reorder attachments easily.
- Authors have to workaround limitations where RichDocument breaks the usual Plone paradigms. For example, adding an attachment to a newly created (still in the factory) RichDocument won't work unless that page's title has been set, because Plone has to create a (valid) RichDocument first.
- Replacing a fundamental component like the Page type with a non-core product effectively forks your site away from - and behind - the path of Plone core. For example, blob storage worked for core File types, but not for the FileAttachment type used in RichDocument (this is fixed now).
- A bug in Iterate causes attachment edits on a RichDocument working copy to 'disappear' on checkin.
- RichDocument is Archetypes only. Dexterity types cannot simply model attachments in the same way.
The Iterate bug is painful, but Archetypes-only is the big reason to stay away from RichDocument for me. I want a consistent attachments mechanism for Archetypes and Dexterity types.
Hacks and other atrocities
All we need to do is reference Files from Pages, right? There are some obvious, brute force ways to do that. Unfortunately, none of these are appealing solutions to me:
- Point to attachment Files in the Page's Related Items field
- Eugh. Breaks the 'Don't hijack general metadata for hacks' rule. What if I want a related file that is not an attachment?
- Tag Files in the Subject field with a keyword convention like Attached to: Page = dinosaurs
- As above but more so. No.
- Add a new 'attachments' ReferenceField (for both Archetypes and Dexterity types)
- No no no. No hacky metadata full stop! I don't want general content to have explanatory text about when to set this field - it's too much cognitive load for content authors.
The chief problem with all of these is that they're unintuitive. A content author has to remember to take some step to have their attachment show up.
A happy medium
What to do then? The solution I've arrived at is to use a Folder to contain the Page and any Files that are attachments. In other words we model the relationship with containment after all, only using a Folder as a 'wrapper' object rather than having the Page contain the Files directly.
What I like about this is that it's just good, solid Plone content organisation - Pages, Files and Folders as explained in any Plone reference. The Page has to be set as the default item in the Folder - that's fairly common Plone practice. The Files have to be excluded from navigation - again something regular content authors will find familiar. No extra metadata twiddling is necessary.
However:
- It's far from intuitive. After all, I thought it noteworthy enough to warrant a blog post!
- Authors must give the Folder and Page the same title for consistency in the navigation.
- If attachment display is active (see below) any File in the folder is listed as an attachment, which might not be what's wanted.
- It's hard to retrofit attachments to existing Pages. We have to make a Folder and move the Page into it.
- Searching for a Page by title will match both the Folder and the Page inside.
Display-wise, in my particular case I needed the list of attachments to show up on the right hand side. The obvious candidate was a portlet. Considering the options, I realised that a navigation portlet set to display only content at its own level would work great if set up as a Page content type portlet. I had to change the renderer to ignore exclusion (remember Files are set excluded from the left hand navigation!) and describe them as 'Attachments'. Being a content type portlet it is created automatically for a new Page (and other attachment-enabled types), but is simple to remove or block if unwanted on a given Page.
I've put together an example.attachments package demonstrating the approach.
Conclusion
Often we can satisfy our custom requirements neatly by leveraging features in core Plone. In this case simple container types did the trick. There's a tonne of other useful tools in there too (workflow, content rules and nested groups are among my favourites). Take the time you were going to spend knocking up a quick fix to investigate how to take advantage of a core feature. Long term, your solution will be much more solid.
That Deco is rethinking how some of Plone's fundamental authoring tools work is really exciting to me. It should open up even more ways to elegantly solve customers' problems. That's why I'm starting to think now about how Deco will fit into any new site's lifecycle.
Got another approach for modeling attachments in Plone? Let me know!