Skip to content

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

  • GenericForeignKey combining content_type and object_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

  • draft
  • pending
  • approved
  • rejected
  • canceled

Constraints

Indexes:

  • org, kind, status
  • content_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:

  • approved
  • rejected
  • canceled

then these fields are required:

  • decided_by
  • decided_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_active
  • org, to_user, is_active
  • org, 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

  • kind must be blank or one of ApprovalKind.values
  • from_user and to_user must not be the same user
  • ends_at must be after starts_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:

    • approver
    • forwarded
  • created_at

  • assignment creation timestamp

  • created_by

  • optional user who created the assignment

Constraints

Unique together:

  • approval, user

Indexes:

  • user, approval
  • approval

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:

  • draft
  • pending
  • approved
  • rejected
  • canceled

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

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 Approval as the source of truth for approval state
  • use ApprovalAssignee to model who may currently act
  • use ApprovalDelegation for temporary or scoped delegation
  • prefer service methods for state changes instead of mutating models directly
  • treat kind values as stable workflow identifiers
  • keep payload small 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