Saturday, April 2, 2011

Plone portlets are not enemies... just rude friends

I want to put in words all I like (and don't like) in Plone portlets, and all the problems and limit that everyday a Plone user can find using them.

First main reason for liking portlets
Portlet are quite simple to be developed, and using formlib you can generate your add/edit form in a very straightforward way. You have no limit to the number of configuration option you can add to your form. Then, after that, you can use them all to develop you target portlet template.
All of those configuration are persistent, and are saved in your Plone site.

Second main reason for liking portlets
If the first reason is only a "developer toy", the real good thing of using portlet in Plone is that you are now forced to keep them in the classic two-columns.

Adding a new area where storing portlet (a new portlets manager) is very simple and well documented, for example see "Adding Portlets Manager" guide.

This is not a so common task however, just because two great product did this for you.

ContentWellPortlets
The first one I want to introduce is Products.ContentWellPortlets. The idea behind this is very simple: Plone gives to you two areas where managing you portlets? So, ContentWellPortlets add two additional ones: one above the content main area, another under it. New portlets area works exactly as the standard ones!

This simple general new behavior can resolve a lot of additional theme requests. Can you think about some other Plone area where you need to put portlets?!

collective.portletpage
...yes. There's at least one additional place where you can like portlets: inside the content area that your visitor is reading. Maybe not in every content (for this maybe is better to wait for Deco super drag&drop feature), but to be honest Plone today didn't offer much out-of-the-box for creating homepages.

A simple document, or a collection (even if with a custom view) is not always enough. A lot of Plone websites offer as main homepage (but also but main page for site subsections) a set of blocks, one with a static text, another that display last news, another with the box to register users to a web service... and so on.

As I said, is difficult to do this with Plone out-of-the-box... so what? We must simulate dynamic content with a static use of TinyMCE? We must create some Plone Site root specific view?
Obviously no! In the great star system of Plone add-ons, you can find a product for creating your homepages. Our choice is 90% of times collective.portletpage. We found it perfect for making homepages:
  • simple to be customized for your theme
  • simple for the power user that manage the homepage to be customized
  • simple to add new features (AKA: new portlets)
Using the static text portlet, the collection portlet (distributed directly with Plone), and a good skinner in the team, you can satisfy maybe more the 50% of requests. For the other half, rarely you need to develop new portlets (you can find a lot of theme as Plone add-ons if the initial set is not enough).

NB: a lot of other Plone users choose another well know project for making homepages. I'm talking of Collage.

So: what's bad in portlets?
There nothing really bad in portlets in Plone: it's a great technology, but you need to know some thing to live with them without headache.

Lesson 1: remember to uninstall
This is a good thing to be done with all Plone products but some of theme didn't leave problems behind if you remove theme without uninstall. Sometimes they left some garbage, sometimes nothing...

But not portlets.

With portlets you must remember to uninstall the product that gave you the portlet before remove it from your buildout.
If you don't follow this simple rules sooner or later you will try to access the "manager portlet" section; then you'll get the standard not found error that says "We’re sorry, but that page doesn’t exist…".

What is missing? To see this, let say that we installed a product named example.portlet.foo, we installed it, then we removed it from our buildout.

On Plone 3, you will see a NotFound error when accessing the "manage portlets" views; to see the real error in console you need to enable the logging of NotFound errors at /error_log in the ZMI.

The only know way to restore proper behavior seems to add the product again, then uninstall it, finally remove it from buildout for the last time.

Luckily latest Plone 4 releases has removed this problem; someway the exception is now swallowed. You can remove the product then don't see any problem in the "Manager portlets" section... but please: don't do this! Uninstall is good!

Lesson 2: you can't remove broken portlets
What is a broken portlet?
When you uninstalled the Plone product that give you a portlet, you can also forget to delete some portlet objects. Let explain this, but be warned that this time we have a deep difference between Plone 3 and Plone 4.

Plone 3 has the best behavior: if you remove the product you can continue to use your Plone site, and also the portlet management section.

Broken portlet in Plone 3Obviously you can't continue to see, use and manage the portlet you left there, but the site still works.
What you see is only a portlet that says "this object from the unknown project is broken" and the common "x" icon for deleting it.

But if you try to delete the portlet in this way you'll get no result, and another error in the log:
Traceback (innermost last):
Module ZPublisher.Publish, line 127, in publish
Module ZPublisher.mapply, line 77, in mapply
Module ZPublisher.Publish, line 47, in call_object
Module plone.app.portlets.browser.kss, line 65, in delete_portlet
Module zope.container.ordered, line 243, in __delitem__
KeyError: 'broken'
So, seems that there isn't a way to remove the portlet if you removed (and uninstalled) the product.

My personal opinion about this is different from the bad attitude of didn't uninstalling products we see at "Lesson 1": forget to uninstall is bad, but this is different!
Maybe I've used the portlet in 100 different places I don't remember (or my users did it)... how can I scan the whole site to know where I added locally portlet, or to what group or user's dashboard it had been installed?

The best behavior there is make possible for the user to remove portlet, alway, also if is broken. No Plone versions right now give this: be warned.

Let's talk of Plone 4.

Plone 4 is more problematic. If you remove the product then forget a portlet you will have a real problem, an immediate error:
TypeError: ('Could not adapt', ... 
I was not aware of this problem with Plone 4 until the time I've started this article, so is possible that this is a bug.

Lesson 3: know how to extend portlets
The foo product I named as example is there, but is splitted in many versions:
http://svn.plone.org/svn/collective/example.portlet.foo/

For the rest of the article I will rely heavily on this example project, take a look at the source when needed.

The problem I'm introducing is about errors you will find when you add fields to you portlet (maybe because a new releases is ready), but this code will be used in sites where old instances of the portlet still lives.
There's a know method of writing portlet code that make you don't fall into this. You can still find this error when playing with old products, or relying onto old tutorials, or because you are using older version of ZopeSkel (because... you are using ZopeSkel, right?).

The error came from the portlet Assignment class, or better from how you make it.
The first sub example will introduce you the early version on the portlet (that is a stupid piece of code, that show a message you put into it). The portlet has a single, string, field. Very simple!

The code is there:
http://svn.plone.org/svn/collective/example.portlet.foo/version1/

You need only to note for now (but will become clear later) the Assignment code:
class Assignment(base.Assignment):
"""Portlet assignment.

This is what is actually managed through the portlets UI and associated
with columns.
"""

implements(IFooPortlet)

def __init__(self, foo_field1=u""):
self.foo_field1 = foo_field1
After that, a quick view to the main expression in the portlet template:
tal:content="view/data/foo_field1"
The question is: how if later we add a new field to the portlet? How Plone and portlets react?

So, let's go the version 2 of the example:
http://svn.plone.org/svn/collective/example.portlet.foo/version2

We have a second silly field that the user can fill with data,
Starting from example 1, this new version will be simply like this:
class Assignment(base.Assignment):
"""Portlet assignment.

This is what is actually managed through the portlets UI and associated
with columns.
"""

implements(IFooPortlet)

def __init__(self, foo_field1=u"", foo_field2=u""):
self.foo_field1 = foo_field1
self.foo_field2 = foo_field2
Trivial... But this new version will badly works with portlet created with previous code.

The first problem is related to the portlet template:
tal:content="view/data/foo_field2"
This expression will raise a simple error. Why? Because the old portlet assignment (that is persistent) has no foo_field2 data at all. We will get:
NotFound: foo_field2
The guy who develop the portlet need to think to his previous version, so you can think that he must provide a template that didn't break if a parameter is missing.
However this is not really needed. Let's go on for now.

Now the worst behavior. If you go to the manage portlet, then you'll try to edit the old portlet with the version 2 of the code, you'll get:
Traceback (innermost last):

* Module ZPublisher.Publish, line 127, in publish
* Module ZPublisher.mapply, line 77, in mapply
* Module ZPublisher.Publish, line 47, in call_object
* Module plone.app.portlets.browser.formhelper, line 123, in __call__
* Module zope.formlib.form, line 782, in __call__
* Module five.formlib.formbase, line 50, in update
* Module zope.formlib.form, line 745, in update
* Module zope.formlib.form, line 820, in setUpWidgets
* Module zope.formlib.form, line 408, in setUpEditWidgets
* Module zope.schema._bootstrapfields, line 171, in get

AttributeError: foo_field2
I can't edit my old portlet anymore! I need to delete it, the create it again?
What is the problem here? The Assignment class don't provide a value for the foo_field2 field and this break the edit form.

There's a simple way to fix this, writing you Assignment class in a different way:
class Assignment(base.Assignment):
"""Portlet assignment.

This is what is actually managed through the portlets UI and associated
with columns.
"""

implements(IFooPortlet)

foo_field1=u""
foo_field2=u"foo"

def __init__(self, foo_field1=u"", foo_field2=u""):
self.foo_field1 = foo_field1
self.foo_field2 = foo_field2
This Assignment class (and the final version of the example) is there:
http://svn.plone.org/svn/collective/example.portlet.foo/version3/

This final way of making Assignment, where you have default values fallback taken from the class instead of the instance, solve both problems:
  • the template will always find a foo_field2 value (to make this more clear, the default of the field is not an empty string
  • the edit form can be used normally. New values put in the foo_field2 field are then saved.
Conclusions
  • Portlets are great for compose your layout
  • Plone has a lot of great portlet products
  • You must remember to uninstall portlet you don't need anymore
  • You must delete all portlets before removing the product
  • Create new portlet, but use class level attribute for our assignment
  • Use ZopeSkel templates

5 comments:

  1. A very good tutorial.
    But I was thinking about a question: if a portal has a lot of users, and each one has the member folder under /Member, and all users can post events-news and other things...can a portlet show the these info PER user?
    For example: when I go to one user's page (A), the portlet must show all the news related to that user (A).
    The same when I go to another user's page (B). Have to show ONLY the news related to user B and not A.
    Is it possible with portlets?

    ReplyDelete
    Replies
    1. I think you get enough answer about this in the italian mailing list! ;-)

      Delete
    2. That's true!
      So, it's possible ;)

      Delete
  2. Very good overview!

    I'm wondering if there is any addon that already customized portlet managers and prevent it from breaking when some portlets classes are not available anymore?

    Thank you!

    ReplyDelete
    Replies
    1. If you mean being able to delete missing portlets from managers: unluckily no, for what I know!

      A perfect Plone would be the one that let you deleting broken objects (like you can do today for items in ZMI).
      Maybe in future!

      Delete