In a newsroom where several operators work on the same archive, the state of a piece of content — who sees it, who edits it, whether it is published — cannot stay implicit in application code. It has to be modelled as a state machine, with transitions constrained by permissions. Zope CMF (Content Management Framework) does this through the DCWorkflow product, and the detail I care about is that the workflow lives outside the content types, not inside them.
Context
The Content Management Framework is a layer Zope Corporation builds on top of Zope to keep content-management logic separate from object publishing proper. Zope, on its own, publishes Python objects persisted in ZODB (Zope Object Database) by traversing the object tree through URLs; CMF adds the notion of a portal, of a typed content type, and a set of tools registered in the portal that govern cross-cutting behaviour: portal_catalog for indexing, portal_membership for users, portal_workflow for editorial states.
The concrete problem that concerns me here is the third one. An editorial archive holds content in different states at the same moment: a draft only the author sees, an article awaiting review, a public article. With several editors on the same ZODB — concurrent access guaranteed by the database’s transactions and, where scaling is needed, by ZEO (Zope Enterprise Objects), which lets several processes share the same storage — these states have to be made explicit and their transitions controlled, otherwise access control degenerates into a handful of if statements scattered across the rendering code.
Architecture
DCWorkflow models each workflow as a finite state machine defined through the web, without writing a line of Python. There are four ingredients:
- states — for example
private,pending,published; - transitions — for example
submit(fromprivatetopending),publish(frompendingtopublished),retract(back toprivate); - guards — the condition that enables a transition, expressed as a required permission or role;
- variables and history — values tied to the workflow instance and a chronological log of the transitions applied to that content.
The architectural point is that the state is not an attribute of the content type. A Document object does not know it is pending: it is portal_workflow that keeps the association between the object and its current state, and rewrites the object’s local permissions on every transition. When an article moves to published, the workflow grants the View permission to the Anonymous role; when it goes back to private, it revokes it. Runtime access control stays Zope’s native one — checking the View permission against the current user’s roles — but the object’s permission-to-role map is produced by the state machine instead of being written by hand.
It follows that adding a new content type does not force you to rewrite the publication logic. The new type is registered in portal_types, an existing workflow is bound to it, and it inherits states, transitions and permission rules without a line of state code. The workflow definition is configuration data, persisted in ZODB like everything else, exportable and re-importable.
The critical point
What makes the model sound is that the transition guard and the runtime permission are the same security primitive. A publish transition is protected by the Review portal content permission; that permission is granted to the Reviewer role; the same permission check that decides whether a user may see /folder/article decides whether they may run the publish transition on it. There are not two authorisation systems to keep aligned — one for the workflow, one for rendering — because there is only one.
Per-instance history is the other half. Each transition records who ran it, when, from which state to which, with an optional comment. For a multi-editor archive it is the minimal substrate of editorial traceability: there is no need for a separate audit system, because the state path of every piece of content is already materialised next to the content itself.
Implications
One practical consequence concerns syndication. If publication states are explicit, the subset of published content is a well-defined query against portal_catalog, and that subset is what gets exposed to the outside. The natural shape for exposing it in 2001 is RSS 1.0 — RDF Site Summary in the revision the RSS-DEV Working Group publishes in December 2000 — an XML application conforming to the W3C RDF specification, carrying descriptive metadata in the Dublin Core module. The elements of the Dublin Core Metadata Element Set 1.1 (author in dc:creator, date in dc:date, title, description) map one-to-one onto the metadata CMF already keeps for each piece of content, because CMF’s own metadata model is itself indebted to Dublin Core. An article that enters the published state becomes, with no further modelling, an exportable rdf:Description.
The other implication concerns the portability of the editorial process. Since the workflow is data and not code, two different installations can share the same state schema and diverge in their guards — the same newsroom can have a private → pending → published chain on one site and a direct private → published on another, without forking the application.
On this model noze builds InFlow CMS, an Open Source system on Zope/CMF with an asynchronous workflow and RDF export, described in the insight: https://www.noze.it/en/insights/inflow-cms/.
Limits
DCWorkflow models a per-content state chain well. It does not natively model dependencies between content: “publish the article only when its cover image is public too” is not a transition, it is a constraint between instances, and has to be expressed as a guard with a script that queries other objects, and so some of the application logic the model meant to keep out comes back into the state machine.
There is also a concurrency cost. ZODB’s storage is optimistic: two editors modifying the same object can hit a ConflictError, resolved by retrying the transaction. For publication this happens rarely, because state transitions are short and seldom on the same content; for simultaneous editing of the same document the state model is of no help — that is not its job — and application discipline is needed on top of it (explicit locks, or finer object granularity).
Finally, the through-the-web workflow is convenient to prototype and inconvenient to version. Living in ZODB as configuration, it sits in no file under version control until it is deliberately exported; for an editorial process that changes over time, periodic export of the definition is a practice to impose on yourself, not an automatic one.
- https://web.resource.org/rss/1.0/spec
- https://www.dublincore.org/specifications/dublin-core/dces/1999-07-02/
- https://www.w3.org/TR/1999/REC-rdf-syntax-19990222/
Cover image: Finite state machine diagram: two circular states “Opened” and “Closed” linked by transition arrows labeled “open door” and “close… — diagram by Macguy314, reworked by Perhelion, public domain — https://commons.wikimedia.org/wiki/File:Finite_state_machine_example_with_comments.svg