How are portlets rendered?¶
The process to find, update and render portlets from the main views is rather complex. Here we describe how does it all work, step by step.
Portlets are always rendered inside a portlet manager. From a template, we can ask a portlet manager to render itself and all its portlets. This is achieved using a zope.contentprovider 'provider:' expression. In Plone's main_template, for example, you will find:
<tal:block replace="structure provider:plone.leftcolumn" />
Behind the scenes, this will look up a local adapter on (context,
request, view) with name
plone.leftcolumn (this is just how the
provider expression works).
As it happens, this local adapter factory was registered when the
portlet manager was installed (via
portlets.xml), and is a
callable that returns an
IPortletManagerRenderer. The portlet
manager renderer is the "view" of the portlet manager.
The default implementation will simply output each portlet wrapped
in a div tag with some helpful attributes to support AJAX via KSS.
You can of course register your own portlet manager renderers. A
portlet manager renderer is a multi-adapter on (context, request,
view, manager). The
@@manage-portlets view, for example, relies
on a portlet manager renderer override for this particular view
that renders the add/move/delete operations. For most people, the
standard renderer will suffice, though.
The portlet manager renderer asks an
IPortletRetriever to fetch
and order the portlet assignments that it should render. This is a
multi-adapter on (context, manager), which means that the fetch
algorithm can be overridden either based on the type of content
object being viewed, or the particular manager. There are two
default implementations - one for "placeful" portlet managers
(those which know about contextual portlets, such as the standard
left/right column ones) and one for "placeless" ones that only deal
in global categories. This latter retriever is used by the
dashboard, which stores its portlets in a global "user" category.
IPortletRetriever algorithm is reasonably complex,
especially when contextual blacklisting/blocking is taken into
account (see below). To make it possible to re-use this algorithm
across multiple configurations, it is written in terms of an
IPortletContext. The context content object will be adapted to
this interface. The portlet context provides:
- A universal identifier for the current context (usually just the
physical path) - the
- A way to obtain the parent object of the current context (for
acquiring portlets and blacklist information in a placeful
retriever) - the
- A list of global portlet categories to look up, and the keys to
look under (obtainable by using the
globalPortletCategories()method on the adapted context).
The last parameter is best described by an example. Let's say we're
logged in as "testuser", a member of both the "Administrators" and
"Reviewers" groups, and were looking at a Folder. The return value
globalPortletCategories() would then be:
>>> portlet_context.globalPortletCategories() [("content_type", "Folder",), ("group", "Administrators",), ("group", "Reviewers",), ("user", "testuser",)]
This informs the retriever that it should first look up any
portlets in the current portlet manager in the "content_type"
category under the "Folder" key, and then portlets in the "group"
category under the "Administators" and "Reviewers" key, and finally
portlets in the "user" category under the "testuser" key, all in
that order. Thus, if we wanted to add a new category, or change the
order of categories, we could override the
either everywhere or just for one particular type of context.
IPortletRetriever has retrieved the assignments that
should be shown for the current portlet manager, the portlet
manager renderer will look up the portlet renderer for each
assignment, ensure that it should indeed be rendered by checking
available property, and finally call
render(), placing the output in the reponse.