We used fysom at the Fool for our CMS. It is a super minimal finite state machine implementation in python. We built all our transitions and states in the database and managed them in the django admin. That was fine for a while but most of the tooling we had to build ourselves and migrating workflows between environments was a PITA. It also meant that while we could change around the order of existing workflow states in the admin if we wanted to add a callback that didn't exist we still had to do a software release so we realized we might as well just define the whole thing in python.
I did a bit of a rummage around for lookit, knowing that we wanted a workflow for study approval. I settled on transitions. It's still pretty minimal but has way more tooling than fysom and integrates with django's models fairly simply. If we did want to move the workflow to the database at some point because it's just a simple python dictionary we could still do that and generate it at app start.
Here's my workflow definition.
# snip
transitions = [
{
'trigger': 'submit', # name of a trigger
'source': 'created', # valid source states (optionally can be a list)
'dest': 'submitted', # destination state
'after': 'notify_administrators_of_submission', # callbacks to be run after transition
},
# /snip
That basically says that there is a valid trigger called submit
when things are in the state created
, the state they should go to is submitted
and after they make it to that state call notify_administrators_of_submission
. There are some other options, I think before
is valid which will run before state change and an exception will invalidate the transition.
Here is where I hook the state machine up to the model.
self.machine = Machine(
self,
states=workflow.states, # set the list of states
transitions=workflow.transitions, # set the transitions
initial=self.state, # pull the initial state when an instance is created from the database field state
send_event=True, # send the transition event to the callback(s). I do this to get the active user
before_state_change='check_permission', # call this method before every state change
after_state_change='_finalize_state_change' # call this method after every state change
)
Here are my mocked out callbacks.
Since state
is the name of both the state on the FSM and on the model when the model is saved in _finalize_state_change
the model field state
is set to the value of the state property on the FSM.
Here is an example of my transitioning between states. I use get_permitted_triggers
to get a list of valid trigger names for a user. This is terribly hacky right now, I intend to refactor it. It just got it to a demoable state. I use that list in a template to draw buttons with trigger names. On form submit the post
method is called and the transition method is called.
Transitions also has the ability to draw a svg/png/etc graph of your workflow which makes it fairly easy to visualize where you've gone wrong. See the comment below.