Skip to main content

Complete Example

A full approval workflow combining statuses, form-based transitions, conditions, done methods, and tags.

Scenario

A patient record goes through an approval process: Draft → Pending Approval → Approved / Rejected → Active

Only Managers can approve or reject. Rejection sends the record back to draft for correction. Managers can also tag records as urgent.

workflow.py

from django import forms
from django.utils import timezone
from ...packages.workflow.base.engine import WorkflowBase
from ...packages.crud.forms import BaseSimpleForm
from .models import Patient


class ApprovalForm(BaseSimpleForm):
notes = forms.CharField(label="Approval Notes", required=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.declared_fields["notes"].extra_ui_schema = {
"ui:widget": "TextareaFieldWidget",
"ui:options": {"rows": 3},
}

class Meta:
title = "Approve Patient"
order = ["notes"]

def save(self):
obj = self.initial.get("object_instance")
obj.approval_notes = self.cleaned_data.get("notes")
obj.save()


class RejectionForm(BaseSimpleForm):
reason = forms.CharField(label="Rejection Reason", required=True)

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.declared_fields["reason"].extra_ui_schema = {
"ui:widget": "TextareaFieldWidget",
}

class Meta:
title = "Reject Patient"
order = ["reason"]

def save(self):
obj = self.initial.get("object_instance")
obj.rejection_reason = self.cleaned_data.get("reason")
obj.save()


class PatientWorkflow(WorkflowBase):

status_transitions = [
{
"name": "submit",
"display_name": "Submit for Approval",
"description": "Submit this record for manager review",
"from": "draft",
"to": "pending",
"confirmation_message": "Submit for approval?",
},
{
"name": "approve",
"display_name": "Approve",
"from": "pending",
"to": "approved",
"roles": ["Manager", "Admin"],
"form": ApprovalForm,
},
{
"name": "reject",
"display_name": "Reject",
"from": "pending",
"to": "rejected",
"roles": ["Manager", "Admin"],
"form": RejectionForm,
},
{
"name": "reopen",
"display_name": "Return to Draft",
"from": "rejected",
"to": "draft",
"confirmation_message": "Return to draft for corrections?",
},
{
"name": "activate",
"display_name": "Activate",
"from": "approved",
"to": "active",
"roles": ["Admin"],
"confirmation_message": "Activate this patient record?",
},
]

tag_transitions = [
{
"name": "urgent",
"enabled": {
"confirmation_message": "Mark as urgent?",
"roles": ["Manager", "Admin"],
},
"disabled": {
"confirmation_message": "Remove urgent flag?",
"roles": ["Manager", "Admin"],
},
},
]

# --- Conditions ---

def submit_condition(self, request, object_instance, **kwargs):
if not object_instance.name:
return False, "Name is required before submitting"
if not object_instance.date_of_birth:
return False, "Date of birth is required before submitting"
return True, "Ready to submit"

def approve_condition(self, request, object_instance, **kwargs):
if not object_instance.is_complete():
return False, "All required fields must be filled before approval"
return True, "Ready to approve"

# --- Done methods ---

def approve_done(self, request, object_instance, transaction_obj):
object_instance.approved_at = timezone.now()
object_instance.approved_by = request.user
object_instance.save()

def reject_done(self, request, object_instance, transaction_obj):
object_instance.rejected_at = timezone.now()
object_instance.save()

def activate_done(self, request, object_instance, transaction_obj):
object_instance.activated_at = timezone.now()
object_instance.save()

class Meta:
model = Patient
on_create_status = "draft"
statuses = {
"draft": {"color": "#717680", "label": "Draft"},
"pending": {"color": "#F59E0B", "label": "Pending Approval"},
"approved": {"color": "#2E90FA", "label": "Approved"},
"rejected": {"color": "#F04438", "label": "Rejected"},
"active": {"color": "#12B76A", "label": "Active"},
}
tags = [
("urgent", "Urgent"),
]

views.py

from ...packages.crud.base import BaseCrudView
from .tables import PatientTable
from .forms import PatientForm
from .workflow import PatientWorkflow

class PatientCrudView(BaseCrudView):
page_title = "Patients"
add_btn_title = "Add Patient"
table = PatientTable
form = PatientForm
workflow = PatientWorkflow

tables.py

from ...packages.crud.table.base import ModelTable
from ...packages.crud.table.column import ModelCol, StatusCol, TagsCol

class PatientTable(ModelTable):
name = ModelCol(display_as="Name", sortable=True, searchable=True)
status = StatusCol(display_as="Status")
tags = TagsCol(display_as="Tags")

class Meta:
model = Patient