Skip to content

Instantly share code, notes, and snippets.

@cwisecarver
Last active August 13, 2018 23:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cwisecarver/7335d99f04fa412a1004c72e2b979e34 to your computer and use it in GitHub Desktop.
Save cwisecarver/7335d99f04fa412a1004c72e2b979e34 to your computer and use it in GitHub Desktop.
Lookit workflow description

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.

@cwisecarver
Copy link
Author

wolfkrow

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