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:
but can change change default workflow for the whole site, register new workflows related to specific content types or disable it as well.kotti.use_workflow = kotti:workflow.zcml
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 workflowKotti 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 typesThe 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:
The simplest way to deal with workflow definitions is:kotti.use_workflow = kotti_yourplugin:workflow.zcml
- create a copy of the default workflow definition
- customize it (change permissions, add new states, permissions, transitions, initial state and so on)
How to enable the custom workflow for images and filesImages 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.
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
__init__.pykotti_boxes.use_workflow = kotti_boxes:workflow.zcml
workflow.pyfrom 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() ...
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).
workflow.zcmlfrom kotti_boxes.interfaces import IBoxWorkflow def elector(context): return IBoxWorkflow.providedBy(context)
<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
- Pyramid, MySQL and Windows: the good, the ugly and the bad
- Kotti CMS events - insert subobject automatically
- Kotti CMS - how to turn your Kotti CMS into an intranet
- Kotti CMS - how to store arbitrary data with annotations
- How to install Kotti CMS on Windows
- Kotti CMS - avoid types addable in content root
- Kotti CMS - how to create a new content type with an image
- Kotti CMS - workflow reference