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)
- Account Authorization (New->Active->Suspended->Deleted)
- Membership (Trial->Paid->Cancelled)
- Quality Assurance, Games
- Anything with a series of steps
Definitely avoid ...
Booleans for states
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?
- side-effects mix with transition code
- Safe, verifiable transitions between states
- Conditions for the transition
- Clear side effects from state transitions
- declarative transitions and conditions (via decorators)
- specialized field to contain state
P.S. RoR has similar apps too
- Specialized CharField
- to prevent direct/accidental manipulation
- forces use of transition methods
- raises an AttributeError "Direct state modification is not allowed"
state = FSMField( default=State.DRAFT, verbose_name='Publication State', choices=State.CHOICES, protected=True, )
@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
Graphing state transitions
./manage.py 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.