Personal tools
You are here: Home Documentation Manuals Plone Core Developer Reference Plone patterns and best practice
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Plone patterns and best practice

Note: Return to reference manual view.

This reference manual describes the conventions, concepts and components of the core Plone codebase. It is intended as a point of reference for new developers who want to be able to contribute, and for old developers who are doing things they haven't done before. At the moment, it is a work in progress, sections will be expanded as we are able to find time. This manual is of course useful to anybody doing development with Plone, but will focus on documenting the areas important to development of Plone itself.

1. Performance

Some general guidelines about coding for performance

Here's a little fact we should admit up front: Plone is slow. Not as slow as it used to be, not as slow that it's useless, but neither Zope nor Plone have ever been sold on speed. In general, Plone is a content production system, not a content delivery system, and has been written for features and ease-of-use — not for blazing speed. (We use caching to improve speed, or separate delivery platforms altogether.)

This has less to do with the design of the software (or the capabilities of the people involved) than with the trade-offs that have been made for usability and flexibility. However, it is important to think about performance when writing code for Plone core.

Profiling

To find performance bottlenecks, you need to use a profiler. The PTProfiler is very useful for finding out which expressions in the page templates are using up the most CPU time. An alternative profiler is the Call profiler. See also Dieter Mauer's Zope Profiler.

Waking up objects

If there is one axiomatic law of performance in Zope and Plone it is this: Do not wake up objects unless you need to. "Waking up" objects refers to pulling them out of the ZODB - traversing to them, loading them into memory. This is an expensive operation. The old navigation tree in Plone 2.0 used to wake up each and every object it displayed. Sometimes it could wake up each object in a BTreeFolder (aka "Large Folder") with 1,000 children, even if it only rendered a single one of them. This was bad.

In general, we prefer to use catalog queries to find objects. This has been possible since the introduction of ExtendedPathIndex in Plone 2.1. The getFolderContents script uses a catalog query. The navigation tree uses a catalog query. The portlets all use catalog queries.

Essentially, if you find yourself using objectValues() or contentValues() you need to think long and hard about whether you should be using a catalog query instead. The following pattern is the most common use of a path search:

  portal_catalog = getToolByName(self, 'portal_catalog')
  results = portal_catalog.searchResults(path = {'query' : '/'.join(self.getPhysicalPath()),
                                                 'depth' : 1})
  for brain in results:
    ...

Catalog queries themselves are not the fastest to set up, so if you are expecting only one or two items, traversal may be better. For the breadcrumbs in Plone 2.5, for example, we use traversal up the acquisition parent chain (by using aq_parent, which returns full objects) instead of a catalog query, because the parent objects are quite likely to be in the ZODB cache already, and because setting up a catalog query for what is most likely going to be 1-5 results is sub-optimal.

Actions

CMF actions are used to drive things like the tabs in the editable border, the site actions (the sitemap, etc.), the personal bar and many other parts of the Plone UI. Unfortunately, actions are also slow, because each may be associated with a TALES expression that is used to determine whether the action should be displayed or not. Each expression requires an expression context to be set up, the expression to be executed, and the result to be evaluated. This is not particularly efficient.

Actions are still used for many of the dynamic parts of the Plone UI, but if you are using them, you should be aware of the performance implications. At the very least, avoid complex TALES expressions if possible.

Avoiding 404s

A 404, or a NotFoundException in Zope, is an expensive operation. 404s can occur unnoticed when a page template references and image or stylesheet that does not exist. Be careful about referencing items that may or may not exist. You can code defensively by doing something like:

  <tal:block condition="exists:some-name">
    ...
  </tal:block>

If you are in doubt, go to the error_log and remove NotFoundException from the list of ignored execptions. Re-load your page and watch out for new entries in the error log.

Making pages cacheable

One of the ways in which real-world installations make Plone faster is by using cacheing, for example via CacheFu. It is important that we make pages and elements of pages as easy to cache as possible. Most of the time, this simply boils down to avoiding unnecessarily dynamic elements.

Take the sitemap, for example. Constructing this is an expensive operation, since the sitemap view needs to execute query for all objects matching the current navigation filter settings. It then needs to render these using a recursive page template. Notice that the action pointing to the sitemap is ${portal_url}/sitemap.

At first sight, this is sub-optimal, because the sitemap can't know the context the user was in before clicking the link, and thus can't put a "you are here" marker in the sitemap. But if the path were ${object_url}/sitemap, then the sitemap on /foo would be different from the sitemap at /bar. Any cache for /foo/sitemap would be completely separate from /bar/sitemap even though 98% of the information on those two pages would be the same. Worse, when Google comes to index the site it will sprider a /sitemap for each and every page, which may bring your site to a grinding halt from executing too many queries.

This principle applies to other things too, such as the author page (which is linked to from all the by-lines), and most commonly image src attributes for common icons.

Cache with v attributes

Sometimes you can't avoid executing an expensive operation more than once during a request. In this case, you may want to cache the result. Zope provides a mechanism for simple caching called "volatile" attributes, or _v_ attributes.

Any non-transient object (that is, any object that lives in the ZODB - note that view classes are not included in this group) can store a value in a v attribute. A v attribute may or may not exist at any given point in time! It will only be valid whilst the object is cached in memory, and there is no guarantee it will stay in memory even during a single request cycle. However, it most likely will be, so it pays off to use it.

You need to code defensively whilst using v attributes:

  from Products.CMFPlone.utils import base_hasattr
  ...

  if not base_hasattr(self, '_v_cachedValue'):
    self._v_cachedValue = self.calculateExpensiveValue()
  return self._v_cachedValue

Download size

Nobody likes having an 800k download to get a few lines of text. Plone's UI is by necessity heavy, and there are many stylesheets, images and HTML snippets included when you download even the lightest of Plone pages. The least you can do is to not make this problem any worse. Think carefully about what you are including, especially if it is going to be included on every page. Keep whitespace and superfluous comments to a minimum, and reduce the size of images as much as you can.

2. Usability

Or... how not to destroy Plone's user-friendliness

Plone differentiates itself on usability. The intuitiveness of the user interface is what attracts people to Plone the most.

There are many subtle principles of usability that won't be discussed here, but there is one golden rule: be consistent. Do not introduce new UI metaphors without a damned good reason. Re-use existing HTML and CSS patterns. Make sure things look consistent.

Also, you must think about the purpose of a UI element and avoid overloading it. The classic example is that people use portlets for static UI elements. Remember that portlets can be removed and re-ordered by users, so depending on them for functionality is not a very good idea.

Similarly, the personal bar (the blue one with my folder, preferences etc.) is used for "personal" items, related to the user, not for site-wide actions. The site actions are used for ... site-wide actions like the sitemap and the site setup, and so on.

Usability often comes down to taste and finesse. If in doubt, ask on the developer list, do your best, and don't be offended if limi changes your carefully crafted HTML.

3. Testability

How to write code that is testable

As explained many times before, every feature, bug fix and re-factoring needs to be backed up with appropriate tests. Sometimes, it is necessary to write code in such a way that it can be tested. Normally, however, making code testable also forces you to think about how that code is modularised and what interfaces other code should be using to talk to your code. If your methods are predictable in a test, they will be predictable to calling code as well. Display logic is most easily tested by moving it from page templates to views.

And remember: the easiest way of making sure that code is testable is to write the tests first!

4. Localisability

Yes, we know it's not really a word, but Plone needs to be localised to a lot of different languages and locales. Think people will only be using your code in English, and will only read text on your page left-to-right? Think again!

Plone prides itself on being multi-lingual, multi-national and multi-cultural. The whole user interface is translatable, and supports right-to-left languages such as Hebrew or Farsi.

The most common interaction you will have with internationalisation (i18n for short) is through page templates. Please read the developers guide to internationalisation, but in general, be mindful that any user-visible string will need a translation key, and that this key will need to be unique within its domain.

Internationalisation of user-visible strings may also be an issue if you are returning a string from a script or method. For example, you may set a status message in a form controller script. In that case, you will need to invoke the translation machinery directly:

  from Products.CMFPlone import PloneMessageFactory as _
  def myMethod(self):
      value = self.getValue()
      return _(u'My name is ${fullname}', mapping=${u'fullname' : value})

Note the use of unicode strings (the u prefix) - all strings that form part of the UI should use unicode. Also note that this assumes the message will be processed by a page template (e.g. via a tal:replace or tal:content statement).

If you need to translate something that is not going to be processed by a page template, you will need to invoke the translation service directly:

  from Products.CMFCore.utils import getToolByName
  ...
  translation_service = getToolByName(self, 'translation_service')
  value = u'John Doo'
  return translation_service.utranslate('plone',
                                         u'My name is ${fullname}',
                                         mapping={u'fullname' : value})

In versions of Plone prior to 2.5, there used to be a translate python script. Please do not use this anymore.

5. Views

Views are a Zope 3 technology for making display components. They are used for certain things in Plone, but because views do not provide all the features users have gotten used to, they must be approached with a little bit of background knowledge.

Page templates promise to separate logic from presentation. Unfortunately, on their own they only deliver half of that promise. How many times have you made a page template that has a very complex python: expression and thought "I should move this out of the page template"? The options until Zope 3 have been:

  • Make ever more complex python: expressions in the page template and look the other way when people ask who the hell put that in there. Unfortunately, such expressions are difficult to maintain and debug, nearly impossible to test, and expensive to execute.
  • Make a new Script (Python) in a skins folder such as plone_scripts. Unfortunately, skin scripts are hard to debug, hard to test, quite slow to execute and litter a global namespace.
  • Put the code in a content type class that is the context of the script. This is quite common in Archetypes, for example. The problem is that this bloats the interface to that class with quite display-specific code, making the type and (particularly) the page template more difficult to re-use.
  • Put the code in the plone_utils tool (PloneTool.py). This has made PloneTool an incredibly bloated set of losely related functions that may or may not be useful to more than one or two templates.

In Zope 3, this is solved with a view. A view is basically just a multi-adapter that is looked up on the context object (usually a content object) and the request (so that the view can be different for HTTP requests that for FTP requests, say). In pure Zope 3, views are used to represent objects to XML-RPC or FTP and typically comprise a Python class implementing a particular interface and a page template. Views are registered in ZCML under a given name and referred to by @@name (the @@ looks like a pair of eyes peering out, if it helps you remember the symbol).

In Plone 2.5, we used to use a custom base class in Products.CMFPlone.utils.BrowserView and a related utility method in Products.CMFPlone.utils called context() to deal with acquisition problems in views. However, this is now frowned-upon, because it introduced a hard dependency on CMFPlone and slightly deviates from the agreed IBrowserView interface.

Instead, views tend to be used like this in Plone 3:

 from Acquisition import aq_inner
 from Products.Five.browser import BrowserView

 class MyView(BrowserView):
     """My new view
     """

     def some_function(self):
         context = aq_inner(self.context)
         ...

Notice the use aq_inner(). This ensures that the acqusition chain of the context does not include the view as a parent. There are numerous examples of browser views in the plone.app.* packages.

View tips and tricks

  • View can (and should) be unit tested! They are just simple python classes, nothing else.
  • Views are transient - they are never stored in the ZODB, and never acquisition-wrapped.
  • Views are created fresh for each request cycle. Therefore, they cannot cache values directly between requests, nor can they rely on any form of persistence.
  • Sometimes it's necessary to call a view method more than once. If that method does some expensive calculation, you should cache the results of the operation. To make this easier, a cache decorator exists in plone.memoize. Using it is pretty simple:
      from plone.memoize.instance import memoize
    
      class MyView(BrowserView):
        ...
    
        @memoize
        def someExpensiveOperation(self):
            x = 10
            for x in range(10000):
                x = x ** x
            return x
    

6. Adapters

Adapters are the Zope 3 way of making re-usable components, or of re-using existing components. It is important that you make proper use of interfaces and the component architecture in order to make your code more re-usable and more loosely coupled.

Consider a Python library that is not aware of Zope. To use this in Zope 2, one would have to mix in a large number of classes just to get the basic Zope object support, making this a very difficult task. In Zope 3, you use adapters to provide the glue. You start by defining an interface that explains how you want other Zope components to talk to the library. Then you provide an adapter that can adapt an object from the non-Zope library to that interface. The Component Architecture provides the registry that makes it easy to look up the appropriate adapter for any given context to any given interface (providing one exists).

As it turns out, adapters are also very useful in creating loosely-coupled components. An adapter is registered from a given interface, to a given interface. Because interfaces can subclass each other, you can have a general adapter for a very general interface, and a more specific adapter for a more specific interface. Similarly, you can provide a very different adapter for one interface than another. Thus, you could make one adapter that makes any code expecting the IMyFunctionality interface able to talk to any object implementing IMyContentType, and a very different adapter that works on ISomeOtherContentType.

By factoring functionality out into separate interfaces implemented with separate adapters, you create slimmer, more re-usable components that are easier to understand and test. Adapters do not necessarily have to "extend" the functionality of an object in a conventional sense. Consider the following example:

Navigation tree needs to consider a large number of options, set in portal_properties. The code that builds the navigation tree performs one complex, but well-defined task: taking a catalog query and turning it into a tree of nodes that can be displayed in the navtree. The old navigation tree code used to mix these tasks together, which meant that the code was impossible to re-use. If you wanted a navtree-like structure (such as the table of contents in this reference manual), you had to write it all again!

For Plone 2.1.3, this was rewritten to use a more generic method that externalised the navtree preferences into a more generic "strategy" object. This let other objects re-use the basic algorithm using different strategies. However, the navtree strategy for the actual navigation tree was still hard-coded into Plone, so if you wanted slightly different strategies in a different context, say, you couldn't do that.

In Plone 2.5, the strategy objects were defined with the following interface in 'CMFPlone.browser.interfaces':

    class INavtreeStrategy(Interface):

        rootPath = Attribute("The path to the root of the navtree (None means use portal root)")

        showAllParents = Attribute("Whether or not to show all parents of the current context always")

        def nodeFilter(node):
            """Return True or False to determine whether to include the given node
            in the tree. Nodes are dicts with at least one key - 'item', the
            catalog brain of the object the node represents.
            """

        def subtreeFilter(node):
            """Return True or False to determine whether to expand the given
            (folderish) node
            """

        def decoratorFactory(node):
            """Inject any additional keys in the node that are needed and return
            the new node.
            """

The basic implementations for these are defined in CMFPlone.browser.navtree, e.g.:

    class SitemapNavtreeStrategy(NavtreeStrategyBase):
        """The navtree building strategy used by the sitemap, based on
        navtree_properties
        """
        implements(INavtreeStrategy)
        #adapts(*, ISiteMap)

        def __init__(self, context, view=None):
            ...

        def nodeFilter(self, node):
            ...

        def subtreeFilter(self, node):
            ...

        def decoratorFactory(self, node):
            ...

ISiteMap is the interface that is used on the view that builds the sitemap, in CMFPlone.browser.navigation. The adapts line is commented out - in later versions of Zope it will be the easiest way of specifying which object this adapter class adapts from (the implements line tells you what it adapts to), but for now we need to wire this up in ZCML. In CMFPlone/browser/configure.zcml we have:

  <adapter for="*
                .interfaces.ISiteMap"
            factory=".navtree.SitemapNavtreeStrategy"
            provides=".interfaces.INavtreeStrategy" />

Note the newline - the adapter is registered for * (any interface) and ISiteMap. Since it's registered for more than one interface, it's a multi-adapter. The factory attribute specifies the class that is used to create the adapter, and the provides attribute tells the component architecture that this provider should be loaded when someone is looking for an INavtreeStrategy.

Also note the class paths starting with a .. This means they are relative to the directory that configure.zcml is in, CMFPlone.browser in this case. You could also use a full path, e.g. Products.CMFPlone.browswer.interfaces.INavtreeStrategy, but this makes it more difficult to refactor the package layout and is harder to read.

The sitemap creation code in CMFPlone.browser.navigation adapts the context (i.e. the content object being viewed) and the view class itself to an INavtreeStrategy in order to find out which strategy to use:

  from zope.component import getMultiAdapter
  ...
  strategy = getMultiAdapter((context, self), INavtreeStrategy)

In fact, if you were using a single-context adapter, you could just instantiate it like so:

  from interfaces import IFoobar
  ...
  foobar = IFoobar(context)

Here you are instantiating the interface as if it were an actual class (it kind of is, but that's beside the point). The component architecture knows to find the appropriate adapter factory by figuring out what interfaces context implements and finding the most specific adapter from one of these interfaces to IFoobar.

Note that the factory (e.g. the __init__ method) for the IFoobar adapter must take exactly one argument - the context (i.e. the object being adapted from), for a single-item adapter. For multi-adapters, it must take one argument for each interface specified in the for attribute in the adapter directive.

Now, let's say you wanted to use a different strategy when the sitemap was being viewed on IMyContentType. All you would need to do is to register a more specific adapter:

  <adapter for=".interfaces.IMyContentType
                .interfaces.ISiteMap"
            factory=".mycontent.MyContentTypeNavtreeStrategy"
            provides=".interfaces.INavtreeStrategy" />

The various Zope 3 resources cover adapters in much greater detail, but it is important to be familiar with adapters and understand the pattern they are trying to solve. Using adapters means talking to interfaces. If you talk to interfaces and not to classes directly, you not only ensure that your interface adequately covers the uses of your code, you also make it possible to substitute different implementations in different scenarios that you never dreamt of. Similarly, by using adapters to glue components together, you can avoid making "fat" interfaces that make components harder to re-use and understand.

7. Future proofing

How to stay in line with Zope 3 concepts and other "future-proofing" techniques

As Plone progresses towards Zope 3, there are some general principles you should keep in mind to make sure your code is "future proof" and won't need to be refactored in the near future. This list may grow, but briefly:

  • Look around you! A lot of problems have been solved in CMF, in Zope 3, or by other Python or JavaScript libraries. Before you start inventing something new that the community will have to maintain, consider carefully whether you can re-use something else.
  • Declare interfaces for new (or existing) code. In general, any new component should use a well thought-out interface. A class should be registered as a factory for an adapter, and any code using that class should use the interface directly, instantiating the class through the component architecture rather than use the class constructor directly. This will make code much easier to refactor and much easier to override.
  • Similarly, where existing tools and objects have interfaces (the CMF tools all have interfaces starting with CMF 1.6, for example), instantiate the objects through the component architecture, not directly from the classes.
  • Don't add new Script (Python)'s. In general, any view logic should be in a Zope 3 view. Generic functionality should use adapters or utilities.
  • We only ever use DTML for email handling (because it's better preserving whitespace than page templates), and even there we keep it to a minimum. DTML is hard to debug and maintain, and is most definitely frowned upon.
  • Use Zope 3 naming conventions for filenames and package layout.

8. Debugging

Tips on how to debug Plone

Whilst working on Plone, there will surely be times when you need to debug your own code, or someone else's code. Debugging Plone core is no different from debugging a third party producy, but there are some general tips you should bear in mind:

  • pdb is your friend. Put import pdb; pdb.set_trace() in your code, restart Zope, reload the page and use the debugger in your terminal. Learn how the debugger works and how to make the most of it.
  • Putting pdb sessions inside unit tests is often the most predictable way of entering a debugging session
  • If it helps, put print statements in your code and watch the output in the terminal, or put debug elements in a page template with tal:content.
  • If you need to debug a TTW Python Script, use context.plone_log("message") and watch your event.log
  • If it makes your life easier, put some debug code deep inside CMF or Archetypes or even Zope itself. You can always remove the file, run an svn up and get back to the previous state.
  • To debug visual problems, use the Mozilla DOM Inspector and the Web Developer and Firebug extensions to Firefox. These will save you hours of trial-and-error.
  • To debug JavaScript, install the Venkman Debugger for Mozilla.
  • Always remove any debug statements and run all tests before checkin! Checking in pdb statements is truly embarrassing.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by