2015-02-04

Kotti CMS - workflow reference

Yet another blog post about Kotti CMS (http://kotti.pylonsproject.org/): this time I'm going to talk about workflows and security.

Workflows in Kotti are based on repoze.workflow. See http://docs.repoze.org/workflow/ for further information. Basically you can use an xml file (zcml) in order to describe your workflow definition. You can see an example here: https://github.com/Kotti/Kotti/blob/master/kotti/workflow.zcml. A you can see it is quite straightforward adding new states, new transitions, new permissions, etc. You can easily turn your 2-states website workflow into a 3-states website workflow with reviewers or turn Kotti app into an intranet application.

The default workflow definition is loaded from your project .ini file settings (using the kotti.use_workflow settings). The kotti.use_workflow setting's default value is:
kotti.use_workflow = kotti:workflow.zcml
but can change change default workflow for the whole site, register new workflows related to specific content types or disable it as well.

Anyway, if you need to write a Python based CMS-ish application with hierarchical contents, custom content types, workflows, security, global and local ACL (sharing permissions), pluggable and extensible, based on relational databases, developer friendly, with a simple UI, etc... Kotti is your friend!

How to disable the default workflow

Kotti is shipped with a simple workflow implementation based on private and public states. If your particular use case does not require workflows at all, you can disable this feature with a non true value. For example:
kotti.use_workflow = 0

How to override the Kotti's default workflow for all content types

The default workflow is quite useful for websites, but sometimes you need something of different. Just change your workflow setting and point to your zcml file:
kotti.use_workflow = kotti_yourplugin:workflow.zcml
The simplest way to deal with workflow definitions is:
  • create a copy of the default workflow definition
  • customize it (change permissions, add new states, permissions, transitions, initial state and so on)
If your site already has content and you configure it use a workflow for the first time, or you use a different workflow than the one you used before, run the kotti-reset-workflow command to reset all your content's workflow.

How to enable the custom workflow for images and files

Images and files are not associated with the default workflow. If you need a workflow for these items you need to attach the IDefaultWorkflow marker interface.

You can add the following lines in your includeme function:
from zope.interface import implementer
from kotti.interfaces import IDefaultWorkflow
from kotti.resources import File
from kotti.resources import Image
...


def includeme(config):
    ...
    # enable workflow for images and files
    implementer(IDefaultWorkflow)(Image)
    implementer(IDefaultWorkflow)(File)
    ...

How to assign a different workflow to a content type

[UPDATE 20150604:
We are going to use the default workflow for standard content types and a custom workflow for content providing the IBoxWorkflow marker interface. The custom workflow is configurable via .ini configuration files.
Note well: the elector, the config.begin/config.commit and the load_zcml can be omitted. I'll update this section soon.

UPDATE 20150606:
Confirmed: things can be done in a simpler way! So no load_zcml, no config.begin/config.commit, no elector.
See the following video:
]

In this kind of situation you want to use the default workflow for all your types and a different workflow implementation for a particular content type.

You'll need to:
  • create the new workflow definition, with a workflow elector
  • write an elector function that will returns True or False depending if the workflow should be applied (otherwise will win the default default workflow, or better, the first matching workflow without an elector)
  • load manually your zcml file in your includeme function
.ini file (optional)
kotti_boxes.use_workflow = kotti_boxes:workflow.zcml
__init__.py
from pyramid.i18n import TranslationStringFactory
from kotti import FALSE_VALUES


def includeme(config):
    ...
    workflow = config.registry.settings.get('kotti_boxes.use_workflow', None)
    if workflow and workflow.lower() not in FALSE_VALUES:
        config.begin()

        config.hook_zca()
        config.include('pyramid_zcml')
        config.load_zcml(workflow)
        config.commit()

    ...
workflow.py
From the repoze.workflow documentation: """A workflow is unique in a system using multiple workflows if the combination of its type, its content type, its elector, and its state_attr are different than the combination of those attributes configured in any other workflow."""
Depending on how specific is your combination you may need to implement an elector (a function that returns True or False for a given context).
from kotti_boxes.interfaces import IBoxWorkflow


def elector(context):
    return IBoxWorkflow.providedBy(context)
workflow.zcml
<configure xmlns="http://namespaces.repoze.org/bfg"
           xmlns:i18n="http://xml.zope.org/namespaces/i18n"
           i18n:domain="Kotti">
  <include package="repoze.workflow" file="meta.zcml"/>

  <workflow
      type="security"
      name="simple"
      state_attr="state"
      initial_state="private"
      content_types="kotti_boxes.interfaces.IBoxWorkflow"
      elector='kotti_boxes.workflow.elector'
      permission_checker="pyramid.security.has_permission"
      >
    <state name="private" callback="kotti.workflow.workflow_callback">
      <key name="title" value="_(u'Private')" />
      <key name="order" value="1" />
      <key name="inherit" value="0" />
      <key name="system.Everyone" value="" />
      <key name="role:viewer" value="viewbox view" />
      <key name="role:editor" value="viewbox view add edit delete state_change" />
      <key name="role:owner" value="viewbox view add edit delete manage state_change" />
    </state>
    ...

  <transition
      name="private_to_public"
      from_state="private"
      to_state="public"
      permission="state_change" />
  ...

  </workflow>
</configure>

All posts about Kotti

3 comments:

  1. Thank you for this post. It helped me to enable the default workflow for a custom content type.

    ReplyDelete
  2. This howto was included in the official Kotti technical documentation now. See
    http://kotti.readthedocs.org/en/latest/developing/basic/security.html

    ReplyDelete