Skip to content

Instantly share code, notes, and snippets.

Last active January 27, 2024 08:29
Show Gist options
  • Save Nagyman/9502133 to your computer and use it in GitHub Desktop.
Save Nagyman/9502133 to your computer and use it in GitHub Desktop.
Workflows in Django

Workflows (States) in Django

I'm going to cover a simple, but effective, utility for managing state and transitions (aka workflow). We often need to store the state (status) of a model and it should only be in one state at a time.

Common Software Uses

  • Publishing (Draft->Approved->Published->Expired->Deleted)
  • Payments
  • Account Authorization (New->Active->Suspended->Deleted)
  • Membership (Trial->Paid->Cancelled)
  • Quality Assurance, Games
  • Anything with a series of steps

Definitely avoid ...

Booleans for states

  • is_new
  • is_active
  • is_published
  • is_draft
  • is_deleted
  • is_paid
  • is_member
  • is_*

Mutually exclusive states ... sort of finite, but the number of states increases with each bool:

  • 2 bools = 2^2 = 4 states
  • 3 bools = 2^3 = 8 states
  • etc (2^N)

Brittle and too many states to check.

Finite State Machine

  • finite list of states
  • one state at a time; the current state
  • transition state by triggering event or condition

The behavior of state machines can be observed in many devices in modern society which perform a predetermined sequence of actions depending on a sequence of events with which they are presented.

Simple approach ...

CharField with defined choices

state = CharField(
    choices=[(1, "draft"), (2, "approved"), (3, "published")]

Define methods to change state:

def publish(self):
    self.state = 3

def approve(self):
    self.state = 2

Better, but ...

  • not enforced
    • Can I go from draft to published, skipping approval?
    • What happens if I publish something that's already published?
  • repetitive
  • side-effects mix with transition code

Some Goals

  • Safe, verifiable transitions between states
  • Conditions for the transition
  • Clear side effects from state transitions
  • DRY


  • declarative transitions and conditions (via decorators)
  • specialized field to contain state

P.S. RoR has similar apps too


  • Specialized CharField
  • Set protected=True
    • to prevent direct/accidental manipulation
    • forces use of transition methods
    • raises an AttributeError "Direct state modification is not allowed"


state = FSMField(
    verbose_name='Publication State',

(alternatives FSMIntegerField, FSMKeyField)

Transition decorator

@transition(field=state, source=[State.APPROVED, State.EXPIRED],
def publish(self):
    Publish the object.

What does this get us?

  • defined source and target states (valid transitions)
  • a method to complete the transition and define side-effects
  • a list of conditions (aside from state), that must be met for the transition to occur


Graphing state transitions

./ graph_transitions -o example-graph.png fsm_example.PublishableModel

Something a bit more complex:


  • submit row
  • logging history
  • noting what's required to change state (messages)


If you'd like your state transitions stored in something other than the admin history.


Not much out there. django-fsm has the most activity.


Craig Nagy @nagyman G Adventures - Software Engineering, eComm Mgr.

Copy link

Would love to see some explicit documentation, i.e. what is source? what is target? How do I change the state?

It wasn't clear that I still need to call .save() after calling the helper fn, or that the helper fn doesn't detect a state change if I directly set the value; it only provides a home for side effects + enforces restrictions on state change directions.

Copy link

blueyed commented Jun 1, 2022

Copy link

CARocha commented Mar 14, 2023

You say "P.S. RoR has similar apps too !! " which?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment