Skip to content

Instantly share code, notes, and snippets.

@Nagyman
Last active November 20, 2024 01:08
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(
    default=1,
    choices=[(1, "draft"), (2, "approved"), (3, "published")]
)

Define methods to change state:

def publish(self):
    self.state = 3
    email_sombody(self)
    self.save()

def approve(self):
    self.state = 2
    self.save()

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

django-fsm

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

https://github.com/kmmbvnr/django-fsm

P.S. RoR has similar apps too

FSMField

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

Example

state = FSMField(
    default=State.DRAFT,
    verbose_name='Publication State',
    choices=State.CHOICES,
    protected=True,
)

(alternatives FSMIntegerField, FSMKeyField)

Transition decorator

@transition(field=state, source=[State.APPROVED, State.EXPIRED],
    target=State.PUBLISHED,
    conditions=[can_display])
def publish(self):
    '''
    Publish the object.
    '''
    email_the_team()
    update_sitemap()
    busta_cache()

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

Extras

Graphing state transitions

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

Something a bit more complex:

django-fsm-admin

https://github.com/gadventures/django-fsm-admin

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

django-fsm-log

https://github.com/gizmag/django-fsm-log

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

Alternatives?

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

Fin

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

@wnmurphy
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.

@blueyed
Copy link

blueyed commented Jun 1, 2022

@CARocha
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