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