Skip to content

Instantly share code, notes, and snippets.

@vigo
Forked from Nagyman/workflows-in-django.md
Created February 26, 2019 14:20
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save vigo/421293ac341e26635f8e2274e8643329 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.

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