44. Using starzel.votable_behavior in ploneconf.site#

In this part you will:

  • Integrate your own third party package into your site.

Topics covered:

  • Permissions

  • Workflows

  • We want to use the votable behavior, so that our reviewers can vote.

  • To show how to use events, we are going to auto-publish talks that have reached a certain rating.

  • We are not going to let everybody vote everything.

First, we must add our package as a dependency to ploneconf.site.

We do this in two locations. The egg description setup.py needs starzel.votable_behavior as a dependency. Else no source code will be available.

The persistent configuration needs to be installed when we install our site. This is configured in GenericSetup.

We start by editing setup.py

 1...
 2zip_safe=False,
 3install_requires=[
 4    'setuptools',
 5    'plone.app.dexterity [relations]',
 6    'plone.app.relationfield',
 7    'plone.namedfile [blobs]',
 8    'starzel.votable_behavior',
 9    # -*- Extra requirements: -*-
10],
11...

Next up we modify profiles/default/metadata.xml

1<metadata>
2  <version>1002</version>
3    <dependencies>
4      <dependency>profile-starzel.votable_behavior:default</dependency>
5    </dependencies>
6</metadata>

... only:: not presentation

What a weird name. profile- is a prefix you will always need nowadays. Then comes the egg name, and the part after the colon is the name of the profile. The name of the profile is defined in zcml. So far I've stumbled over only one package where the profile directory name was different than the GenericSetup Profile name.

Now the package is there, but nothing is votable. That is because no content type declares to use this behavior. We can add this behavior via the control panel, export the settings and store it in our egg. Let's just add it by hand now.

To add the behavior to talks, we do this in profiles/default/types/talk.xml

Note

After changing the metadata.xml you have to restart your site since unlike other GenericSetup XML files that file is cached.

Managing dependencies in metadata.xml is good practice. We can't rely on remembering what we'd have to do by hand. In Plone 4 we should also have added <dependency>profile-plone.app.contenttypes:plone-content</dependency> like the documentation for plone.app.contenttypes recommends.

Read more: https://5.docs.plone.org/develop/addons/components/genericsetup.html#dependencies

1<property name="behaviors">
2  <element value="plone.dublincore"/>
3  <element value="plone.namefromtitle"/>
4  <element value="starzel.voting"/>
5</property>

... only:: not presentation

Now you can reinstall your Plone site.

Everybody can now vote on talks. That's not what we wanted. We only want reviewers to vote on pending Talks. This means the permission has to change depending on the workflow state. Luckily, workflows can be configured to do just that. Since Talks already have their own workflow we also won't interfere with other content.

First, we have to tell the workflow that it will be managing more permissions. Next, for each state we have to configure which role has the two new permissions.

That is a very verbose configuration, maybe you want to do it in the web interface and export the settings. Whichever way you choose, it is easy to make a simple mistake. I will just present the XML way here.

The config for the Workflow is in profiles/default/workflows/talks_workflow.xml

 1<?xml version="1.0"?>
 2<dc-workflow workflow_id="talks_workflow" title="Talks Workflow" description=" - Simple workflow that is useful for basic web sites. - Things start out as private, and can either be submitted for review, or published directly. - The creator of a content item can edit the item even after it is published." state_variable="review_state" initial_state="private" manager_bypass="False">
 3 <permission>Access contents information</permission>
 4 <permission>Change portal events</permission>
 5 <permission>Modify portal content</permission>
 6 <permission>View</permission>
 7 <permission>starzel.votable_behavior: View Vote</permission>
 8 <permission>starzel.votable_behavior: Do Vote</permission>
 9 <state state_id="pending" title="Pending review">
10  <description>Waiting to be reviewed, not editable by the owner.</description>
11  ...
12  <permission-map name="starzel.votable_behavior: View Vote" acquired="False">
13   <permission-role>Site Administrator</permission-role>
14   <permission-role>Manager</permission-role>
15   <permission-role>Reviewer</permission-role>
16  </permission-map>
17  <permission-map name="starzel.votable_behavior: Do Vote" acquired="False">
18   <permission-role>Site Administrator</permission-role>
19   <permission-role>Manager</permission-role>
20   <permission-role>Reviewer</permission-role>
21  </permission-map>
22  ...
23 </state>
24 <state state_id="private" title="Private">
25  <description>Can only be seen and edited by the owner.</description>
26  ...
27  <permission-map name="starzel.votable_behavior: View Vote" acquired="False">
28   <permission-role>Site Administrator</permission-role>
29   <permission-role>Manager</permission-role>
30  </permission-map>
31  <permission-map name="starzel.votable_behavior: Do Vote" acquired="False">
32   <permission-role>Site Administrator</permission-role>
33   <permission-role>Manager</permission-role>
34  </permission-map>
35  ...
36 </state>
37 <state state_id="published" title="Published">
38  <description>Visible to everyone, editable by the owner.</description>
39  ...
40  <permission-map name="starzel.votable_behavior: View Vote" acquired="False">
41   <permission-role>Site Administrator</permission-role>
42   <permission-role>Manager</permission-role>
43  </permission-map>
44  <permission-map name="starzel.votable_behavior: Do Vote" acquired="False">
45  </permission-map>
46  ...
47 </state>
48  ...
49</dc-workflow>

We have to reinstall our product again.

But this time, this is not enough. Permissions get updated on workflow changes. As long as a workflow change didn't happen, the talks have the same permissions as ever.

Luckily, there is a button for that in the ZMI Workflow view Update security settings.

After clicking on this, only managers and Reviewers can see the Voting functionality.

Lastly, we add our silly function to auto-approve talks.

You quickly end up writing many event handlers, so we put everything into a directory for eventhandlers.

For the events we need an events directory.

Create the events directory and add an empty events/__init__.py file.

Next, register the events directory in configure.zcml

1<include package=".events" />

Now write the ZCML configuration for the events into events/configure.zcml

 1<configure
 2    xmlns="http://namespaces.zope.org/zope">
 3
 4  <subscriber
 5    for="starzel.votable_behavior.interfaces.IVotable
 6         zope.lifecycleevent.IObjectModifiedEvent"
 7    handler=".votable.votable_update"
 8    />
 9
10</configure>

This looks like a MultiAdapter. We want to get notified when an IVotable object gets modified. Our method will receive the votable object and the event itself.

And finally, our event handler in events/votable.py

 1from plone.api.content import transition
 2from plone.api.content import get_state
 3from starzel.votable_behavior.interfaces import IVoting
 4
 5
 6def votable_update(votable_object, event):
 7    votable = IVoting(votable_object)
 8    if get_state(votable_object) == 'pending':
 9        if votable.average_vote() > 0.5:
10            transition(votable_object, transition='publish')

We are using a lot of plone api here. Plone API makes the code a breeze. Also, there is nothing really interesting. We will only do something if the workflow state is pending and the average vote is above 0.5. As you can see, the transition Method does not want the target state, but the transition to move the state to the target state.

There is nothing special going on.