Programming Plone – Mastering Plone 5 development

Programming Plone

Programming Plone#

In this part you will:

  • Learn about the right ways to do something in code in Plone.

  • Learn to debug

Topics covered:

  • plone.api

  • Portal tools

  • Debugging


The most important tool nowadays for plone developers is the add-on plone.api that covers 20% of the tasks any Plone developer does 80% of the time. If you are not sure how to handle a certain task be sure to first check if plone.api has a solution for you.

The API is divided in five sections. Here is one example from each:

plone.api is a great tool for integrators and developers that is included when you install Plone, though for technical reasons it is not used by the code of Plone itself.

In existing code you'll often encounter methods that don't mean anything to you. You'll have to use the source to find out what they do.

Some of these methods will be replaced by plone.api:

  • Products.CMFCore.utils.getToolByName() -> api.portal.get_tool()

  • zope.component.getMultiAdapter() -> api.content.get_view()


Some parts of Plone are very complex modules in themselves (e.g. the versioning machinery of Products.CMFEditions). Most of them have an API of themselves that you will have to look up at when you need to implement a feature that is not covered by plone.api.

Here are a few examples:


unrestrictedSearchResults() returns search results without checking if the current user has the permission to access the objects.

uniqueValuesFor() returns all entries in an index


runAllExportSteps() generates a tarball containing artifacts from all export steps.


isProductInstalled() checks if a product is installed.

Usually the best way to learn about the API of a tool is to look in the in the respective package and read the docstrings. But sometimes the only way to figure out which features a tool offers is to read its code.

To use a tool you usually first get the tool with plone.api and then invoke the method.

Here is an example where we get the tool portal_membership and use one of its methods to logout a user:

mt = api.portal.get_tool('portal_membership')


The code for logoutUser() is in Many tools that are used in Plone are actually subclasses of tools from the package Products.CMFCore. For example portal_membership is subclassing and extending the same tool from Products.CMFCore.MembershipTool.MembershipTool. That can make it hard to know which options a tool has. There is a ongoing effort by the Plone Community to consolidate tools to make it easier to work with them as a developer.


Here are some tools and techniques we often use when developing and debugging. We use some of them in various situations during the training.

tracebacks and the log

The log (and the console when running in foreground) collects all log messages Plone prints. When an exception occurs Plone throws a traceback. Most of the time the traceback is everything you need to find out what is going wrong. Also adding your own information to the log is very simple.


The python debugger pdb is the single most important tool for us when programming. Just add import pdb; pdb.set_trace() in your code and debug away!

Since Plone 5 you can even add it to templates: add <?python import pdb; pdb.set_trace() ?> to a template and you end up in a pdb shell on calling the template. Look at the variable econtext to see what might have gone wrong.


A great drop-in replacement for pdb with tab completion, syntax highlighting, better tracebacks, introspection and more. And the best feature ever: The command ll prints the whole current method.


Another enhanced pdb with the power of IPython, e.g. tab completion, syntax highlighting, better tracebacks and introspection. It also works nicely with Products.PDBDebugMode. Needs to be invoked with import ipdb; ipdb.set_trace().


An add-on that has two killer features.

Post-mortem debugging: throws you in a pdb whenever an exception occurs. This way you can find out what is going wrong.

pdb view: simply adding /pdb to a url drops you in a pdb session with the current context as self.context. From there you can do just about anything.

Debug mode

When starting Plone using ./bin/instance debug you'll end up in an interactive debugger.

An add-on that allows you to inspect nearly everything. It even has an interactive console, a tester for TALES-expressions and includs a reload-feature like plone.reload.


An add-on that allows to reload code that you changed without restarting the site. It is also used by


An add-on that prevents Plone from sending mails. Instead, they are logged.

Products.enablesettrace or Products.Ienablesettrace

Add-on that allows to use pdb and ipdb in Python skin scripts. Very useful when debugging terrible legacy code.

verbose-security = on

An option for the recipe plone.recipe.zope2instance that logs the detailed reasons why a user might not be authorized to see something.

./bin/buildout annotate

An option when running buildout that logs all the pulled packages and versions.


Sentry is an error logging application you can host yourself. It aggregates tracebacks from many sources and (here comes the killer feature) even the values of variables in the traceback. We use it in all our production sites.


Buildout can create a python shell for you that has all the packages from your Plone site in its python path. Add the part like this:

recipe = zc.recipe.egg
eggs = ${instance:eggs}
interpreter = zopepy



  • Do not try everything at the same time, work in small iterations, use plone.reload to check your results frequently.

  • Use pdb during development to experiment.


Add this to browser/configure.zcml:

2  name="demo_content"
3  for="*"
4  class=""
5  permission="cmf.ManagePortal"
6  />

This is browser/

 1# -*- coding: utf-8 -*-
 2from Products.Five import BrowserView
 3from plone import api
 4from plone.protect.interfaces import IDisableCSRFProtection
 5from zope.interface import alsoProvides
 7import json
 8import logging
 9import requests
11logger = logging.getLogger(__name__)
14class DemoContent(BrowserView):
16    def __call__(self):
17        portal = api.portal.get()
18        self.create_talks(portal)
19        return self.request.response.redirect(portal.absolute_url())
21    def create_talks(self, container, amount=5):
22        """Create some talks"""
24        alsoProvides(self.request, IDisableCSRFProtection)
25        plone_view = api.content.get_view('plone', self.context, self.request)
26        jokes = self.random_jokes(amount)
27        for data in jokes:
28            joke = data['joke']
29            talk = api.content.create(
30                container=container,
31                type='talk',
32                title=plone_view.cropText(joke, length=20),
33                description=joke,
34                type_of_talk='Talk',
35            )
36            api.content.transition(talk, to_state='published')
37  'Created talk {0}'.format(talk.absolute_url()))
38        api.portal.show_message(
39            u'Created {0} talks!'.format(amount), self.request)
41    def random_jokes(self, amount):
42        jokes = requests.get(
43            '{0}'.format(amount))
44        return json.loads(jokes.text)['value']

Some notes:

  • Since calling view is a GET and not a POST we need alsoProvides(self.request, IDisableCSRFProtection) to allow write-on-read without Plone complaining. Alternatively we could create a simple form and create the content on submit.

  • transition has two modes of operation: The documented one is api.content.transition(obj=foo, transition='bar'). That mode tries to execute that specific transition. But sometimes it is better to use to_state which tries to to find a way to get from the current state to the target-state. See for the docstring.

  • To use methods like cropText from another view, you can use the method already discussed in

  • Here the joke is added as the description. To add it as the text, you need to create an instance of RichTextValue and set that as an attribute:

    from import RichTextValue
    talk.details = RichTextValue(joke, 'text/plain', 'text/html',)