Friday, December 30, 2011

Related contents: keeping compatibility with Plone 3

Some days before the Christmas break we found a little compatibility problem with collective.portletpage: when used on Plone 4, and a user add a related item to a Portlet Page content, the "Related content" area were doubled, shown twice.
One in the Plone 3 style (a ul/li structure inside a fieldset element) and another in the new Plone 4 style (that now is a dl/dt+dd code).

The problem? Starting with Plone 4, the related content section has been removed from content view of default content types and has been registered as a viewlet.
This change is great if speaking of design of Web pages. Now a user can move the viewlet in the content view.
Before this (in Plone 3) the related contents area was always at the bottom of the content's view template, called explicitly as a macro... that still exists in Plone 4.

Technically speaking, the new Plone 4 viewlet is registered for every content type, but still the Plone 3.3 related contents macro exists, and contains the same old code (by the way... I think that keeping it as an empty macro could be a better choice).
So: the problem I'm describing is probably quite frequent. It strikes every content type product updated to Plone 4 but were the view has not been updated recently. I meditate about this problem two minutes and I found other two products we own that right now suffer the same problems: Smart Link and RedTurtle Video. We already planned to fix them sooner.

Quick fix
If you don't need anymore to keep Plone 3 compatibility, simply remove the old call to the macro from your content's view. So: take a loot to your content types now!

Less quick (but fair) fix
What if you want to keep Plone 4 compatibility? Removing the macro call make your product no more perfectly working on Plone 3.3: explicit related contents area will be removed from the Plone version where the related item viewlet is not present.

For  Plone newcomers this can seem a waste of time, but for us isn't. Even if Plone 4 is quickly growing, we still have a lot of old Plone 3 customers that ask for new features. I personally don't like too much the "branch way" of keeping compatiblity (continue releasing an "old family" product and a "new generation" ones).

So you can find many workaround for this, but the new Plone 4 feature (a viewlet for related items) in my opinion is also a great enhancement.
For this reason I take some time to develop a very simple product: collective.relateditems.
With this you simply need to update your content view to Plone 4, then add this product to your buildout when you are in a Plone 3 environment (see the documentation to know how to make this dependency automatically fulfilled).

The product simply register a related items viewlet exactly how is done for Plone 4, but keeping Plone 3.3 CSS styles.
What this product also does is to register this viewlet only for content's that implements a new provided interface. So you are the one who choose if display related items viewlet or not (thanks to ZCA, there is a very simple way to make Python classes implementing an interface even without touching 3rd party code).

Conclusion
I have no real conclusion, just a suggestion: go and try your old product, adding to it at least one related item!

Saturday, December 17, 2011

5 minutes with tal:attributes

Let's take a quick read about tal:attributes, an important Template Attribute Language statement of the Zope and Plone development.
About static attributes
You already know that when using tal:attributes you can mix dynamic attributes with static ones:
<h1 class="main"
    tal:attributes="id python:'main-%s' % (25+25)">
Hello World
</h1>
...that produce...
<h1 class="main" id="main-50">
Hello World
</h1>
Obviously, when you use the same attributes in the static and dynamic part, the dynamic ones take precedence:
<h1 class="main"
    id="main-999"
    tal:attributes="id python:'main-%s' % (25+25)">
Hello Worldd
</h1>
...that produce...
<h1 class="main" id="main-50">
Hello World
</h1>
When I learned this behavior, years ago, I started as attitude to remove all static attributes that later would be overridden by dynamic ones (like in the first example). Why? Because in this way is impossible for a developer to do something stupid like don't realize that the attribute is dynamic and not static.

Today I changed my idea because when you begin to look at someone else code, you will like documentation.

Sometimes when you look at a page template made by someone else you don't need (want) to understand the meaning of every single attribute, but a general idea can be enough:
<a href="http://myhost/site/folder/document-to-be-updated"
   tal:attributes="href view/getSomething">
Update me!
</a>
In the example above, knowing that "view/getSomething" return a valid URL can be understood because the value is used for an href attribute, but what kind of URL? Is an internal URL? External ones? An URL to a specific view?
To know this we need to investigate the getSomething code, or test the application.
This can be avoided if the original template designer documented an example URL using a static href URL that document more or less the general meaning, exactly like done above.

Playing with attributes order
Let's look at this TAL code that generate XML:
<foo attb="value2"
     tal:attributes="attc string:this is C;
                     atta string:this is A;" />
... that produce...
<foo attb="value2"
     attc="this is C"
     atta="this is A" />
When mixing static and dynamic attributes, static ones are rendered first keeping the original order, then all dynamic ones again keeping the order.

In the example above I'd like to see three attributes in the right logic order, but two of them are dynamic:
<foo atta="value1"
     attb="value2"
     attc="value3"
     tal:attributes="attc string:this is C;
                     atta string:this is A;" />
... that produce...
<foo atta="this is A"
     attb="value2"
     attc="this is C" />
So with static attributes you can control also order of dynamic ones.

OK, this is not really interesting (commonly who cares of order of attributes inside generated code?) but sometimes you need to controls exactly the generated code. For example, when you wrote functional Python tests, then you check output.
Omissis
Another important thing to learn is omit an attribute dynamically, based on expression. Yes, because with tal:attributes you can also want to control if an attribute must be added to a node or not.
This is very popular when you want to work with an HTML select element or checkbox:
<select tal:define="values python:[1,2,3,4]">
    <option name="foo:list" value="1"
            tal:repeat="val values"
            tal:attributes="value val;
                            selected python:val==2 and True or False" >
        <span tal:replace="val" /></option>
</select>
... that produce ...
<select>
    <option name="foo:list" value="1">
        1</option>
    <option name="foo:list" value="2" selected="selected">
        2</option>
    <option name="foo:list" value="3">
        3</option>
    <option name="foo:list" value="4">
        4</option>
</select>
As you can note, we simply return True or False, without specifying what kind of value we want for the attribute. This is the way for obtaining the standard XHTML value equals to the attribute name.

Another example is for attributes where we want to controls also the attribute value:
<div tal:define="foo python:2">
    <span tal:attributes="class python:foo==1 and 'fooClass' or None">First</span>
    <span tal:attributes="class python:foo==2 and 'fooClass' or None">Second</span>
</div>
... that produce ...
<div>
    <span>First</span>
    <span class="fooClass">Second</span>
</div>
There we are also controlling the class attribute value: we need to specify its value or return None to omit the attribute.
Using static default
The last tip! I learned this one recently, looking at 3rd party code.

Sometimes you put in the static node attribute the very-common value of an attribute, for example a class:
<ul>
    <li class="veryCommonClass1 veryCommonClass2"
        tal:repeat="foo python:[1,2,3]"
        tal:attributes="class python: foo==2 and 'veryCommonClass1 veryCommonClass2 selected' or 'veryCommonClass1 veryCommonClass2'"
        tal:content="foo" />
</ul>
... that produce ...
<ul>
    <li class="veryCommonClass1 veryCommonClass2">1</li>
    <li class="veryCommonClass1 veryCommonClass2 selected">2</li>
    <li class="veryCommonClass1 veryCommonClass2">3</li>
</ul>
In the example above we used a real static HTML attribute for our nodes (and this is good, also for documentation) but it's annoying to be forced to repeat the whole default value also in the tal:attributes expression.

This can be simplified a bit, using default!
<ul>
    <li class="veryCommonClass1 veryCommonClass2"
        tal:repeat="foo python:[1,2,3]"
        tal:attributes="class python: foo==2 and 'veryCommonClass1 veryCommonClass2 selected' or default"
        tal:content="foo" />
</ul>
... that produce ... the same output!

Seems that the variable default can be used in the tal:attributes expression to get the static value we used.
Unluckily default is not a string but a Python object, so we can't use it also for compose the class value when we need to add also the "selected" class: trying in the expression a default + 'selected' will raise an error.

Thats all! TAL can be a language sometimes tricky! Hopefully we will use all Chameleon in the near future and things will be a little cleaner!

Sunday, October 30, 2011

Debugging a slow site using Apache response time and TinyLogAnalyzer

We met recently a new customer, a company that need help with a slow application composed by:
  • a Plone CMS
  • a set of external processes (a daemon, an e-mail scan system, ...)
  • an complex integration with an external database engine
  • an integration with two kind of Visual Basic based client desktop application
With the grow of the company bussines they suffer in years a gradually decay of performance.
Some architectural changes were needed, but the company also asked for some quick fix, to get back a faster application while major changes are planned.

Tune up this application was not simple because the whole system is Windows based, so a lot of well-know software we commonly use are not applicable. The company contacted us because the core of the application seems the Plone CMS and the underlying Zope, but we met a customized version of a very old Plone installation. More or less we were facing something that was not Plone.

We decided to start to look at the code (everyone always hope to find some app-go-slow=True flag activated!), but meanwhile we enabled the response time log on the Apache front-end (and it works also on the Windows version!)

How long does it take to serve a request?
You can change the default Apache HTTP log configuration from something like this:
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

...to this:
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %T/%D" combined

Suddently you log format change to this:
    [31/Jan/2008:14:19:07 +0000] "GET / HTTP/1.1" 200 7918 "" "..." 0/95491

...adding the time (in seconds and microseconds) needed to handle the request.

Please note that in a complex installation this will measure the total time needed for the request (if you put a reverse-cache proxy and a load balancer behind your Apache, this will raise the total time) but their Windows installation is very simple: Apache and Zope behind.

How to analyze results?
After a month of collected log data in the new format we wanted to see some statistics. The problem now was: how to read this log to learn something?

After some Google search I was a little worried. There are some real-time monitor tool for log analysis, but we are really scared to try them on a production Windows environment.

The only log analyzer found was apache-response-time: the projects description is really promising, however I was not able to use it: I installed it on a Linux and MacOS system but both I get a segmentation fault error.
Neither a stackoverflow question helped me to find something more. 

So I started to write some few Python lines preparing a custom analyzer. This job became interesting, so I spent a pair of week-ends for code refactoring and to make initial foo.py script is something reusable.

The result was TinyLogAnalyzer, a command line HTTP log analyzer that take a log file with response time enabled and extract two kind of statistics: the top total time and the top average time.

The top total time give you an idea of what is the most time consuming part of the application. In out case we found that more than 20% of the total time was spent performing searches and a special call performed by an external daemon.
Some optimization and changes to this code make the customer a little happier.
Also we find that a lot of requests were done for download external JavaScript resources, never cached.

The top average time instead give you an idea of the slower part of the application. With this I found that object creation and traversing in a special part of the application was really slow due to use of basic (not BTree) Plone folders.

All other release of the log analyzer product you can find are bug-fixing and new features still done for the same customer's problem (meanwhile we collected two more months of log).
For example, I found that some of the external tasks run also during the weekend, so collect data during Saturday and Sunday was wrong. The same for the night time.

Conclusion
The script is all but perfect. For example it keep all data in memory (so it's not the good tool for handle gigabytes-long-file) and read the log from the first line to the last (so if you need to skip some months it is still really slow).

However: it helped me a lot, giving some time to us and some breath to the customer, making things going better while major refactoring are planned.

Apart of the script itself, I think that enabling the Apache response time can be a great source of information when things goes wrong.

Sunday, October 16, 2011

Plone, security and workflows: learn how to use "Acquire" option

This article is a new episode of my security and workflow common mistake using Plone.

In the first part, "Plone, security and workflows: when rely on Owner role is bad" I introduced a potential workflow problem, that however can be a real problem only in some kind of organizations.

Today I don't want to focus onto roles, but on the use (or missing use) of the "Acquire" check when designing workflows.
Don't know how to use this checkbox can lead to security problem, especially in conjunction with the View permission.

Is also important to be noted that this problem can be inside current workflows distributed using Plone. This depends on how you and your users think that Plone security must work to fill your needs.

What is the "Aquire" meaning?
You can see the "Acquire" check ("Acquire permission settings?") while defining the workflow permissions page, for every permission managed by the workflow.

A workflow state's permission setting
The simple meaning is "roles that behave this permission are the ones listed there, plus ones defined on the container of this content".

If the parent itself use a workflow and manage same permissions, it can also use Acquire check again, so acquiring other permissions for roles from it's own parent... and so on.
This continue until the Plone site (or sometimes, up again to Zope root object) where all permissions are defined in the "Security" tab.

Use the Acquire settings without flagging additional roles for a permission is ok: the meaning is "this workflow state don't want to manage this permission". Instead, another state of the same workflow will probably add or change something to the permission.
If every state of a workflow simply use the Acquire check without change something, the customization of this permission in the workflow is useless and can probably be removed.

Also: don't manage a permission for a workflow is still ok. You will never find a workflow that handle all permissions. Only manage the ones you care about (so, for workflow for custom content types, please remove the "Change portal events" permission!).

Out of the box security
More than one Plone standard workflows suffer of what we can define "a not perfect configuration". This do not mean that this is a Plone architectural problem: with Plone you can create great, perfectly working and safe, workflows! Just: the provided ones could be not perfect in some situations and for being able to fix them or create new ones, you need to understand why!
Also, if the workflow is ok or not, depends on how you and your customer think that Plone must work.

The problem is commonly associated with the View permission, however all analysis that follow are the same for every other permissions. We will refer to "Simple Publication Workflow", while will be the same for other like "Intranet/Extranet Workflow".
The "Simple Publication Workflow" is used from all Plone default contents, also by folders.

Let's take a look at the published state permissions setting.

How the default public state looks like
Look in detail at the Anonymous column. The View permission is given at the Anonymous role. And this seems good: you are explicitly saying "every content in this state will be visible by the anonymous". Always. Everywhere.

How can be this bad? The content is public, I want that Anonymous see it!

Let think this applied to folders: users (99% of times, in my experience) see the private and the published state like a room with a door:
  • A published folder is like a room with an open door. You can see inside the room itself, and other rooms that follow (if those rooms are open)
  • A private folder is like a closed door. You can't see inside it, and you don't care if other doors that follow are open or closed. You don't even know if there are other doors!
Users that move from published to private a main folder that contains a deep tree of subfolder and contents, commonly think that this action will hide all the tree!

You probably already get the point. An explicit check of the View permission for a role is like a magic crystal ball. You don't care of the door: you have the power too see inside.

Going back to Plone world: think about having a Plone folder (folder-1) with a document inside (document-1). If the document is published you can see it using your crystal ball. The review state (even if private) of the containing folder doesn't matter.
This is what is not always clear to users.
  • Users can reach http://yourcompany.com/
  • Users can't reach http://yourcompany.com/folder-1 (he will get Unauthorized)
  • Users can reach http://yourcompany.com/folder-1/document-1
During developer training that customers ask us, one of the argument is always how to customize workflows. Sometimes those customers have already installed a Plone site or intranet by their own so when I explain this, is not uncommon to see surprised faces.

O__o

Another bad attitude: customers never tested the workflow deeply and they think that their contents are is a secure review state just because a main folder is private.

How to fix this
As said above: this is not a Plone technological error. There's a way to get exactly what we want: a set of rooms with doors, with no crystal ball, secret passage or teleport bridge that can transport unwanted users inside the room.

The trick is: don't say that the document is visible explicitly, leave this responsibility to the container (to the room's door).

This is traduced in a workflow configuration that remove explicit permission for Anonymous but use the Acquire power.

A workflow state's permission setting
Note also an important thing: we don't give the check to Anonymous, but we now check permissions for all other roles that matter. We mean there: all roles that must see the content ignoring if the access door is open or closed.

In this way an anonymous user is able to see the content if he can enter the room (published container folder) and will be no more able to see anything when the door is closed (private container folder).

This is really simple!

When not talking of anonymous access
Somewhere above I said that also the "Intranet/Extranet Workflow" suffer the same configuration problem. Can we fix also this?

This kind of workflow is designed for intranets, but also provide some review state for public contents: it add the "internally published" concept to our Plone site.

Note that this workflow is designed to be used with "Intranet Workflow for Folders" associated with folderish content types.

When using those workflows, we are sometimes referring to the Member roles as we did for Anonymous above.

The default configuration of the "internally_published" state is as follow:

A workflow state's permission setting
The concept behind this state is "having a published state for intranet users" (so excluding Anonymous).
So we have the same problem for the explicit check of the View permission, this time for Member role.

We can fix this in the same way:

A workflow state's permission setting
We removed the explicit View permission for Member role while enabling the "Acquire".

This time the cure has some limit.

First problem: if we put an internally_published content in the site root, in our "fixed" version of the workflow we are exposing the content to anonymous access, while the original version protects it (know that you will never remove the View permission for Anonymous role from the site root or Plone will work in unpredictable way).

Another problem: take a look at states defined for "Intranet Workflow for Folders". It's composed by two states: private and internal. If we look at the internal description we read "Visible to all intranet users", so it want to be a "published folder for intranet users".

The private state has no View permission for Member role, while the internal state explicitly check it for site members.
As explained above, this mean that if we put an internal folder inside a private folder, site members will be able to skip the private state of the main container folder and access directly the inner ones and all contents inside.

If we fix this removing the explicit check for Member while activating the "Acquire" check we will have a similar problem to the first ones: anonymous users will be able to access the internal folder in the site root...

There is no perfect solution for this problem using only a good workflow configuration. We need to make some default configuration to the site structure, helping the workflow:
  • We fix all "internal published like" states of both workflows as described (using "Acquire")
  • We put all internal folder in one (or more) main private folder(s) in the site root
  • We give to all users (we can also use the "authenticated users" virtual group) the "can read" check (Reader role) from the sharing tab of this private folder
  • We remove from all contents/subfolders inside master private folder(s) the roles inheritance from the sharing tab, removing Reader power to all members
  • Inner subfolder and contents will live with normal worfklow security settings
With a configuration like this we can be sure that Anonymous users can't access our contents (as they all live in a private folder) while still be able to control the visibility of a subtree changing the main folder review state (relying on "Acquire").

Think about having a dedicated workflow for folders
Designing a workflow can often be design a set of workflows. This is common; the "Intranet Workflow for Folders" is a perfect example. To use it right, it's better to use also the workflow for folders.

I find often useful to create two different workflow for real contents and folders.
Here a common request I get: Reader role must be able to enter inside private area, but inside this being able to only published contents.

Obviously the workflow I will design will be a workflow that rely onto "Acquire".
While the private state for folders will give explicit View permission to Reader, the private state for contents won't give such power. Done!

Conclusion
Before load your new workflow in a production site or intranet, be sure of what workflow you give to contents... and test it.

A simple way for performing those tests is_ search contents using the site search feature.

Sunday, September 18, 2011

Plone, security and workflows: when rely on Owner role is bad

Once upon a time Plone was a CMS that:
  • was using less roles than today (no Contributor, Reader or Editor and obviously no Site Administrator, the new entry)
  • None of few roles available (Manager, Owner, Reviewer and Member) were hidden from the sharing form
Starting from Plone 3 the sharing view were simplified in two ways:
  • hiding confusing roles (Member and Owner)
  • translating the general meaning of every roles left
In this post I want to talk about the Owner role.

First of all: why it has been hidden from the sharing form? Because this was confusing users a lot.
Who is the Owner of a document? Commonly is a single user, the one you read from the Creator metadata field.

You can manipulate the Owner of a document (you know? If you change the Creator from the edit tab you simply change an information, nothing different as changing the description): you need simply to access http://url/to/document/ownership_form (the link to this page has been removed from the sharing form... I never get why, however it's always there if you need its service).
Also: you can use the very good plone.app.changeownership that also help you to manage the deprecation/removal of a user, transferring ownership of all documents from one to another.

The Dark Side Of The Owner
In a basic Plone installation there's nothing wrong with Owner. It's natural to base part of the document security on the ownership nature and it's right that the document creator still keep special power onto it.
If you use Plone for a open collaboration project this is ok. For example if I publish something on plone.org is good that I can always change it later, or retract it.

You must be warned about using workflows that give special powers to Owner only if the organization using Plone is a bit structured. Follow some examples:
  • your Organization is splitted in at least two or more departments and on that division you also create a set of Plone groups. "Department A" can have a personal area where "Department B" can't enter, or simply can't write into.
    What's happen when a user from Department A is moved (let say because get a promotion) to Department B?
  • you give temporary special power to a user that isn't really part of your team and for some times he will be able to contribute, creating some documents. Then you revoke this power but you can't simply also remove the users itself
Using Plone default workflows both cases above can leave you security problems.
Take for example the Simple Publication Workflow, current Plone default workflow: when a content is in the published state the Owner can still modify it. Commonly this is not a problem (if it is, you need a different workflow, but Plone is really good for build them quickly) but if you are in a situation like the two described above the real creator will not be the proper user for having this power on the content.

There isn't only a solution to this problem. From now for simplicity we will consider the first example, where the user that is creator of a document has been moved away.

Perfect solution
The perfect solution is to use plone.app.changeownership because in this way you can:
  • keep the high level ownership (metadata) of the original user. Commonly this is good, you don't want to change the history and destroy proof that the user one time worked in Department A (but know that with this product you can also do this, like cancel documents creation performed by super user used during site creation).
  • give low level ownership (Owner role) to another user that still work in Department A
However the user of this product is not automatically integrated in Plone. Your organization must take care of perform the migration manually every time someone leave one of the departments.
So, even if I name this "the perfect solution", you probably prefer a well Plone workflow integrated ones.

Don't rely on Owner
The other kind of solution is to design and use workflows that don't rely on the Owner role.
Going back to Simple Publication Workflow, you can remove to Owner the power to edit published contents.
Simple Publication Workflow: published state
If this seems enough for you, remember that this will not cover all cases. For example: the user moved to other department must not be able to edit also document he created that are still in private state (because he didn't published them, or a reviewer retracted).

You can't simply remove all permissions to Owner role. Why not? Because you need to keep special power for the Owner in the workflow initial state.
Look again at Simple Publication Workflow, this time to the private state permissions. Who can edit the content? If you remove administrative roles (Manager and Site Administrator), only Editor and Owner are left. You can't be sure that the user is also in a group that will give him Editor role (the "can edit" from the sharing form)... maybe he is simply a Contributor ("can add").
Simple Publication Workflow: private state
So, if you remove the Owner power there your user will not be able anymore to create new contents. He will get Unauthorized error when saving for the first time.

Rely on Editor Role
In facts you can still rely only to Editor role, but for what described above this can be only applied to organizations where you don't have the Owner/Contributor/Editor concepts, but simply the "Author" ones, where is not important who create a document, but all the team matters.

In my customer-types experience, we have many organizations like this. It mean that you can create a content, but it isn't yours; it's always of your group (Office, Department, ...). You create it, but your colleague can edit it later, even if it is still in the private state. The Plone history form take track of all changes done.

When your organization works in this way, for you the Editor and Contributor roles collapse because your users can add new content and edit all contents. You commonly have a group that can manage contents on a folder and all users in this group have both roles.
You don't get Unauthorized errors removing to Owner all permissions because the Contributor will be also an Editor so he will be able to save the content.

What's happen when you are moved away? You loose all Editor powers and the Owner roles is giving you nothing special.

Fake initial state
The solution above is OK, but it can't be used if you want to keep the differences between Editor role and Contributor: you could like the fact that a member of the department can add new contents but also that he isn't able to edit other contents, while an editor user can modify all contents.

In this situation you need to go back to the Owner role. But how use it without falling in the security risk and also give to the user the permission to save the content the first time?

The solution can be add a new initial state: let name it "Being created" (stolen from one of the Poi's workflows).

Added Being Created state
(images above realized using the always great DCWorkflowGraph)

You can leave "Modify portal content" permission to Owner only in this state. The "Post" transition can be "Automatic", instead of "Initiated by user action" so, as soon as the user save the content, it will move in the real initial state: private.

This solution has some limitation: Owner can't be able to edit again the content after saved it (or better: he need also additional roles like Editor).

Fake initial state (lite)
Another solution is very similar to the one above: use again a pre-initial state (let say again "Being created") but leave to the user the choice of move forward. Also, only a Reviewer/Manager power role can move the content back to the pre-initial state, where the Owner can edit it.
The Owner will be able to save it multiple times, the send it, but later can ask permission for edit again. Also: this "Being created" state will be "super private". No other Editor/Reviewer/Reader role will show those contents.

In this way, even if later the user moved to Department B mess up or destroy his old documents, no one will cry. If those document stay there, they will be only a little garbage in your database.

Conclusion
Owner role is a good mate, just know how and when use it!

Saturday, September 10, 2011

Plone and Related Items use for Collections and Folders

This post is about an old but hidden feature of Plone Collections, and how related items are used in some non-standard ways in Plone.

Have you ever used related items for a Plone Collection? Maybe you never used them neither for Folders, so you simply learn that Plone choose to hide related items for some types.

But while for Folders this is simply a choice to make related items as hidden, for Collections is different.

Using related items for Folders
Every AT content schema, after being defined, call a function that is finalizeATCTSchema.

    def finalizeATCTSchema(schema, folderish=False, moveDiscussion=True):
        """Finalizes an ATCT type schema to alter some fields
        """
        schema.moveField('relatedItems', pos='bottom')
        if folderish:
            schema['relatedItems'].widget.visible['edit'] = 'invisible'
        ... 

The function receive the argument folderish=True for folder content types. As you can see this hide the related items widget when editing (and in this way no simple user can fill the field, so you'll never see it also in view).

But if you fill the field through an external code, the field appear also in Folder's view.

To restore the field normally, simply make the widget visible again.

Using related items for Collections
For Collections the related items field is not hidden but has a special purpose.

You must use it to add additional items to the collection search result, ignoring whatever type of criteria you have set in the Collection.

For example: you can have a Collection that show all contents inside a folder (using a simple well-know path criterion) then you can add to related items of the collection one or more contents outside that folder. The resultset of the collection will include also this content.

Another example: you have a collection that display news items on the portal, then you need to include also an important event. The normal way for Plone is to create a new news item that talk about this event, but you can also use this trick: add the event to related items.

Drawbacks
I consider this a good feature. Not common, but two examples above are not so uncommon... but probably you never know of this feature.
However this behavior has some problems.

Plone 4.1 double related items. Seems that there is a bug in recent Plone. On Plone 3 if you were using related items in collections, the related items section was not shown in the Collection view template. Now is... probably a little bug that need to be fixed.

Documentation. No one know of this behavior because this isn't readable anywhere in the Plone UI. The related items field is the same of always. Some suggestion that can help:
  • related items for Collections must be moved on the main fieldset, to highlight that there has some special meaning.
  • related items widget help text for Collections could give some additional information on this behavior.
Related items aren't as normal criterion results. Related items are added to the resultset of the Collection, but:
  • They are not sorted using the choosen sorting criteria
  • They are not mixed with normal results
  • They are always returned first
So related items are always used first, then other results are displayed. This can be good (because you can use this feature to put an high important news as first item of your Collection) but not simple to understand. However this is not a bug... mix related items and other results sorting them all can be complex and expensive for Plone.

How to sort those additional resultset? Recent Plone releases have finally add a way to sort related items (without using external add-ons like before) but using Plone 4.1 trunk the manual sorting is ignored for this feature. Maybe another little but to be fixed?

Can I use related items normally? Giving us this feature will steal us the possibility to use related items for Collections for the common purpose. I don't find this a big problem, however I get this request one time: the customer asked us to be able to use related items for collections in the classic way. Use the same field for a different task is someway strange.

Conclusion
Is this a good feature? I think it can be... however sometime I think that is too complex and simply needs to be removed. Some other times I find it smart.

What you think?

Sunday, August 21, 2011

TagCloud and SEO in Plone: problem solved and lesson learned

This article is an analysis of how a minor bug in a Plone product we are maintaining give us some problems on a production site, how fixing it was not enough to revert problems, and what lesson I learned.

The environment
The Plone site I'm talking about is an old Plone 3.3 installation with an old hardware. It worked without problems for years then suddenly it started to be slow (sometimes really slow).
In front of this Plone installation there's also a Varnish installation that cache also HTML for anonymous users (a standard for us).

So what's can be turned wrong?!

After started checking the problem I also find that it wasn't a memory consumption problem (a lot of free memory, thanks to the use of plone.app.blob also on this Plone 3.3 installation), but only a CPU usage (unluckily we have only a single core for the Plone site).

Next step was checking what the site was doing to keep the CPU so busy. Before installing products like zope.healthwatcher, I spent some minutes simply checking the HTTP log and I found that the site is... "really popular". I mean it is very often visited by web crawlers, mainly Googlebot. This is not strange: the site is the main site of a well know public agency, and update really often.

The Vaporisation problem found
Is this enough to make a Plone site "slow"? Obviously no! The problem was not Plone but the TagCloud portlet.

One of the best features of collective.vaporisation (maybe the main feature that convinced us to takeover the project and maintain it) is the joint navigation.
When activating it, clicking on one of the links inside the cloud will not simply display a search result, but a search result where you can also navigate through found items using related additional terms (something like a faceted navigation).

The first problem was that customer site use a lot of different keyword, using them widely in a lot of site contents.
In this way the joint navigation became complex. Imagine a web crawler that starts to scan the tag cloud results page: it's able to follow a lot of additional link that create a great permutation of different search results (let call this big number N).

The second problem (the real one) was a bug in the way Vaporisation was creating links from the cloud to the result page (note that version 1.2.0 of the TagCloud fixed this problem so recent releases will not give you such behavior): older releases were calling the cloud_search template on the context.
This mean that when the visitor was checking the cloud links from the home page, it will call Plone as http://thesite.com/cloud_search, but when visiting another page, the URL will became http://thesite.com/path/to/document/cloud_search.

This is a disaster for the cache that Varnish is trying to produce for our site: the two URL are different from the cache point of view, but in facts on Plone they are generating the same result. This is really bad (also for page rank).

This also raise the number of possible cloud search pages from N to NxM (where M is the number of documents in the site). Terrible!

The fix was simple: change the way the URL to the cloud is created, always give to users the http://thesite.com/cloud_search version.

Google: the elephant
When I released this fix I make another mistake (not very lucky...).
Always remember that in Plone the context can be important. If you created a site view that must be called onto the site root is better to define it callable only on this context. An common error can be define this view callable everywhere (like old CMF skins templates).

So even if all URL were always generated in the right way, the view was defined still as follow:

<browser:page
	 for="*"
	 name="cloud_search"
	 class=".search.CloudSearch"
	 template="search.pt"
	 permission="zope2.View"
	 />

In this way calling (manually) again the http://thesite.com/path/to/document/clud_search will still be valid.

Why this is still a problem? Haven't you fixed all possible wrong links?
Because of Google long memory.

Even if we removed all links to the useless cloud_search call, Google already indexed them so it continue to call all those URLs and make the Varnish cache useless.
Maybe with the time those kind of call could decrease (as the search engines will find no links to this page anymore), but after applied the fix we get no benefits.

Version 1.2.2 fixed this, defining also the right context for the view:

<browser:page
	 for="Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot"
	 name="cloud_search"
	 class=".search.CloudSearch"
	 template="search.pt"
	 permission="zope2.View"
	 />

After this change all calls to something that isn't http://thesite.com/cloud_search will simply generate a NotFound error page. This is good: Plone is very fast to generate this page, and this kind of pages (if no link lead to them) will rapidly disappear from the index.

Some SEO enhancement can help
Version 1.2.2 helped a lot, but in the meantime I was also reading a book about SEO and talked with coworkers (real SEO expert) of those kind of problems.

Must Google be aware of the cloud_search page? Is a good thing that this page will be indexed?Also, the site still use the joint navigation, so a major number of pages are still called, but in any case are those pages giving some good feedback to Web users that performs searches?

I think that there isn't an universal answer (I've doubt also related to simple Plone SERP... must be indexed by Google? Maybe Plone itself must think about this) but in this case no: let search engines index results page of search performed by tag cloud is useless.

Version 1.2.3 make the Vaporisation product more "SEO friendly".

First of all we can suggest search engines to not follow a link when scanning our site putting a rel='nofollow' attribute on it.

The new version of Vaporisation will put this rel nofollow value on every tag cloud link. I read recently that this attribute is only a suggestion, but can help.

The second and last change of this version is the use of a tag meta in the cloud SERP:

<meta name="robots" content="noindex, nofollow" />

This says to the search engine to not index this page (noindex value) but also don't follow any links from this page (nofollow value, less important there).

In this way Google quickly stopped to index also the big number of possible joint navigation results pages.

Damn Google Reader!
After every fix I applied, the site quickly became faster.
After all those fixes I still saw an heavy Google access to the site on a lot of different pages: this time to the search_rss page (used by Plone to generate RSS feeds from search result), commonly called with an URL like http://thesite.com/search_rss.
The site now was not so slow, but I liked to continue the investigation.

The problem was still the first one I analyzed above: the search_rss URL was available from the cloud_search page and so, when using older releases, was created on the context and not on the portal root.
So while a version lower than 1.2.0 was active, Google indexed a lot of cloud_search versions but for each one also a useless search_rss page.

Again, have fixed the problem for the "master" cloud_search page will not stop Google from indexing all the RSS calls.

This time we can't rely onto a well-done meta tag, as the RSS isn't HTML! Where I can put the tag?!

We have two alternative ways.

The quicker one is to block every web crawler access to the search_rss page, using simply a robots.txt file on the site root (but this will works with query parameters?):

User-agent: *
    Disallow: /search_rss

The other way, that also leave the search_rss page indexable by services like Google Reader, is to make also the search_rss template callable only from the site root.

This need some simple Plone customization (outside the Vaporisation product itself). Maybe Plone 5 (or 6?) will not use anymore CMF templates, but right now we have a lot of them all around Plone.
One of the problem of old-style template is that they can be called on every possible context. And obviously search_rss template is a very old template...

So the fix is not elegant as the cloud_search ones. We can:
  • configure Apache to disallow any search_rss call outside the site root
  • manually check if the site context is the site root; if not, we manually raise a NotFound error.

Conclusion
I found the resolution of this someway "funny".

Other Plone sites that installed directly the 1.2.0 version of Vaporisation didn't suffer all problems that are here described because the cloud_search page was always called in the "most correct way": on the site root.
So in this case Google did not indexed all other useless alternative way to call the same page (this mean also no need to fix the Plone basic search_rss)

Last thing: if you create a Plone template planned to be called on a context be sure to register it only on this context. If this template is an old CMF ones keep an eye on how you create links to this template.

Saturday, August 6, 2011

Data di Scadenza/Pubblicazione in Plone: la guida definitiva

E' una delle funzionalità da sempre presente nel CMS, eppure rimane ancora oggi poco capita dagli utenti e spesso provoca effetti imprevisti.

Stiamo parlando della data di scadenza e della data di pubblicazione di Plone.

Il caso peggiore che può capitare
Mi torna in mente un affascinante concetto letto in un libro sulla storia della crittografia ("Codici e Segreti", ve lo consiglio). Più o meno recitava così:
La sola cosa peggiore di non avere segretezza è credere di averla
Torno subito coi piedi per terra.
Plone ha un potente sistema per rendere i nostri dati segreti e sicuri: il workflow. Quando il workflow è ben configurato e i documenti o le cartelle sono nel giusto stato di revisione, possiamo dormire sonni tranquilli.

La cosa più difficile da capire per un redattore Plone è che le date di scadenza e pubblicazione non influiscono sul workflow!

Dopo tutti questi anni ho spiegato e visto spiegare questo concetto ai redattori molte volte eppure tante volte ho visto questa stessa caratteristica di Plone non essere capita.

E qui ci ricolleghiamo alla frase che ho citato sopra. Assai spesso l'utente crede (sebbene nessuno lo abbia mai spiegato in questi termini) che quando un documento di Plone scade, questo magicamente diventi privato.
Ho visto casi dove, a volte anche dopo anni di utilizzo di un sito in produzione, un redattore scopre che qualche altro utente riesce ad accedere ad un contenuto che non dovrebbe vedere e la loro prima impressione è che sia un baco di Plone (o peggio, un baco dell'installazione data dal fornitore).

Vi siete mai sentiti dire "Ma è scaduto! Perché si vede?!", oppure: "Credevo che diventassero privati!". Ed ecco che entriamo nel caso peggiore... per tutto questo tempo il redattore ha creduto che i suoi contenuti fossero al sicuro, ma non era così!

Per certi versi questo caso è particolarmente sentito per la scadenza dei contenuti, mentre raramente lo è per la data di pubblicazione (o visibilità effettiva). Nel seguito del documento mi riferirò quindi maggiormente al concetto di scadenza ma sia chiaro che quanto verrà spiegato vale anche per i documenti non ancora giunti alla data di pubblicazione.

Badate bene: se c'è una colpa non è dei redattori. Il funzionamento del meccanismo di scadenza/pubblicazione è in effetti difficile da assimilare (e la lunghezza che assumerà questo articolo è quantomai sospetta per poterlo negare... elefantiasi letteraria a parte!  :-)).

In più, come vedremo poi, è un meccanismo ibrido... in parte usa i permessi (il cuore della sicurezza Plone) ma in modo assai diverso da come i permessi funzionano per tante altre situazioni. Quindi anche gli sviluppatori Plone meno esperti, che magari si sentono già padroni dell'uso del workflow, possono commettere errori.

Prima cosa da capire: scaduto non significa privato
Questo semplice concetto a volte basta agli utenti per poter dire "quindi questo meccanismo non mi serve" perché avrebbero preferito che la scadenza portasse automaticamente all'archiviazione o alla "spubblicazione" del documento.
Consiglio a questi utenti di non abbandonare così precipitosamente questa funzionalità, perché rimane nonostante tutto molto interessante e utile se la si capisce fino in fondo.

Ammettiamo di essere in presenza di un semplice workflow a due stati: privato e pubblicato. Il contenuto privato non è visibile da nessun utente se non il redattore stesso (o il suo team) ma comunque non ai semplici ed anonimi visitatori del sito.
Il contenuto pubblicato è invece di pubblico dominio. Tutti lo vedono, tutti lo trovano (compreso Google).

Poi abbiamo la data di scadenza (o pubblicazione).

Queste due entità (stato di revisione del documento e data di pubblicazione) viaggiano in modo completamente disaccoppiato e non si influenzano in nessun modo. Posso avere tutte e quattro le possibili combinazioni:
  • documento privato e ancora valido
  • documento privato a scaduto
  • documento pubblicato e ancora valido
  • documento pubblicato e scaduto
La sicurezza (il workflow) è l'unica cosa certa. Se un contenuto è privato è sicuro. Qualunque sia la data di scadenza poco importa, il documento privato non può essere acceduto direttamente, o tramite ricerche da Google o nel sito. Non ci sono problemi relativamente a scarsa sicurezza in questi casi.

I problemi che potete riscontrare iniziano quando i documenti diventano pubblicati e per questo da ora in poi ci concentriamo su questi casi. Se un documento è pubblicato è accessibile al mondo. Le date di scadenza/pubblicazione servono solo a limitare alcuni tipi di accesso al contenuto.

La prima lezione da imparare è quindi non confondere la sicurezza del contenuto con la sua validità.

Contenuto scaduto significa "non è ricercabile"
Questa è la mia Legge Zero sull'uso della scadenza/pubblicazione in Plone. E' la prima cosa che dico agli utenti quando parlo di questa funzionalità, che sia ad un corso o ad un nuovo redattore (o purtroppo ad uno vecchio che magari non aveva capito).
Ovviamente questa frase non basta... va argomentata! Ma intanto vi fa iniziare la discussione in modo diverso dal più vago "quando un contenuto è scaduto, non è più visibile", che ricorda troppo quello che capita col passaggio allo stato di revisione privato.

Il funzionamento della data di scadenza/pubblicazione è in realtà davvero tutto qui: il contenuto non può essere trovato dal catalogo di Plone.
Questo significa quindi che il contenuto è escluso automaticamente:
  • dalla ricerca del sito (base, istantanea, avanzata)
  • dal navigatore
  • dai risultati delle collezioni
  • dalle viste applicabili alle cartelle
Come dicevo, il funzionamento va argomentato un po': l'utente deve capire che Plone usa la ricerca per quasi qualunque cosa venga mostrata nella sua interfaccia. Se è ovvio comprendere questa regola per quello che riguarda la ricerca del sito, è bene accennare al fatto che anche:
  • che il navigatore è frutto di una serie di utilizzi del catalogo di Plone
  • che le varie viste applicabili alle cartelle eseguono delle ricerche nel catalogo (questo in realtà dovrebbe essere cambiato da Plone 4.0 ma non è cambiato il risultato quindi non complichiamo le cose!)
  • che le collezioni non sono altro che "ricerche salvate" e quindi usano il catalogo
Il risultato di questa regola è che il contenuto scaduto, se non per il Manager del sito (il perché lo vedremo poi) risulta sparito.
L'effetto primario per l'utente finale è che in effetti (ad una prima occhiata che spesso confonde i redattori) subisce lo stesso effetto del cambio di stato quando si revoca il contenuto e lo si porta a privato: il contenuto non viene più trovato dalla ricerca, sparisce dai navigatori e dalle viste...

Ma la differenza quindi qual'è?

Google e cronologia hanno la memoria lunga
La differenza è semplice: un contenuto che non può essere trovato non significa sia inaccessibile mentre con l'uso del workflow abbiamo entrambe le cose.

Partiamo da Google, che ha indicizzato il vostro contenuto (in quanto c'è stato un momento nella sua storia dove probabilmente questo era pubblicato e non era scaduto). E' vero che anche i crawler dei motori di ricerca, visitando il sito come utenti anonimi, improvvisamente smetteranno di trovare link al documento in quanto il contenuto è sparito dai navigatori e dalle varie viste.

Ma questo basta per dire a Google di smettere di indicizzare quella pagina e farla sparire dagli archivi?
Ovviamente no! I motori di ricerca (e gli utenti, tramite questi) continueranno ad arrivare alla pagina. Google conosceva l'indirizzo e continuerà ad indicizzare quella pagina a meno che non decida spontaneamente di rimuoverla dall'indice. L'unico effetto certo è vederne calare il page rank, visto che i link a questa pagina saranno meno (o addirittura spariti completamente). Niente più.

A dire la verità forse Plone potrebbe esplicitamente avvertire i motori di ricerca di smettere di indicizzare le pagine scadute (basterebbe un tag META ben scritto... che sia un'ottimizzazione SEO da considerare)?

Senza andare a disturbare Big G, ricordate poi che gli utenti usano browser che sono sempre più avanzati. Oltre ai bookmarks (preferiti o segnalibri), possono contare sulla cronologia delle ricerche, l'autocompletamento, ... E' quindi comunque possibile che gli utenti raggiungano la vostra pagina semplicemente perché ci sono già stati (loro direttamente, o un altro utilizzatore dello stesso browser).

Infine: siete certi al 100% che nel resto del sito, o in un altro sito che non è sotto il controllo vostro o dei vostri redattori, qualcuno non abbia inserito un link alla pagina scaduta? Non l'avete per caso inviata per posta? Ad una mailing list? In un forum?

Capire questo vi eviterà di trovarvi nella situazione di credere che i vostri contenuti, i quali non dovrebbero essere più accessibili, siano spiacevolmente ancora pubblici.

Vorrei che i contenuti scaduti venissero archiviati
Questo è possibile? Certamente, ma va sviluppato qualcosa... a Plone va aggiunto qualche componente.
Un paio di idee:
  • un processo esterno chiama Plone ogni ora (o una volta al giorno, magari la notte) e scatena un'operazione che porta i contenuti scaduti allo stato privato
  • come sopra, ma li sposta in una cartella privata
  • aggiungere a tutti i contenuti del sito un controllo (una viewlet andrebbe bene ma probabilmente non sarebbe troppo elegante...) che controlli se l'utente corrente ha poteri per vedere i contenuti scaduti e in caso contrario lanci l'errore di "Non Autorizzato"
Tutte queste soluzioni sono praticabili ed hanno pro e contro.

Mi serve davvero farlo scadere?
La scadenza di News ed Eventi a mio avviso è nella natura stessa di questi contenuti, quindi sì, probabilmente dovete farli scadere.
Altre volte invece ho visto utenti che volevano fondamentalmente archiviare un documento, mettere una data di scadenza a quello vecchio e pubblicare quello nuovo, per poter avere sempre disponibile la vecchia versione.

A questi utenti ricordo che Plone ha un sistema di versionamento integrato. In questo caso non vi serve assolutamente giocare con la data di scadenza. Semplicemente modificando il contenuto e sostituendo le sue informazioni con quelle aggiornate fa sì che i vecchi dati vengano salvati come "vecchia versione".

Imparare ad usare le date di scadenza e pubblicazione
Ora che abbiamo capito come non intendere il funzionamento delle date di scadenza e pubblicazione e come evitare che il loro uso improprio provochi problemi al nostro sito, non ci rimane che tirare le somme e riassumere come vanno usate.

I documenti scaduti, per la loro natura, perdono grandissima visibilità nel sito e questo è un bene. Come dicevo sopra anche Google viene influenzato dalla sparizione di link che portano a quella pagina, quindi anche la ricerca della stessa risulterà più difficile.

Ma nella mente dei vostri redattori questa deve rimanere una pagina pubblica. Dovete chiarir loro questo concetto in modo definitivo. Il documento ha perso importanza, contiene informazioni non più aggiornate, ma se un utente particolare volesse accedervi potrebbe ancora farlo.
Dovete vedere questa caratteristica come un pregio... se il visitatore vuole leggere una news scaduta, relativa ad un evento dell'anno scorso sul vostro sito, perché impedirglielo?

Purtroppo, nella pratica di Plone questo non è un ragionamento che torna completamente. Plone non permette completamente nemmeno questo (a meno di modificare i permessi, come spiegato sotto): come dicevo sopra, non potete cercare il documento scaduto.

Siamo quindi in presenza di una situazione un po' "a mezza via". Se infatti la vostra scelta è consapevole e sapete come Plone si comporterà col vostro contenuto scaduto (e lo accettate), non è quindi facile per il visitatore del sito tornare a quel documento.

L'assurdità della situazione è che Google può ancora guidare i vostri utenti alla pagina scaduta, la ricerca integrata nel sito no.

Come funziona la "magia" dei documenti scaduti?
Questa sezione diventa un po' più tecnica ma servirà a capire perché succede tutto questo.

Nei fatti non appena il comportamento di Plone diventa chiaro mi sono sentito chiedere varie volte "ma non si può correggere"?
Come dicevo all'inizio, questo comportamento è percepito con un bug. Ma è bene chiarire che non è un bug... forse potrà essere definito un limite del sistema, ma è un comportamento che ha una precisa spiegazione.

Quando eseguiamo qualcosa che scatena una ricerca nel catalogo di Plone (e questo capita decine di volte per ogni click che provoca il caricamento di una pagina), per sapere se quel contenuto può essere trovato dall'utente corrente, viene analizzato uno speciale indice del catalogo dal nome allowedRolesAndUsers. Questo indice ha un comportamento complesso che non voglio approfondire; diciamo solo che contiene, per ogni contenuto, tutte le informazioni necessarie per capire se l'utente corrente (anche il visitatore anonimo), con i suoi ruoli e i ruoli dei gruppi di cui fa parte, può vedere un documento.

Lasciando momentaneamente perdere le ricerche nel catalogo, chiediamoci invece come funziona la sicurezza di Plone: come fa Plone a sapere se un certo utente può vedere un contenuto?

Viene verificato ogni ruolo dell'utente corrente per capire se ad uno di questi è associato il permesso View, che identifica proprio il permesso di vedere il contenuto. Questo (e tutti gli altri permessi che di solito interessano) sono gestiti dal workflow di Plone. Avere questo permesso significa vedere, non averlo significa non poter accedere e ottenere quindi la pagina di errore per accesso non autorizzato (nel caso si tenti comunque di visitarne l'URL).

Stato privato del workflow

Lo spostamento del contenuto da stato pubblicato a privato non fa altro che modificare quel valore.

Stato pubblicato del workflow

Il numero di ruoli (e anche l'essere Anonimo è visto come un ruolo) che può vedere il contenuto cambia.
Come già detto, la verifica della sicurezza di un oggetto è perfetta e non da problemi, ma le ricerche non funzionano in questo modo.

Le ricerche di Plone non accedono al contenuto vero e proprio per verificarne lo stato di revisione, ma usano l'indice allowedRolesAndUsers di cui parlavamo sopra.
Questo viene fatto per questioni di velocità: accedere al contenuto è una pratica decine di volte più lenta (e più costosa in termini di memoria) che non usare il catalogo. Quell'indice è una traduzione dello stato di visibilità corrente del contenuto (permesso di View) e viene aggiornato ad ogni modifica del contenuto stesso che ne provochi un cambio.

Non esiste quindi un permesso che si occupi dei contenuti scaduti?
Certo! Il permesso è "Access inactive portal content" (letteralmente: "accesso al contenuto inattivo") ma a differenza dello stato di revisione e del permesso di View, questo risente anche di un'altra variabile non controllata da Plone: il tempo.

Per questo non è possibile avere un ulteriore indice (o modificare il comportamento di allowedRolesAndUser) che memorizzi in qualche modo questa informazione perché l'informazione cambia con lo scorrere del tempo.
Non è nemmeno è possibile verificare il permesso direttamente sul contenuto: questa verifica comporterebbe il caricamento del contenuto stesso (e come detto poco fa è un procedimento che rallenterebbe troppo Plone, se immaginato ripetuto per centinaia o migliaia di documenti).

Quindi come viene usato? Nella pratica se l'utente corrente non ha questo permesso, ai criteri di ricerca viene letteralmente iniettato un nuovo criterio basato sull'indice effectiveRange, quindi l'intervallo di tempo in cui il contenuto è valido.
Ma come viene fatta quindi la verifica del permesso? La modifica di questo permesso non ha effetto sui singoli contenuti (e non va quindi usato nel workflow a meno di non introdurre la viewlet poco elegante ipotizzata sopra) ma può essere gestita a livello di sito.
Questo apre nuovi scenari e possibilità... e purtroppo qualche altra incomprensione.

Modifiche di "Access inactive portal content" da valutare
Abbiamo quindi imparato come questo permesso possa essere usato, ed in effetti influenzi la ricerca, sul sito Plone. Ecco infatti il motivo per cui il Manager del sito vede i documenti scaduti.

Ragionando un attimo, potrete arrivare a capire come questo porti anche ad un altro comportamento di Plone poco amichevole. Vi è mai capitato che l'autore di un contenuto (ruolo Owner) non veda più i propri documenti una volta che sono scaduti?
Ebbene... capita. L'utente è Owner sul contenuto, ma non lo è a livello di sito (l'Owner del sito è ovviamente il suo creatore, quindi l'utente Manager principale che di solito ha id "admin").

Questo ci porta in fretta a valutare le uniche due modifiche al permesso "Access inactive portal content" che vi consiglio di valutare.

Chi ha un account nel sito può vedere documenti scaduti
Nella maggior parte dei siti chi ha un account (di qualunque tipo) è un redattore o un revisore di contenuti. Ha quindi senso valutare se dare al ruolo "Member" (il Collaboratore) il permesso per trovare i contenuti scaduti.
Il Redattore A potrà vedere i contenuti scaduti, suoi e anche del Redattore B... ma questo è un problema? Dopo tutto sarebbero comunque accessibili conoscendone l'URL!

In questo modo evitate lo spiacevole caso del "non vedo i miei contenuti".

Tutti vedono i contenuti scaduti
L'interfaccia di Plone evidenzierebbe comunque l'accesso ad un contenuto che è scaduto, mostrando una scritta "scaduto".

Vista di un contenuto scaduto

Se vi sembra poco, la modifica di questo testo con qualcosa di più "acceso" è semplice.

Dunque perché non valutare se dare questo permesso anche agli anonimi? Otterreste che la ricerca nel sito funzionerà alla pari di Google, trovando di nuovo tutti i contenti pubblici, ma visitandone il dettaglio il visitatore potrà comunque accorgersi che sta accedendo a dati non più aggiornati.
Nota bene: purtroppo perché l'utente anonimo sia in grado di visualizzare quella scritta, come nell'immagine sopra, è anche necessario andare nel pannello di controllo di Plone (sezione "Sicurezza") e selezionare "Consenti a chiunque di vedere le informazioni personali".

Altre combinazioni?
Ci sono in effetti altri casi possibili che potete valutare ma li trovo meno comuni.
Esempio: se la squadra di revisione dei contenuti è unica nel sito (e quindi avete un gruppo di persone che hanno ruolo Reviewer ovunque) allora potreste dare il permesso "Access inactive portal content" a questo ruolo.
Stessa cosa per i contributori (Contributor) e gli Editor... dipende molto dalla dimensione e dalla struttura della vostra redazione, e del vostro sito.

Saturday, July 16, 2011

Hidden (great) secrets inside ATVocabularyManager

ATVocabularyManager is a well-know Plone product developed by BlueDynamics that make simple handling vocabulary values used by your contents directly inside Plone.
That's all: the power of the product is all in this first sentence.

Generic Setup Strike Back
In my personal experience I not commonly found projects that need a Plone user able to change vocabularies, but in the information architecture this is quite common (they call this "Controlled Vocabulary"). So I used ATVocabularyManager a couple of times in the past, but I never became habit to rely on it.

Another thing I don't liked was the unexistent Generic Setup integration.

Recently a customer asked us a new project with some new content types, with a lot of field with controlled vocabularies (with many values inside). Also he explicitly ask to be able to handle and change it in the future.

So I looked back to ATVocabularyManager, hoping that during this time something changed.

A New Hope
This time I note immediately that latest releases (1.5 branch for Plone 3.3, and 1.6 for Plone 4) give us something new.

First of all: for the first time I understand that the name prefix "AT" means obviously "Archetypes", but you can think it as "the way of controlling vocabulary is done using some archetypes contents". This mean that you can use it only if your vocabularies are inside archetypes content types? No!
You can also use it to handle whatever ZCML vocabulary you need (portlet? Dexterity?)

The other thing I found is that now we have Generic Setup integration. Great!

The Generic Setup integration
Right now the integration is not fully complete. Seems that export step is not there, but looking at the code I saw that the import step code (the most important!) is available. The product right now suffer only some missing of documentation.

How the import steps works? Instead of creating new vocabulary content types ("Simple Vocabulary", "Sorted Simple Vocabulary", ...), it is based on the "IMS VDEX Vocabulary File" content.

At first glance this can seem the less user friendly way and most obscure content type (and probably this is true) but going back to information architecture this is probably the best choice, because it's based on an XML international standard for handle vocabularies: the IMS VDEX.
Another good news: this format also support i18n (and also Plone)!

What I needed after this is simple: provide a VDEX compatible XML file. How? Let show a complete example.

How to add the GS support
First of all you need to provide a "vocabularies.xml file" to your profile directory.
The format of the file is as follow:

<?xml version="1.0"?>
<object name="portal_vocabularies" meta_type="ATVocabularyManager">
<object name="test.vdex" /> 
...
</object>

So you need to provide a reference to a vocabulary file for every vocabulary you need to import (.vdex of .xml file extensions are valid ones).

Where to put all vocabulary files? You need also to put at the same level a "vocabularies" directory. Inside this you simply need to put all files.

Now I will show the file format of our test.vdex file.

<vdex xmlns="http://www.imsglobal.org/xsd/imsvdex_v1p0"
orderSignificant="true">
  <vocabIdentifier>test-vocab</vocabIdentifier>
  <vocabName>
    <langstring language="en">A test vocabulary</langstring>
    <langstring language="it">Un vocabolario di test</langstring>
  </vocabName>
  <term>
    <termIdentifier>aaa</termIdentifier>
    <caption>
      <langstring language="en">A value</langstring>
      <langstring language="it">Un valore</langstring>
    </caption>
  </term>
  <term>
    <termIdentifier>bbb</termIdentifier>
    <caption>
      <langstring language="en">Another value</langstring>
      <langstring language="it">Un altro valore</langstring>
    </caption>
  </term>
</vdex>

That's all. We created a vocabulary with two entry inside (foo values are "aaa" and "bbb"). As say above, handle this directly from Plone is not very comfortable (simpler vocabulary type are easier to understand) however this is great for Generic Setup install step!

Internationalization note
A little bug on this approach. Seems that even if you plan to not provide an internationalization for you vocabularies (for example: you only need to provide your italian, spanish or something other translation) you still need to provide also the english one, or the vocabulary content inside ATVocabularyManager control panel will not show you the right title of the vocabulary (something like "unnamed vocabulary" instead of "Un vocabolario di test").
But you can use a trick and duplicate your locale specific translation also for english. Also, put english translation first.

Not very comfortable right now
If you still think it, you are right. Maybe that vdex is a well know standard for handle vocabulary, but build a vocabulary with this XML format can be not very simple.

For example, the customer give us a document (a MS Word attachment, obviously) with a set of lists of values. The easy way is to put all this in some CSV files, where columns are "italian translation" and "english translation".
But after that we need to convert this in the vdex format. How?

What I did is too look on the cheeseshop for a library that can convert a CSV in a VDEX file. What I find is vdexcsv!
Two funny thing about it:
  • It was released something like two hour before I performed that search!
  • The company behind this product is again BlueDynamics!
What this product does? You only need to easy_install it then you will be gifted with a new bash command: csv2vdex.

The documentation is clear: you need to provide a CSV file, some parameter, and you'll obtain your vdex file.

For the example above I used a CSV like this:

"key";"english";"italian"
"aaa";"A value";"Un valore"
"bbb";"Another value";"Un altro valore"

This was named test.csv.

Then I called the script in this way:
csv2vdex test-vocab 'A test vocabulary,Un vocabolario di test' test.csv test.vdex --languages en,it --startrow 1

One last step: the generated vdex file is perfect for all but the root node name. As in the example above you need to have the root node called vdex, but the script generate it as vocabulary. However after contacting Jens (the product creator, that also help me to reach this results) he was in agreement to change this in future releases of vdexcsv.
For now, simply rename the node!
EDIT (2011-08-22): the good guys released vdexcsv 1.1 that is now fully standard compliant, so no more needs to manually rename the node!

Saturday, June 25, 2011

Inno alla Collective

Qualche tempo fa mi è capitata una richiesta da parte di un cliente che al 70% si è rivelata già svolta grazie al lavoro di un'altra azienda (straniera) che produce prodotti Plone e che ha rilasciato il codice.
Fantastico!

Premessa: dato che il discorso che vorrei affrontare qui non è relativo a quel prodotto o a quell'azienda ma è più un discorso di attitudine generale, non scendo nei particolari. Chiamiamoli quindi Prodotto X e Azienda X (e visto che ci siamo, anche Cliente X).

Come innumerevoli altre volte, con soddisfazione ho potuto dire al Cliente X "quello che mi chiedi, Plone (la comunità) te lo offre praticamente già fatto". Soddisfazione sua (che ancora una volta ha capito quanto abbia fatto bene a scegliere Plone nel panorama dei CMS Open Source), soddisfazione mia (che lavoro meno e non devo per l'ennesima volta reinventare la ruota).

Rimane fuori quel piccolo 30%. Il Prodotto X era carente di:
  • traduzione italiana (questo capita ancora troppo spesso)
  • qualche funzionalità aggiuntiva, specifica del Cliente X, che poteva però essere resa una funzionalità generica e riutilizzabile per tutto il Prodotto X.
Aggiungiamo anche un piccolo particolare, ossia che il prodotto era testato (funzionante) solo per Plone 4. Il Cliente X ha una solida installazione Plone 3.3, sono certo che prima o poi passerà a Plone 4 (4.1?) ma senza fretta.
Quindi alla lista sopra, aggiungiamo anche il backport del codice a compatibilità con Plone 3.3.

La Collective
La comunità Plone è fantastica, tra le altre cose, per la sua adesione quasi totale all'uso del repository di codice comune: la Collective.
Raramente lo sviluppatore medio si prende il tempo di andare a cercare nella pagina della documentazione di un prodotto se c'è un link al repository del codice. E' certamente buona norma documentarlo esplicitamente (ne ho già parlato) ma i Ponisti sanno dove andare a trovare il codice.
E' come entrare in una casa di un amico, e questo ti chiede "prenditi pure una birra". La cercheresti fuori dal frigo?

Capita mai che il codice non sia dove me lo aspetto? Certo... è capitato, ma credetemi se vi dico che è un evento talmente raro da potermi dimenticare che alle volte succede!

Quale è stata la sorpresa questa volta? Ovviamente il Prodotto X (che tra le altre cose utilizza il namespace collective.xxx) non era sulla collective.

Digressione
Mi immagino già un certo numero di lettori che a queste prime parole stanno già affilando le armi pensando che questa sia una difesa all'uso di SVN. E' come se già leggessi commenti dire "la collective è roba vecchia, ora è meglio passare a Git".
Non lo nascondo, c'è una recente tendenza di spostare la gestione del codice da Subversion a Git e soprattutto Github.
Se persino il core dello sviluppo Plone si sta muovendo verso questa nuova tecnologia, sono certo al 100% che ci siano ottimi motivi ma qui non si sta parlando di quella collective.

Non deve importare la collective come "scelta tecnologia" ma come "idea": un posto dove lo sviluppatore Plone sappia di poter trovare tutto il codice che gli serve. Un posto dove tutti i plonisti del mondo possano collaborare!

Se poi la collective è un reposiroty di codice fatto con SVN, CVS, Git o come copia manuale del codice... non importa! Se c'è uno standard de-facto, usiamolo. Ad oggi avere il codice Plone sulla collective o su Github, non fa grande differenza!

Torniamo al Prodotto X dell'Azienda X
La mia idea, fin dal principio (e credo sia sempre un'ottima idea) è stata naturalmente quella di:
  • tradurre/far tradurre il Prodotto X in italiano
  • aggiungere la funzionalità richiesta dal Cliente X in modo tale che fosse generica (la nuova funzionalità stessa ovviamente andava ben tradotta)
  • far funzionare il prodotto con Plone 3.3 e superiori (attività banale questa volta)
Come si comincia in questi casi? Ecco un semplice consiglio:
Se possibile, non limitiamoci a sistemare in 10 minuti le cose che ci interessano del Prodotto X, magari sul nostro repository di codice privato o tramite patch, ma forniamo agli sviluppatori dell'Azienda X un branch del loro stesso prodotto.

Dopo questo chiediamo all'Azienda X di integrare le nostre modifiche!
Questo consiglio paga? A mio parere sempre. Un branch o una patch applicata ad un prodotto di terze parti che tenete per voi e per il vostro cliente non porterà a nulla.
La sensazione iniziale è di aver velocemente risolto il problema, ma è un'attitudine che guarda molto avanti nel tempo.

Se avessi preso quella strada avrei impiegato meno tempo (non nascondiamolo), ma prima o poi il Prodotto X avrebbe rilasciato una nuova versione (corretto un bug? Aggiornato il codice ad una nuova versione di Plone? Aggiunto una funzionalità?) e io sarei stato costretto ad "inseguire", a riapplicare la patch alla versione successiva del prodotto.
Ecco che quell'ora o due risparmiate oggi diventano quattro ore domani.

La posizione del codice
L'Azienda X esce però dagli standard, commette quindi un piccolo peccato. Il Prodotto X è open source ma il codice non si trovava sulla collective.

Scopro infine che il prodotto è ben documentato e nella documentazione c'è un link al repository del codice: scopro con perplessità che l'Azienda X ha scelto di usare Mercurial e un servizio simile a Github: Bitbucket.

Non so usare Mercurial e non avevo troppo tempo per imparare quei comandi minimi per usarlo tanto da poter creare un branch.
Quello che alla fine ho fatto è stato quindi mandare all'Azienda X un file di patch con le modifiche.
Per arrivare a quel file, quel prodotto finito, ho dovuto fare qualche tentativo in varie direzioni, che alla fine si sono tradotti in... una copia del prodotto nel nostro repository aziendale (questo perché avere il codice sotto controllo di versione permette di tornare sui propri passi con eleganza e velocità).

L'ironia sta tutta qui: la scelta dell'Azienda X di non usare la collective (e la mia incapacità nell'uso di Mercurial ovviamente!) mi ha obbligato ad una serie di operazioni che mi hanno fatto perdere più tempo del dovuto.
Sono convinto che lo sforzo sia stato ben ripagato: la patch è diventata parte del Prodotto X e questo a mio avviso è sempre un buon risultato.

Ecco quindi un nuovo consiglio:
Se il vostro codice è open source ed è nei vostri obbiettivi rilasciarlo, il posto giusto è la collective (+ Github).
L'Azienda Y preferisce un repository interno. Perché?
Sia ben chiaro: lavorare con l'Azienda X è stato molto fruttuoso. La scelta di usare Bitbucket è, come dicevo, un peccato veniale. Il repository era ad ogni modo pubblico e la patch è stata discussa e integrata con estrema velocità.

Esistono motivi per cui un prodotto rilasciato non debba invece rendere disponibile il codice sulla collective?

Non ne trovo. Nemmeno uno!

Non sto dicendo ovviamente che tutto il vostro codice debba essere rilasciato. Sono convinto che una parte di questo (che sia piccola e grande dipende dal vostro tipo di cliente medio) stia bene in posti riservati perché conterrebbe informazioni che riguardano il Vostro Cliente.
La scelta in questi casi è ovviamente il vostro repository, o quello del vostro cliente (nel caso ne abbia uno).

Ma se rilasciate il prodotto, perché non dovreste usare la collective?

Come dicevo, non trovo grandi motivi, ma posso mostrarvi i motivi per cui invece dovreste rilasciare il codice dove tutti possano contribuire.

Fix di bug
Un plonista usa il vostro prodotto e ha trovato un baco. Potrebbe averlo fissato per voi. Una cosa in meno che dovrete correggere voi.
Aggiornamento ad una versione successiva di Plone
Un plonista vuole usare il vostro prodotto e ne crea un branch che lo rende compatibile con una versione di Plone più recente. Quando uno dei vostri clienti, magari gli stessi per cui già avete sviluppato il prodotto, vi chiederanno "funziona con il nuovo Plone", voi potrete dire di sì... e sopratutto dire che qualcun altro ha pagato per quell'aggiornamento. La comunità funziona.
Aggiunta di una funzionalità
Il plonista, di solito tramite un branch, aggiunge al vostro prodotto una nuova funzionalità. Vi pare poco?!
Nuova lingua
Il plonista straniero ama il vostro prodotto e lo traduce nella sua lingua. Il bacino di utenza del vostro prodotto aumenta e con esso la vostra visibilità, o quella della vostra azienda.

Quanto descritto nei casi qui sopra non è qualcosa che ho letto sul "Manuale Etico del Plonista". Sono tutte cose che ho visto succedere... e non una sola volta!

Siate generici, e riciclate
Ecco forse il consiglio più importante.

Il Cliente Y vi chiederà qualcosa di nuovo. La prima cosa da fare è guardarsi intorno. Capire se c'è giù un prodotto che fa al caso vostro. Se lo trovate, Plone (e voi con lui) farete un figurone.

Se questo non accade ma trovate qualcosa di simile, vale quanto detto fin'ora... se la vostra necessità si avvicina al 60/70% o più a quanto fornito da un altro prodotto, valutate se è possibile estendere quel prodotto, o migliorarlo.

Anche in questo caso Plone (e voi) farete una bella figura.

Capitano invece casi in cui dovete praticamente per forza tornare a sviluppare qualcosa di completamente nuovo.

Prima di iniziare a risolvere il problema del Cliente Y a testa bassa pensate se quanto state sviluppando non possa essere reso più generale, e possa quindi essere rilasciato.

Vi è mai capitato, dopo una nuova richiesta, di dirvi "questa richiesta mi è già stata fatta"? Se sì, e non avete reso la soluzione del problema generale, avete perso un'occasione. Il copia/incolla del codice da un prodotto ad un altro non è una soluzione molto mantenibile...

Non inserite un nuovo pezzo di codice "a caso" dentro ad un altro prodotto. Non conta se questa nuova funzionalità vi sembra minima. Spendendo poco in più potreste ottenere qualcosa di prezioso, qualcosa che in futuro potrebbero usare altre persone, ed essere migliorato da altri.

Come già detto: quello che spendete oggi vi ritornerà domani. Investimento!

Prendo un esempio tra i tanti che potrei citare: collective.portaltabs. Questo ricalca perfettamente quando descritto sopra.
Abbiamo incontrato vari utenti che:
  • volevano il controllo dei link in testata del sito
  • avevano paura (o non avevano i poteri) per accedere alla ZMI
Senza un prodotto apposito l'unica soluzione è accedere alla ZMI ed imparare come fare. E' facile? Lo è certamente per molti di voi, ma l'utente inesperto non ama la ZMI. ZMI è dove nulla è in italiano, dove si possono fare "cose pericolose" e dove le cose non sono per nulla chiare.

Quel prodotto non fa nulla di speciale, ma fa qualcosa che vari nostri clienti hanno richiesto.

Traduzioni
Un altro grande ostacolo al rilascio al pubblico sono le traduzioni del prodotto. La tentazione di partire in quarta scrivendo le varie label e descrizioni della vostra interfaccia grafica direttamente in italiano può essere forte.

Molto spesso quello che si ottiene è quindi un buon prodotto, probabilmente anche riutilizzabile e riutilizzato tra vari clienti e installazioni, ma che non può essere rilasciato pubblicamente.

Le prime volte che vi scontrate con il sistema di traduzioni di Plone può sembrare una procedimento molto lento. Ancora una volta: è un investimento. Ad oggi per me è diventato normale scrivere tutto in inglese e tradurre tutto velocemente alla fine.
Fare le traduzioni dei vostri prodotti in Plone è facile, davvero facile (ho scoperto di recente che con la nuova versione 4.1 non ci sono alcuni problemi che ho riscontrato in questi anni) e richiede un tempo aggiuntivo piuttosto misero.

Se fare questo, il vostro prodotto potrà diventare qualcosa che tutta la comunità potrà riutilizzare ed aiutarvi a mantenerlo.

Plonista avvisato...

Sunday, June 12, 2011

z3c.jbot: magical with your skins resources

This article is the consequence of a small comment of David Glick at Stack Overflow.

If you never met before z3c.jbot, let simply say: this is a toy that commonly make happy the themer of your team. It simplify a lot the customization of a single template inside the Plone universe.
The use of z3c.jbot give you some little performance cost, and commonly you can do stuff without it, however it's use require less development time most of times.

What I learn from the David's comment, is what the official documentation of z3c.bot report in a simple sentence:
CMF objects
Any skin-object (e.g. images, templates) on the file system (directory views) can be overridden.
What is amazing there in the part that say "Any skin-object". Later we will show two great examples.

Complex skins layers subfolders
Maybe Plone 5 (or 6, or 7) won't use the CMF skins technology. However today the CMF skins is still a reality.
What's the problem of skins? Commonly no-one: customize an object in the skins layer is very simple, but only if the object itself isn't in a skin subfolder.
What's the problem with subfolders? Well, the problem is always the customization. To customize the object in the sublayer "/foo/object", you need to customize also the foo folder, putting all this in your personal skins layer. But in this way you also need to customize all other skins object in the foo folder.

So why a product developer sometimes register a single skins layer with subfolders instead of register multiple layers, or keep all resources in a flat simple layer?
Because sometimes you need to put into Plone external resources, that commonly are found as a folder tree structure.
One of the most simple example can be the need of include into Plone an advanced JavaScript product, that is released with static HTML file, images and CSS: all of those resources are already configured to be used keeping this structure. Change this product configuration can be painful.

I will show two main examples:
  • TinyMCE
  • Archetypes widgets
Both examples are available in a collective product there:
https://svn.plone.org/svn/collective/collective.examples.jbotforskins/trunk/

Customizing TinyMCE
Today customizing TinyMCE can be tricky. Maybe you want to change only a little part, maybe a single JavaScript, but the product register a simple skins layer named tinymce, that contain:
langs (dir)
plugins (dir)
resolveuid.py
themes (dir)
tiny_mce_init.js
tiny_mce.js
tiny_mce_plone3.css.dtml
tiny_mce_popup.js
tiny_mce_src.js
tinymce_wysiwyg_support.pt
utils (dir)
wysiwyg_support.pt
I had only a couple of experiences customizing TinyMCE and I was forced to develop a new plugin both the times. Why? Because commonly you need to change something not in the skins layer root (where customizing is easy), but in one of the plugins subsection.

A way can be inject you modification using JavaScript, but this can be really tricky, because TinyMCE WYSIWYG editor is generated using JavaScript and loading an IFRAME (sometimes more that a single IFRAME, if you need to change something in the plonelink or ploneimage plugin).

So, as I sayd, a way is to develop another plugin, that (for example) will replace the basic plonelink ones. You need copy and paste all the plugin code, then you change what you need.
More or less you have created a plugin branch... when another version of TinyMCE will be released, you are forced to check your code for find any changes you need to merge in your plugin...

Let now show how z3c.jbot helps. For example, we simply add an alert JavaScript message inside one of the TinyMCE file. In this way can be really simple to see if our changes are taken (because using TinyMCE we see an alert every time).

We will take only this file into consideration:
Products/TinyMCE/skins/tinymce/plugins/plonelink/editor_plugin.js
How to change only this file without replacing the whole plugin?

First of all we add a Plone product to our buildout. The product can be really simple so we need only a ZCML registration, a folder and out customized JavaScript source.

First of all, we register the z3c.jbot folder.

After that we simply put in the jbot_templates folder the customized JavaScript source. The magic of z3c.jbot is use the name of the customized file for know what to customize. This name must reflect the canonical name of the original file, so:
Products.TinyMCE.skins.tinymce.plugins.plonelink.editor_plugin.js
Very easy! We customized a single file without creating a clone-plugin!

Archetypes widget
Another evil task of the Plone universe is when you need to customize an archetype widget. Why? I bet you know the answer... because all widgets are stored in a skins subfolder:
skins/archetypes/widgets/...
Change the template used for a widget is simple. Create a new template for a widget is easy... however is not so easy to change the default template for all widget of a kind.

Let show an example: imagine you need to customize the file widget, that is all stored in the file.pt template:
Products/Archetypes/skins/archetypes/widgets/file.pt
As before, we can simply put our customized file in the jbot_templates folder. The file name will be:
Products.Archetypes.skins.archetypes.widgets.file.pt
What we can put in the new widget? For example we could like to replace the HTML file upload with a fantastic HTML 5 Drag&Drop Javascript widget. All this can be obtained providing a customized file.pt templates.

Again, this is simple, but only thanks to z3c.jbot!