Django Finite State Machine (FSM)

A finite state machine defines a set of states and the transitions between them. viewflow.fsm enforces these rules at runtime — a method only runs when the current state allows it. It is the maintained successor to the original django-fsm library.

FSM fits simple, sequential workflows. For parallel execution or complex branching, use the BPMN workflow engine instead.

Quick Start

The State class holds a value from a Python enum or Django choices class. You can’t change it with direct assignment—only through transitions.

from enum import Enum
from viewflow.fsm import State

class States(Enum):
   NEW = 1
   DONE = 2
   HIDDEN = 3


class MyFlow(object):
   state_field = State(States, default=States.NEW)

   @state_field.transition(source=States.NEW, target=States.DONE)
   def complete():
       pass

   @state_field.transition(source=State.ANY, target=States.HIDDEN)
   def hide():
       pass

flow = MyFlow()
flow.state_field == States.NEW  # True
flow.state_field = States.DONE  # Raises AttributeError

flow.complete()
flow.state_field == States.DONE  # True

flow.complete()  # Raises TransitionNotAllowed

The @transition decorator adds runtime checks. Methods can only run when the object is in the right state.

Coming from django-fsm?

viewflow.fsm is the maintained successor to the original django-fsm library. The idea is the same — a state value plus @transition-guarded methods — with one structural change: the state stays a plain model field, and the transitions live in a separate flow class instead of on the model.

# Before — django-fsm: state and transitions live on the model
from django_fsm import FSMField, transition

class Report(models.Model):
    state = FSMField(default="NEW")

    @transition(field=state, source="NEW", target="APPROVED")
    def approve(self):
        ...

# After — viewflow.fsm: plain field on the model, transitions in a flow class
class Report(models.Model):
    state_field = models.CharField(
        max_length=150, choices=ReportState.choices, default=ReportState.NEW)

class ReportFlow(object):
    state_field = fsm.State(ReportState, default=ReportState.NEW)

    def __init__(self, report):
        self.report = report

    @state_field.getter()
    def _get(self):
        return self.report.state_field

    @state_field.setter()
    def _set(self, value):
        self.report.state_field = value

    @state_field.transition(source=ReportState.NEW, target=ReportState.APPROVED)
    def approve(self):
        ...

Keeping the machine in its own class separates transition rules from the model definition and lets you reuse plain TextChoices. See Wrapping Django Models for the full pattern.

FAQ

Is django-fsm still maintained?

The original django-fsm is in maintenance mode. viewflow.fsm is its actively developed successor and the recommended choice for new Django projects.

How do I add a state machine to a Django model?

Store the state in a normal CharField with TextChoices, then wrap the model in a flow class whose fsm.State descriptor declares the allowed transitions. See Wrapping Django Models.

What is the difference between FSM and BPMN in Viewflow?

FSM models one object moving through sequential states. BPMN models a whole process with parallel branches and multiple participants. Use FSM for simple status fields and the BPMN workflow engine for multi-step processes.