How are portlets rendered?¶
Description
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.
The
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
uid
property. -
A way to obtain the parent object of the current context
(for acquiring portlets and blacklist information in a
placeful retriever) - the
getParent()
method. -
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 of
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
IPortletContext
, either everywhere or just for one particular type of
context.
Once the
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 its
available
property, and finally call
update()
and
render()
, placing the output in the reponse.