Portlets? Who uses those anymore?
We don't a whole lot, but I had a need recently to add new fields to the static text and navigation portlets. This post is not a question, but an overview of how I accomplished this task (in the off chance someone might be looking to do something similar). This was done on Plone 5.2.
After much thinking and searching, I realized that in my 14 years of Plone development experience, I have never added fields to a default Plone portlet. I always created new portlets. And after discussing some with @abl123, we determined it must not be possible to add new fields to the default portlets. Plus I could not find any instances of anyone else doing this. So I started down the path of doing some subclassing.
But when I ran into some issues (because it has been a long time since I've done much of anything with portlets), I ended up digging into the plone.app.portlets code, and stumbled upon this file for tests: plone.app.portlets/test_formextender.py at master · plone/plone.app.portlets · GitHub - and it does exactly the thing I initially set out to do. My code was written in a way to make sure portlets that already exist in the site won't throw errors, and they will display default styling set in the CSS.
So here is the code I ended up writing to accomplish my tasks. It adds two fields to all portlets (I'm sure the zcml could be adjusted if you only want them on one portlet). Based on the selected choices on the fields, certain classes are added to the portlets. I'm not including the navigation.pt here, the only thing I did was add the classes to the template.
<adapter factory=".portlets.PortletColorAdapter" for="plone.portlets.interfaces.IPortletAssignment" /> <!-- update the portlet add form --> <adapter factory=".portlets.PortletColorFormExtender" for="zope.interface.Interface zope.publisher.interfaces.browser.IDefaultBrowserLayer plone.app.portlets.browser.interfaces.IPortletAddForm" provides="plone.z3cform.fieldsets.interfaces.IFormExtender" name="portletcolor.extender" /> <!-- update the portlet edit form --> <adapter factory=".portlets.PortletColorFormExtender" for="zope.interface.Interface zope.publisher.interfaces.browser.IDefaultBrowserLayer plone.app.portlets.browser.interfaces.IPortletEditForm" provides="plone.z3cform.fieldsets.interfaces.IFormExtender" name="portletcolor.extender" /> <!-- update css_class for the Static Text portlet --> <plone:portletRenderer portlet="plone.portlet.static.static.IStaticPortlet" class=".portlets.StaticRenderer" layer="engineering.theme.interfaces.ICustomTheme" /> <!-- override the navigation portlet template --> <plone:portletRenderer portlet="plone.app.portlets.portlets.navigation.INavigationPortlet" template="navigation.pt" layer="engineering.theme.interfaces.ICustomTheme" />
from plone.portlet.static import PloneMessageFactory as _ from plone.portlet.static import static from plone.portlets.interfaces import IPortletAssignment from plone.z3cform.fieldsets.extensible import FormExtender from zope import schema from zope.component import adapter from zope.interface import Interface, implementer EXTENDER_PREFIX = 'portletcolor' class IPortletColor(Interface): """ Schema for portlet color options """ color = schema.Choice( title = "Portlet Color", description = "Header background and border color", default='gray', vocabulary=schema.vocabulary.SimpleVocabulary([ schema.vocabulary.SimpleTerm( 'gray', 'gray', 'Gray'), schema.vocabulary.SimpleTerm( 'gold', 'gold', 'Gold'), ]), ) display_border = schema.Bool( title = "Display portlet border?", description = "If selected, a 1px border will display \ around the portlet in the color selected above", default = False, required = False, ) class PortletColorFormExtender(FormExtender): def update(self): self.add(IPortletColor, prefix=EXTENDER_PREFIX) @adapter(IPortletAssignment) @implementer(IPortletColor) class PortletColorAdapter(object): """adapter for hooking up the color fields to all portlets each portlet template needs to be updated to add the color class (see NavigationRenderer and StaticRenderer below) """ def __init__(self, context): self.__dict__['context'] = context def __setattr__(self, attr, value): setattr(self.context, attr, value) def __getattr__(self, attr): return getattr(self.context, attr, None) class StaticRenderer(static.Renderer): # the static portlet has css_class we can hook in to # without creating a template override def css_class(self): css_class = super(StaticRenderer, self).css_class() if hasattr(self.data, 'color'): css_class += ' portletcolor-' + self.data.color if getattr(self.data, 'display_border', False): css_class += ' portletborder' return css_class