A Guide to Managing Finite State Machine Using Django FSM

Nagesh Dhope

30 May 2017

Imagine an application like JIRA with complex workflow. Building such application needs a support for managing finite state machine.Well, if you are building your application with Django, Django FSM provides you out of the box support for managing finite state machine.

Let’s assume that our application has the following workflow for a Task.

workflow

The basic model for a Task is as follows:

from django.db import models
class Task(models.Model):
   title = models.CharField(max_length=100, null=False)
   description = models.TextField()

Now, let’s see how can we build this using Django FSM.

 

Installation Process

$ pip install django-fsm

Or, you can install latest git version

$ pip install -e git://github.com/kmmbvnr/django-fsm.git#egg=django-fsm

 

Define States and State Field in Model

You have to define an FSMState field. On this field, Django FSM will perform transitions.

Define these using FSMField.

from django.db import models
from django_fsm import FSMField
class Task(models.Model):
   title = models.CharField(max_length=100, null=False)
   description = models.TextField()
   state = FSMField()

 

We defined FSMState field but we didn’t associate any states to that field. We can do that as follows:

from django.db import models
from django_fsm import FSMField
STATES = ('Open', 'In Progress', 'Resolved', 'Re Opened', 'Closed')
STATES = list(zip(STATES, STATES))
class Task(models.Model):
   title = models.CharField(max_length=100, null=False)
   description = models.TextField()
   state = FSMField(default=STATES[0], choices=STATES)

Creating Transitions

Django FSM provides a decorator called transition. Once you decorate the model method with transition decorator, you can call the model method to perform transition on each object of a model.

from django.db import models
from django_fsm import FSMField, transition
STATES = ('Open', 'In Progress', 'Resolved', 'Re Opened', 'Closed')
STATES = list(zip(STATES, STATES))
class Task(models.Model):
   ...
   state = FSMField(default=STATES[0], choices=STATES)
    @transition(field=state, source=['Open', 'Re Opened'], target='In Progress')
    def start(self):
    	"""
         	This method will contain the action that needs to be taken once the
         	state is changed. Such as notifying Users.
         	"""
        pass
    @transition(field=state, source='In Progress', target='Resolved')
    def resolve(self):
        """
         	This method will contain the action that needs to be taken once the
         	state is changed. Such as notifying Users
         	"""
        pass

 

Source parameter accepts a list of states or an individual state. You can use * for the source, to allow switching to the target from any state. The field parameter accepts both the string attribute name or the actual field instance.

Changing State of Object

To change the state of an object, call the model method which is decorated with transition decorator.

task = Task.objects.get(pk=task_id)
task.start()

If start() gets executed successfully without raising any exceptions, state of task object will get updated but it will not be saved into a database. Call save() method to save the object in the database.

task.save()

Adding Conditions on Transitions

There might be situations wherein you want a check of the certain condition prior to performing the transition. You do so in Django FSM using Conditions parameter of transition decorator. Conditions must be a list of functions taking one argument(model instance). The function/method must return,True or False.
A conditioning method can be a normal python function.

def can_close(instance):
   """ Return True or False, depending upon some condition """
   pass

 

or it can be a model method.

def can_close(self):
   """ Return True or False, depending upon some conditions """
   pass

 

Use conditions like this:

@transition(field=state, source='Resolved', target='Closed', conditions=[can_close])
def close(self):
   """ This method will contain the action that needs to be taken once the state is changed. """
   pass

Handling Exception in Transition Method

Django FSM provides a way to set a fall-back state in case transition method raises an exception.

@transition(field=state, source='Resolved', target='Closed', on_error='failed')
def close(self):
   """ Some exception could happen here """
   pass

Permission-Based Transition

Transition decorator has permission parameter that can be used to check permission prior to transition taking place. Permission accepts a permission string, or callable that expects instance and user arguments and returns True if the user can perform the transition.

@transition(field=state, source='Resolved', target='Closed', permission='myapp.can_change_task')
def close(self):
   """ This method will contain the action that needs to be taken once state is changed. """
   pass

or

@transition(field=state, source='Resolved', target='Closed',
	 permission=lambda instance, user: not user.has_perm('myapp.can_change_task'))
def close(self):
   """ This method will contain the action that needs to be taken once the state is changed. """
   pass

 

You can check permission with the has_transition_permission method.

from django_fsm import has_transition_perm
def publish_view(request, task_id):
   task = get_object_or_404(Task, pk=task_id)
   if not has_transition_perm(task.close, request.user):
       raise PermissionDenied
   task.close()

State Transition Diagram

You can generate a state transition diagram based on transition methods.
You need the pip install graphviz>=0.4library and add django_fsm to your INSTALLED_APPS

INSTALLED_APPS = (
    ...
    'django_fsm',
    ...
)

 

Now run following command.

$ ./manage.py graph_transitions -o task_transitions.png myapp.Task

Summary

Django FSM has everything that you need to manage finite state machine in your application. In addition to this if want to perform transitions from admin site use Django-FSM-admin. Also, you can maintain transition logs using Django-FSM-log.


Have a question?

Need Technology advice?

Connect

+1 669 253 9011

contact@hashedin.com

facebook twitter linkedIn youtube