# 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 ... not _finite_ ... combinatorial ## 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. - http://en.wikipedia.org/wiki/Finite-state_machine ## 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 ![](http://i.imgur.com/RxHsaDl.png) Something a bit more complex: ![](http://i.imgur.com/DTnM9CW.png) ### django-fsm-admin https://github.com/gadventures/django-fsm-admin * submit row * logging history * noting what's required to change state (messages) ![](http://i.imgur.com/Dw76BZ9.png) ### 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. * https://github.com/rbarrois/xworkflows * https://bitbucket.org/elbeanio/django-statemachine ## Fin Craig Nagy @nagyman G Adventures - Software Engineering, eComm Mgr.