Plone3 portlet magic
Some tips if you want to customize the new Plone3 portlet infrastructure
Plone3 comes with lots of cool new features, but alot of them aren't very well documented. All you have is the source, and because of the numerous packages, adapters, interfaces, ZCML, and so on, it can be really hard to find your way through it and figure out which specific component is responsible for a task in a certain context.
Hopefully the following tips will help you if you're ever in a similar situation (and they're a good future reference for me - I swap out knowledge like this really fast)
Adding, removing and configuring portlets programmatically
If you want to configure the portlet configuration in your portal. Given a 'portal' object (i.e. as you get from context.getSite() in your importVarious, here's how you access the right slot:
from plone.portlets.interfaces import IPortletAssignmentMapping from plone.portlets.interfaces import IPortletManager from zope.component import getUtility from zope.component import getMultiAdapter rightcol = getUtility(IPortletManager, name=u'plone.rightcolumn', context=portal) right = getMultiAdapter((portal, rightcol,), IPortletAssignmentMapping, context=portal)
You can probably guess how to access the left slot.
You can now test if a portlet has been configured and, for example, remove it as follows:
if u'calendar' in right: del right[u'calendar']
Portlets are stored as "Assignments" in a plone.portlet.storage.PortletStorage, the definition of the Assignment (and its interface) is usually defined in the portlet itself, i.e. plone.app.portlets.portlets.calendar.Mapping, .ICalendarPortlets, etc.
To (re)configure a portlet, you modify its Assignment. For example, to reconfigure the navigation portlet in the left slot:
if 'navigation' in left: assignment = left['navigation'] assignment.title = u'Hacking away' assignment.name = u'Hacking away' assignment.topLevel = 0 assignment.bottomLevel = 1
Adding content-type portlets
Adding a content-type specific portlet requires a bit more magic. First we need a magic constant and a PortletCategoryMapping to store the content related portlet:
from plone.portlets.constants import CONTENT_TYPE_CATEGORY from plone.portlets.storage import PortletCategoryMapping
Next, we need to fist create the storage for our content (called "FooContent" in the example below) and assign the appropriate mapping. The foo stuff should, of course, be replaced by your own content type package.
assignment = foo.content.portlets.fooportlet.Assignment() manager = getUtility(IPortletManager, name='plone.rightcolumn') manager[CONTENT_TYPE_CATEGORY]['FooContent'] = PortletCategoryMapping() manager[CONTENT_TYPE_CATEGORY]['FooContent']['fooportlet'] = assignment
Changing the (category) order of portlets
There are different types of categories: the normal "context" / inherited protlets, group portlets, user portlets and content-type portlets. The latter are always shown last, but I needed to have them shown first, for a specific content type. This can be achieved by implementing your own PortletRetriever which adapts your specific content type (or just Interface if you want it to work through your portal).
The standard retriever implementation is defined in plone.portlets.retriever.PortletRetriever. In my case, I simply copied the file, changed what it adapts:
class PortletRetriever(object):
# ...
implements(IPortletRetriever)
adapts(IFooContent, IPortletManager)
and changed the order in which the "categories" list is built: portletCategories first, local/acquired portlets last:
for category, key in pcontext.globalPortletCategories(False):
if not blacklisted[category]:
# ...
# ...
while current is not None and currentpc is not None:
assignable = ILocalPortletAssignable(current, None)
# ...
This is basically swapping the order of two loops. Strictly speaking, I only needed the content-type category to be on top, not all of them (i.e. group, user). But this works for me, for now.
Finally, we register our adapter through some ZCML.
<adapter factory=".retriever.PortletRetriever" />
Fairly simple once you know how to do it.
Some remarks
Wiggy pointed out that you can't rely on the names given above. They work for the standard portal configuration (which is the case for me) but as soon as you add new portlets they'll get numbers assigned in stead of readable names (which makes sense - else you'd get a conflict when adding the same portlet twice).
The correct way to find a portlet would be to iterate over the portlets and search for the specific interface. Perhaps I'll document this later.

