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.
- 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
 
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 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.
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
 
- Safe, verifiable transitions between states
 - Conditions for the transition
 - Clear side effects from state transitions
 - DRY
 
- declarative transitions and conditions (via decorators)
 - specialized field to contain state
 
https://github.com/kmmbvnr/django-fsm
P.S. RoR has similar apps too
- Specialized CharField
 - Set 
protected=True- 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,
)
(alternatives FSMIntegerField, FSMKeyField)
@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
 
./manage.py graph_transitions -o example-graph.png fsm_example.PublishableModel
Something a bit more complex:
https://github.com/gadventures/django-fsm-admin
- submit row
 - logging history
 - noting what's required to change state (messages)
 
https://github.com/gizmag/django-fsm-log
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.



Hi Nagy,
Thanks for your sharing.
I don't understand how to use the handle the signals from django-fsm though.
My model is as follows:
Then when I "published" an article using django-fsm-admin, I got an error:
How do I use these callbacks?