46
votes

I've been looking for a framework to simplify the development of reasonably complex workflows in Django applications. I'd like to be able to use the framework to automate the state transitions, permissioning, and perhaps some extras like audit logging and notifications.

I've seen some older information on the same topic, but not too much in the last 2-3 years. The major choices I've heard of are GoFlow (not updated since 2/2009) and django-workflow (seems more active).

Has anyone used these packages? Are they mature and/or compatible with modern (1.3) Django? Are there other options out there worth considering that might be better or better supported?

7

7 Answers

99
votes

Let me give a few notes here as i'm the author of django-fsm and django-viewflow, two projects that could be called "workflow libraries".

Workflow word itself is a bit overrated. Different kind of libraries and software could call themselves "workflow" but have varying functionality. The commonality is that a workflow connects the steps of some process into a whole.

General classification

As I see, workflow implementation approaches can be classified as follows:

  • Single/Multiple users - Whether workflow library automates single user tasks or has permission checking/task assignment options.
  • Sequential/Parallel - Sequential workflow is just a state machine pattern implementation and allows to have single active state at a moment. Parallel workflows allow to have several active tasks at once, and probably have some sort of parallel sync/join functionality.
  • Explicit/Implicit - Whether workflow is represented as a separate external entity, or is weaved into some other class, that main responsibility is different.
  • Static/Dynamic - Static workflows are implemented in python code once and then executed, dynamic workflows typically could be configuring by changing contents of workflow database tables. Static workflows are usually better integrated with the rest of the django infrastructure like views, forms and templates, and support better customization by usual python constructions like class inheritance. Dynamic workflows assume that you have generic interface that can adapt to any workflow runtime changes.

Of these, the first two could be considered gradual differences, but the other two are fundamental.

Specific packages

Here is brief description what we have nowadays in django, djangopackages and awesome-django project list under workflow section:

  • django.contrib.WizardView - implicit, single user, sequential, static the simplest workflow implementation we could have. It stores intermediate state in the hidden form post data.
  • django-flows - explicit, single user, sequential, static workflow, that keeps flow state in external storage, to allow user to close or open page on another tab and continue working.
  • django-fsm - implicit, multi-user, sequential, static workflow - the most compact and lightweight state machine library. State change events represented just as python methods calls of model class. Has rudimentary support for flow inheritance and overrides. Provides slots for associate permission with state transitions. Allows to use optimistic locking to prevent concurrent state updates.
  • django-states - explicit, multi-user, sequential, static workflow with separate class for state machine and state transitions. Transitions made by passing string name of transition to make_transition method. Provides way for associate permission with state transitions. Has a simple REST generic endpoint for changing model states using AJAX calls. State machine inheritance support is not mentioned in the documentation, but class state definition makes it possible with none or few core library modifications.
  • django_xworkflows - explicit, sequential, static workflow with no support for user permission checking, separated class for state machine. Uses tuples for state and transition definitions, makes workflow inheritance support hard.
  • django-workflows - explicit, multi-user, sequential, dynamic workflow storing the state in library provided django models. Has a way to attach permission to workflow transition, and basically thats all.

None of these django state machine libraries have support for parallel workflows, which limits their scope of application a lot. But there are two that do:

  • django-viewflow - explicit, multi-user, parallel, static workflow, with support for parallel tasks execution, complex split and join semantic. Provides helpers to integrate with django functional and class based views, and different background task execution queries, and various pessimistic and optimistic lock strategies to prevent concurrent updates.

  • GoFlow, mentioned in question, tends to be the explicit, multi-user, parallel, dynamic workflow, but it has been forsaken by author for a years.

I see the way to implement dynamic workflow construction functionality on top of django-viewflow. As soon as it is completed, if will close the last and the most sophisticated case for workflow implementation in the django world.

Hope, if anyone was able to read hitherto, now understands the workflow term better, and can do the conscious choice for workflow library for their project.

10
votes

Are there other options out there worth considering that might be better or better supported?

Yes.

Python.

You don't need a workflow product to automate the state transitions, permissioning, and perhaps some extras like audit logging and notifications.

There's a reason why there aren't many projects doing this.

  • The State design pattern is pretty easy to implement.

  • The Authorization rules ("permissioning") are already a first-class part of Django.

  • Logging is already a first-class part of Python (and has been added to Django). Using this for audit logging is either an audit table or another logger (or both).

  • The message framework ("notifications") is already part of Django.

What more do you need? You already have it all.

Using class definitions for the State design pattern, and decorators for authorization and logging works out so well that you don't need anything above and beyond what you already have.

Read this related question: Implementing a "rules engine" in Python

5
votes

It's funny because I would have agreed with S.Lott about just using Python as is for a rule engine. I have a COMPLETELY different perspective now having done it.

If you want a full rule engine, it needs a quite a few moving parts. We built a full Python/Django rules engine and you would be surprised what needs to be built in to get a great rule engine up and running. I will explain further, but first the website is http://nebrios.com.

A rule engine should atleast have:

  • Acess Control Lists - Do you want everyone seeing everything?
  • Key/Value pair API - KVP's store the state, and all the rules react to changed states.
  • Debug mode - Being able to see every changed state, what changed it and why. Paramount.
  • Interaction through web forms and email - Being able to quickly script a web form is a huge plus, along with parsing incoming emails consistently.
  • Process ID's - These track a "thread" of business value. Otherwise processes would be continually overlapping.
  • Sooo much more!

So try out Nebri, or the others I list below to see if they meet your needs.

Here's the debug mode

enter image description here

An auto generated form

enter image description here

A sample workflow rule:

class task_sender(NebriOS):
# send a task to the person it got assigned to
listens_to = ['created_date']

def check(self):
    return (self.created_date is not None) and (self.creator_status != "complete") and (self.assigned is not None)

def action(self):
    send_email (self.assigned,"""
        The ""{{task_title}}"" task was just sent your way!

        Once you finish, send this email back to log the following in the system:

        i_am_finished := true

        It will get assigned back to the task creator to look over.

        Thank you!! - The Nebbs
        """, subject="""{{task_title}}""")

So, no, it's not simple to build a rules based, event based workflow engine in Python alone. We have been at it over a year! I would recommend using tools like

5
votes

A package written by an associate of mine, django-fsm, seems to work--it's both fairly lightweight and sufficiently featureful to be useful.

5
votes

I can add one more library which supports on the fly changes on workflow components unlike its equivalents.

Look at django-river

It is now with a pretty admin called River Admin

1
votes

ActivFlow: a generic, light-weight and extensible workflow engine for agile development and automation of complex Business Process operations.

You can have an entire workflow modeled in no time!

Step 1: Workflow App Registration

WORKFLOW_APPS = ['leave_request']

Step 2: Activity Configuration

from activflow.core.models import AbstractActivity, AbstractInitialActivity
from activflow.leave_request.validators import validate_initial_cap

class RequestInitiation(AbstractInitialActivity):
    """Leave request details"""
    employee_name = CharField(
        "Employee", max_length=200, validators=[validate_initial_cap])
    from = DateField("From Date")
    to = DateField("To Date")
    reason = TextField("Purpose of Leave", blank=True)

    def clean(self):
        """Custom validation logic should go here"""
        pass

class ManagementApproval(AbstractActivity):
    """Management approval"""
    approval_status = CharField(verbose_name="Status", max_length=3, choices=(
        ('APP', 'Approved'), ('REJ', 'Rejected')))
    remarks = TextField("Remarks")

    def clean(self):
        """Custom validation logic should go here"""
        pass

Step 3: Flow Definition

FLOW = {
'initiate_request': {
    'name': 'Leave Request Initiation',
    'model': RequestInitiation,
    'role': 'Submitter',
    'transitions': {
        'management_approval': validate_request,
    }
},
'management_approval': {
    'name': 'Management Approval',
    'model': ManagementApproval,
    'role': 'Approver',
    'transitions': None
    }
}

Step 4: Business Rules

def validate_request(self):
    return self.reason == 'Emergency'
1
votes

I migrate the django-goflow from django 1.X -python 2.X to fit for django 2.X - python 3.x, the project is at django2-goflow