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