Approvals — Models¶
Overview¶
The approvals app provides a generic approval workflow system for business actions that require review and decision.
It is designed to support:
- approval requests for different target objects
- delegation of approval responsibility
- assignee tracking
- approval inbox workflows
- approval state transitions
- future expansion to additional approval kinds
The model layer defines the persistence structure for approvals and their assignment relationships.
Core models¶
Approval¶
Represents a single approval request for a target object within an organization.
This is the central workflow object of the app.
An approval links:
- an organization
- a target object
- a workflow kind
- a lifecycle state
- requester metadata
- decision metadata
Fields¶
id-
UUID primary key
-
org -
foreign key to
Organization -
content_type -
content type for the generic target
-
object_id -
primary key of the target object
-
target -
GenericForeignKeycombiningcontent_typeandobject_id -
kind - type of approval workflow
-
uses
ApprovalKind -
status - current lifecycle status
-
uses
Approval.Status -
requested_by -
optional user who initiated the approval
-
requested_at -
timestamp when the approval was requested
-
decided_by -
optional user who made the final decision
-
decided_at -
timestamp when the decision was made
-
decision_reason -
optional text explaining rejection or decision context
-
payload -
JSON metadata for workflow context, audit data, UI hints, or snapshots
-
is_active - soft-active flag for the approval row
Status values¶
draftpendingapprovedrejectedcanceled
Constraints¶
Indexes:
org,kind,statuscontent_type,object_id
Unique constraint:
- prevents multiple active pending approvals for the same:
- org
- target object
- approval kind
This ensures that one target cannot accumulate duplicate active pending approvals of the same type.
Validation rules¶
When status is one of:
approvedrejectedcanceled
then these fields are required:
decided_bydecided_at
Model helpers¶
mark_approved(user, reason="")- marks approval approved
- sets decision metadata
-
validates and saves
-
mark_rejected(user, reason="") - marks approval rejected
- sets decision metadata
- validates and saves
Notes¶
The Approval model is intentionally generic.
It can be used for:
- weekly timesheet approval
- expense approval
- leave approval
- report approval
- future custom workflow approvals
ApprovalDelegation¶
Represents a delegation relationship where one user delegates approval responsibility to another user.
This enables temporary or open-ended delegation of approval workloads.
Fields¶
id-
UUID primary key
-
org -
foreign key to
Organization -
from_user -
user delegating approval responsibility
-
to_user -
user receiving delegated approval responsibility
-
kind - approval kind this delegation applies to
-
empty string means all approval kinds
-
starts_at -
start timestamp for delegation validity
-
ends_at -
optional end timestamp
-
is_active -
whether delegation is currently active
-
created_at -
creation timestamp
-
created_by -
optional user who created the delegation
-
disabled_at -
optional timestamp when delegation was disabled
-
disabled_by - optional user who disabled the delegation
Constraints¶
Indexes:
org,from_user,is_activeorg,to_user,is_activeorg,kind,is_active
Unique constraint:
- prevents multiple active delegations for the same:
- org
- from_user
- kind
This means one user can have at most one active delegation per kind in an org.
Validation rules¶
kindmust be blank or one ofApprovalKind.valuesfrom_userandto_usermust not be the same userends_atmust be afterstarts_at
Model helpers¶
is_current(at=None)-
returns whether the delegation is currently active and within date range
-
disable(by_user=None) - deactivates the delegation
- records disable metadata
Notes¶
Delegation supports two scopes:
- global delegation for all kinds
- kind-specific delegation
That gives the workflow flexibility without adding excessive complexity.
ApprovalAssignee¶
Represents a user currently assigned to act on an approval.
This model is the bridge between an Approval and the users who may process it.
It supports:
- direct approvers
- delegated visibility
- forwarded approvals
- future assignee reasons
Fields¶
approval-
foreign key to
Approval -
user -
foreign key to assigned user
-
reason - optional string describing why the user is assigned
-
examples:
approverforwarded
-
created_at -
assignment creation timestamp
-
created_by - optional user who created the assignment
Constraints¶
Unique together:
approval,user
Indexes:
user,approvalapproval
Notes¶
This model is intentionally simple.
It allows the rest of the system to ask:
- who currently sees this approval?
- who may act on it?
- was this assignment forwarded?
without overloading the Approval model itself.
Enums¶
ApprovalKind¶
Defines supported approval workflow kinds.
Current values:
timesheet_submit
This is expected to expand over time.
Examples of future kinds:
- expense_submit
- leave_request
- report_publish
- inventory_transfer
Approval.Status¶
Defines the lifecycle state of an approval.
Values:
draftpendingapprovedrejectedcanceled
Model relationships¶
flowchart TD
O[Organization] --> A[Approval]
O --> D[ApprovalDelegation]
U1[Requested by user] --> A
U2[Decided by user] --> A
A --> T[Generic target object]
A --> AA[ApprovalAssignee]
U3[Assigned user] --> AA
U4[Assignment created by] --> AA
U5[Delegates from_user] --> D
U6[Delegates to_user] --> D
U7[Delegation created by] --> D
U8[Delegation disabled by] --> D
¶
flowchart TD
O[Organization] --> A[Approval]
O --> D[ApprovalDelegation]
U1[Requested by user] --> A
U2[Decided by user] --> A
A --> T[Generic target object]
A --> AA[ApprovalAssignee]
U3[Assigned user] --> AA
U4[Assignment created by] --> AA
U5[Delegates from_user] --> D
U6[Delegates to_user] --> D
U7[Delegation created by] --> D
U8[Delegation disabled by] --> D
Approval lifecycle¶
stateDiagram-v2
[*] --> draft
draft --> pending
pending --> approved
pending --> rejected
pending --> canceled
approved --> [*]
rejected --> [*]
canceled --> [*]
Delegation lifecycle¶
stateDiagram-v2
[*] --> active
active --> inactive: disable()
active --> expired: ends_at passed
inactive --> [*]
expired --> [*]
Assignmetn and delegation flow¶
flowchart TD
A[Approval created] --> B[Assignees computed]
B --> C[ApprovalAssignee rows created]
C --> D[Inbox visibility]
E[ApprovalDelegation active] --> F[Delegate may see approval]
F --> D
G[Approval forwarded] --> H[Forwarded assignee row added]
H --> D
Responsibilities of the model layer¶
The model layer is responsible for:
- persisting approval requests
- storing decision state
- linking approvals to generic targets
- storing delegation relationships
- storing assignee relationships
- enforcing key DB-level constraints
What models do not do¶
These models do not:
- determine who may decide an approval
- compute assignees for every workflow
- determine inbox visibility rules
- send notifications
- apply target-specific side effects
- enforce view-level permissions
Those responsibilities belong to:
- services
- permissions
- views
- notification integrations
Design principles¶
Generic target support¶
The Approval model uses a generic foreign key so the approval system can be reused across many apps.
This avoids building a separate approval model for each domain.
Explicit state transitions¶
Approval state is explicit and constrained by status values.
This makes workflow behavior easier to reason about and audit.
Delegation as a first-class concept¶
Delegation is modeled explicitly rather than inferred indirectly.
This makes it possible to:
- audit delegation
- show delegated approvals in inboxes
- support temporary delegations
- support kind-specific delegations
Assignees as a separate relation¶
Assignments are stored separately from approvals.
This keeps the approval record focused on workflow state while allowing flexible recipient logic.
Database-backed safety¶
The app uses DB constraints to enforce critical invariants such as:
- one active pending approval per target/kind
- one active delegation per from_user/kind/org
- one assignee row per approval/user
Best practices¶
- use
Approvalas the source of truth for approval state - use
ApprovalAssigneeto model who may currently act - use
ApprovalDelegationfor temporary or scoped delegation - prefer service methods for state changes instead of mutating models directly
- treat kind values as stable workflow identifiers
- keep
payloadsmall and structured
Future extensions¶
Possible future improvements include:
- more approval kinds
- multi-step approvals
- ordered approval chains
- SLA / escalation timestamps
- decision history model
- approval comments or notes
- richer forwarding metadata
- audit snapshots for target objects
Summary¶
The approvals model layer provides the structural foundation for:
- generic approval workflows
- assignee tracking
- delegation handling
- decision state persistence
Together, these models enable an approval system that is:
- reusable across apps
- delegation-aware
- workflow-friendly
- easy to extend