pbauer

Magic templates in Plone 5

Due to the new rendering-engine chameleon it is fun again to write templates

Plone 5 uses Chameleon a its rendering engine. Did you know that because of that you can put a pdb in a template? If you saw the keynote by Eric Steele on Plone 5 you probably do.

But did you also know that the variable econtext holds all current variables up to the moment the pdb is thrown?

Let's put a pdb in newsitem.pt:

(...)

<metal:content-core fill-slot="content-core">
    <metal:block define-macro="content-core"
          tal:define="templateId template/getId;
                      scale_func context/@@images;
                      scaled_image python: getattr(context.aq_explicit, 'image', False) and scale_func.scale('image', scale='mini')">

<?python import pdb; pdb.set_trace() ?>

    <figure class="newsImageContainer"
         tal:condition="python: scaled_image">
        <a href="#"
           tal:define="here_url context/@@plone_context_state/object_url;
                       large_image python: scale_func.scale('image', scale='large');"
           tal:attributes="href large_image/url">
          <img tal:replace="structure python: scaled_image.tag(css_class='newsImage')" />

(...)

When rendering a News Item the variable scaled_image is accessible as econtext['scaled_image']:

> /Users/philip/workspace/test/f56e9585b89e34318d1171acc17f531a7a428a1f.py(132)render_content_core()
(Pdb) econtext['scaled_image']
<plone.namedfile.scaling.ImageScale object at 0x110073b90>
(Pdb) econtext['scaled_image'].width
200 

You can inspect the whole econtext:

(Pdb) from pprint import pprint as pp
(Pdb) pp econtext
{'__convert': <function translate at 0x10fb4d7d0>,
 '__decode': <function decode at 0x10fb4d578>,
 '__slot_content_core': deque([]),
 '__slot_javascript_head_slot': deque([]),
 '__translate': <function translate at 0x10fb4d7d0>,
 'ajax_include_head': None,
 'ajax_load': False,
 'args': (),
 'body_class': 'template-newsitem_view portaltype-news-item site-Plone section-super-news userrole-manager userrole-authenticated userrole-owner plone-toolbar-left-default',
 'checkPermission': <bound method MembershipTool.checkPermission of <MembershipTool at /Plone/portal_membership used for /Plone/super-news>>,
 'container': <NewsItem at /Plone/super-news>,
 'context': <NewsItem at /Plone/super-news>,
 'context_state': <Products.Five.metaclass.ContextState object at 0x10db00910>,
 'default': <object object at 0x100291bf0>,
 'dummy': None,
 'here': <NewsItem at /Plone/super-news>,
 'isRTL': False,
 'lang': 'de',
 'loop': {},
 'modules': <Products.PageTemplates.ZRPythonExpr._SecureModuleImporter instance at 0x102e31b48>,
 'nothing': None,
 'options': {},
 'plone_layout': <Products.Five.metaclass.LayoutPolicy object at 0x10db00310>,
 'plone_view': <Products.Five.metaclass.Plone object at 0x10db00dd0>,
 'portal_state': <Products.Five.metaclass.PortalState object at 0x10fbe8d50>,
 'portal_url': 'http://localhost:8080/Plone',
 'repeat': {},
 'request': <HTTPRequest, URL=http://localhost:8080/Plone/super-news/newsitem_view>,
 'root': <Application at >,
 'scale_func': <Products.Five.metaclass.ImageScaling object at 0x10c2bf390>,
 'scaled_image': <plone.namedfile.scaling.ImageScale object at 0x110073b90>,
 'site_properties': <SimpleItemWithProperties at /Plone/portal_properties/site_properties>,
 'sl': False,
 'sr': False,
 'target_language': None,
 'template': <Products.Five.browser.pagetemplatefile.ViewPageTemplateFile object at 0x10fff3ed0>,
 'templateId': 'newsitem.pt',
 'toolbar_class': 'pat-toolbar initialized plone-toolbar-left',
 'translate': <function translate at 0x10fb4d7d0>,
 'traverse_subpath': [],
 'user': <PropertiedUser 'adminstarzel'>,
 'view': <Products.Five.metaclass.SimpleViewClass from /Users/philip/workspace/test/src-mrd/plone.app.contenttypes/plone/app/contenttypes/browser/templates/newsitem.pt object at 0x10cd93910>,
 'views': <Products.Five.browser.pagetemplatefile.ViewMapper object at 0x10f5cc190>,
 'wrapped_repeat': <Products.PageTemplates.Expressions.SafeMapping object at 0x10ffba1b0>}

Using n you can actually walk down the template and inspect new variables as they appear. After pressing n about 11 times the variable large_image appears as econtext['large_image'].

(Pdb) econtext['large_image'].width
768

The pdb-session you are in is no restricted python but real python. This means you can do the following:

(Pdb) from plone import api
(Pdb) portal = api.portal.get_tool('portal_memberdata')
(Pdb) memberdata = api.portal.get_tool('portal_memberdata')
(Pdb) memberdata.getProperty('wysiwyg_editor')
'kupu'

Hey, what is kupu doing there? I found that in some of our sites that were migrated from Plone 3 this old setting prevented TinyMCE to work in static portlets. But that is a different story, let's just get rid of it.

(Pdb) memberdata.wysiwyg_editor = 'TinyMCE'
(Pdb) import transaction; transaction.commit()

This is a full-grown pdb and you can inspect and modify your complete site with it.

But there is more: You can actually have complete code-blocks into templates:

<?python

from plone import api
catalog = api.portal.get_tool('portal_catalog')
results = []
for brain in catalog(portal_type='Folder'):
    results.append(brain.getURL())

?>

<ul>
    <li tal:repeat="result results">
      ${result}
    </li>
</ul> 

Quick and dirty? Maybe dirty but really quick! It is still very true that having logic in templates is bad practice but I think in some use-cases it is ok:

  • Debugging
  • When you customize a existing template (with z3c.jbot or plone.app.themingplugins) and need some more logic
  • When you quickly need to add some logic to a browser-view that only has a template but no class of it's own

Have fun templating with Plone 5! If you want to learn more about Plone 5 you can still register for the training "Mastering Plone 5 Development" in March 2.-6. (http://www.starzel.de/leistungen/training/)

Update:

As discussed here you can add the econtext to the the locals() by using the following stanza:

<?python locals().update(econtext); import pdb; pdb.set_trace() ?>