How to render a portlet in Plone

It’s easy 🙂 It took me only two years to figure this out.

Below is an example how to render a portlet in Plone programmatically. This is useful when you want to have special page layouts and you need to include portlet output from another part of the site.

  • Portlet machinery uses Zope’s adapter pattern extensively. This allows you to override things based on the content context, HTTP request, etc.
  • A portlet is assigned to some context in some portlet manager
  • We can dig these assignments up by portlet assignment id (not user visible) or portlet type (portlet assignment interface)
  • Each portlet has its own overrideable renderer class

This all makes everything flexible, though still not flexible enough for some use cases (blacklisting portlets). The downside is that accessing things through many abstraction layers and plug-in points (adaptions) is little cumbersome.

Here is sample code for digging up a portlet and calling its renderer:

        import Acquisition
        from zope.component import getUtility, getMultiAdapter, queryMultiAdapter
        from plone.portlets.interfaces import IPortletRetriever, IPortletManager, IPortletRenderer

        def get_portlet_manager(column):
            """ Return one of default Plone portlet managers.

            @param column: "plone.leftcolumn" or "plone.rightcolumn"

            @return: plone.portlets.interfaces.IPortletManagerRenderer instance
            """
            manager = getUtility(IPortletManager, name=column)
            return manager

        def render_portlet(context, request, view, manager, interface):
            """ Render a portlet defined in external location.

            .. note ::

                Portlets can be idenfied by id (not user visible)
                or interface (portlet class). This method supports look up
                by interface and will return the first matching portlet with this interface.

            @param context: Content item reference where portlet appear

            @param manager: IPortletManagerRenderer instance

            @param view: Current view or None if not available

            @param interface: Marker interface class we use to identify the portlet. E.g. IFacebookPortlet 

            @return: Rendered portlet HTML as a string, or empty string if portlet not found
            """    

            retriever = getMultiAdapter((context, manager), IPortletRetriever)

            portlets = retriever.getPortlets()

            assignment = None

            for portlet in portlets:

                # portlet is {'category': 'context', 'assignment': , 'name': u'facebook-like-box', 'key': '/isleofback/sisalto/huvit-ja-harrasteet
                # Identify portlet by interface provided by assignment
                if interface.providedBy(portlet["assignment"]):
                    assignment = portlet["assignment"]
                    break

            if assignment is None:
                # Did not find a portlet
                return ""

            #- A special type of content provider, IPortletRenderer, knows how to render each
            #type of portlet. The IPortletRenderer should be a multi-adapter from
            #(context, request, view, portlet manager, data provider).

            renderer = queryMultiAdapter((context, request, view, manager, assignment), IPortletRenderer)

            # Make sure we have working acquisition chain
            renderer = renderer.__of__(context)

            if renderer is None:
                raise RuntimeError("No portlet renderer found for portlet assignment:" + str(assignment))

            renderer.update()
            # Does not check visibility here... force render always
            html = renderer.render()

            return html

This is how you integrate it to your view class:

    def render_slope_info(self):
        """ Render a portlet from another page in-line to this page 

        Does not render other portlets in the same portlet manager.
        """
        context = self.context.aq_inner
        request = self.request
        view = self

        column = "isleofback.app.frontpageportlets"

        # Our custom interface marking a portlet
        from isleofback.app.portlets.slopeinfo import ISlopeInfo

        manager = get_portlet_manager(column)

        html = render_portlet(context, request, view, manager, ISlopeInfo)
        return html

…and this is how you call your view helper method from TAL page template:

        <div tal:replace="structure view/render_slope_info" />

\"\" Subscribe to RSS feed Follow me on Twitter Follow me on Facebook Follow me Google+

Leave a Reply

Your email address will not be published. Required fields are marked *