Skip to content

Approvals — Views

Overview

The approvals view layer exposes the HTTP API for approval workflows.

It is responsible for:

  • listing approvals
  • showing approval inbox items
  • deciding approvals
  • forwarding and unforwarding approvals
  • managing approval delegations
  • shaping paginated API responses

The view layer should remain thin.

Its role is to:

  • authenticate the request
  • enforce org scoping
  • invoke serializers
  • call service functions
  • return HTTP responses

Business workflow logic belongs in services, not in views.


Main view sets

ApprovalViewSet

The main API surface for approval records.

It provides:

  • standard model viewset behavior
  • inbox endpoint
  • decision endpoint
  • forward endpoint
  • unforward endpoint

Responsibilities

  • scope approvals to request.org
  • preload related objects for efficient serialization
  • expose inbox-specific listing behavior
  • pass precomputed serializer context
  • map domain/service errors to HTTP responses

Main actions

  • list
  • retrieve
  • create
  • inbox
  • decide
  • forward
  • unforward

ApprovalDelegationViewSet

The main API surface for approval delegations.

It provides:

  • standard CRUD behavior for delegations
  • current-user delegation summary
  • explicit disable action

Responsibilities

  • scope delegations to request.org
  • limit non-admin users to delegations relevant to them
  • expose active incoming/outgoing delegations
  • delegate disable behavior to services

Main actions

  • list
  • retrieve
  • create
  • destroy
  • me
  • disable

Permissions

The approvals app uses a mix of generic and domain-specific permissions.

Common permissions

Most endpoints use:

  • IsAuthenticated
  • HasCurrentOrg

This ensures:

  • the user is authenticated
  • the request is scoped to a valid org
  • cross-org access is prevented

CanViewApproval

Object-level permission that determines whether a user may view an approval.

A user may view an approval if they are:

  • an active org admin or owner
  • a direct assignee
  • a delegated actor for one of the assignees

This is used for:

  • retrieve

CanDecideApproval

Object-level permission that determines whether a user may decide an approval.

A user may decide an approval if they are:

  • an active org admin or owner
  • a direct assignee
  • a delegated actor for one of the assignees

This is used for:

  • decide

Approval inbox flow

The inbox endpoint is one of the most important read APIs in the app.

Responsibilities

  • return only visible pending approvals
  • include direct assignments
  • include delegated visibility
  • include forwarded approvals
  • enrich approvals with delegation metadata
  • enrich timesheet approvals with serialized target metadata
  • paginate results

Flow

  1. parse include_delegated query param
  2. call inbox query service
  3. paginate queryset
  4. precompute timesheet target metadata
  5. precompute delegation display metadata
  6. serialize results
  7. return paginated response

Mermaid flow

flowchart TD
    A[GET /approvals/inbox] --> B[Parse include_delegated]
    B --> C[inbox_queryset_for_user]
    C --> D[Paginate queryset]
    D --> E[Build timesheet meta map]
    E --> F[Build delegation info map]
    F --> G[Serialize approvals]
    G --> H[Return paginated response]

Approval decision flow

The decide endpoint handles approval state transitions.

Responsibilities

  • retrieve the approval
  • validate object permissions
  • validate request payload
  • call the service-layer decision workflow
  • translate service errors into HTTP status codes
  • return updated approval payload

Flow

  1. load approval
  2. check decide permission
  3. validate decision payload
  4. call decide_approval(...)
  5. map domain errors to HTTP responses
  6. return serialized approval

Mermaid flow

flowchart TD
    A[POST /approvals/{id}/decide] --> B[Load approval]
    B --> C[Check CanDecideApproval]
    C --> D[Validate ApprovalDecisionSerializer]
    D --> E[Call decide_approval]
    E --> F{Service result}
    F -->|success| G[Serialize approval]
    F -->|validation error| H[Return 400 or 409]
    G --> I[Return 200]

Forward flow

The forward endpoint transfers visible action responsibility to another org member.

Responsibilities • validate input • call forwarding service • map permission and validation errors • return updated approval

Flow 1. load approval 2. validate ApprovalForwardSerializer 3. call forward_approval(...) 4. map domain errors to: - 400 - 403 - 409 5. return serialized approval

flowchart TD
    A[POST /approvals/{id}/unforward] --> B[Load approval]
    B --> C[Validate ApprovalForwardSerializer]
    C --> D[Call unforward_approval]
    D --> E{Service result}
    E -->|success| F[Serialize approval]
    E -->|permission error| G[Return 403]
    E -->|not found| H[Return 404]
    E -->|validation/state error| I[Return 400 or 409]
    F --> J[Return 200]

Delegation list and detail behavior

ApprovalDelegationViewSet exposes delegation data differently depending on the caller.

Admin / owner users

May see all delegations in the current org.

Non-admin users

May only see delegations where they are: - from_user - to_user

This keeps visibility limited to relevant delegation relationships.


Delegation “me” endpoint

The me endpoint is a convenience read endpoint that returns: • outgoing active delegations • incoming active delegations

Responsibilities

•   scope to current org
•   filter to active, current delegations
•   split into outgoing and incoming groups
•   serialize both groups

flowchart TD
    A[GET /approval-delegations/me] --> B[Base active current delegations]
    B --> C[Filter outgoing by from_user=request.user]
    B --> D[Filter incoming by to_user=request.user]
    C --> E[Serialize outgoing]
    D --> F[Serialize incoming]
    E --> G[Return combined payload]
    F --> G

Delegation disable flow

Delegations can be disabled either through destroy behavior or through an explicit action.

perform_destroy

The destroy path uses:

  • disable_delegation(...)

instead of hard-deleting rows.

disable action

The explicit disable endpoint also calls:

  • disable_delegation(...)

This ensures delegation deactivation behavior is consistent.

Why this matters

The view layer does not decide how disabling works.
It delegates that behavior to services so notifications and workflow rules remain centralized.


Pagination

The approvals app defines a dedicated paginator:

ApprovalInboxPagination

Settings:

  • default page size: 50
  • page_size_query_param
  • max page size: 200

This paginator is used for inbox and list-style approval endpoints.

Pagination matters because approval inboxes can grow and often include enriched metadata.


Read-model enrichment in views

The views are responsible for supplying serializer context for richer approval cards.

Timesheet meta map

For timesheet approvals, the view precomputes:

  • serialized weekly timesheet payloads
  • overtime information
  • warning metadata

This is then supplied to ApprovalSerializer via:

  • ts_meta_by_id

Delegation info map

The view also precomputes delegation display information via:

  • _build_delegation_info_map(...)

This supplies:

  • delegator user metadata
  • delegation end timestamp

to the serializer context.

Why this lives in views

This is read-model assembly work.
It supports efficient serialization and response shaping, while leaving workflow rules in services.


Responsibilities

View layer owns

  • request parsing
  • serializer invocation
  • pagination
  • response construction
  • permission wiring
  • read-model enrichment assembly

Service layer owns

  • decision workflow
  • forwarding workflow
  • delegation lifecycle
  • assignee computation
  • inbox visibility rules
  • side effects
  • notifications
  • workflow audit behavior

Serializer layer owns

  • payload validation
  • response shaping

Data flows

flowchart TD
    A[HTTP request] --> B[ViewSet action]
    B --> C[Permission check]
    C --> D[Serializer validation]
    D --> E[Service call]
    E --> F[Models and side effects]
    F --> G[Serializer output]
    G --> H[HTTP response]

What views do not do

Views do not:

  • decide who should be assigned
  • implement decision state transitions directly
  • implement forwarding logic directly
  • implement delegation business rules directly
  • apply timesheet side effects directly
  • send notifications directly
  • write workflow audit behavior directly

Those responsibilities belong to:

  • services
  • permissions
  • models

Design principles

Thin controllers

Views should orchestrate, not implement workflow.

Strong org scoping

All approval operations must be scoped to:

  • authenticated user
  • current org

Explicit action endpoints

Approval actions such as:

  • decide
  • forward
  • unforward
  • disable delegation

are exposed as explicit endpoints rather than hidden mutations.

Read/write separation

Views may assemble enriched read context, but write behavior should always go through services.


Best practices

  • keep approval workflow logic in services
  • keep permission classes reusable and separate from views
  • precompute serializer context when related metadata is expensive
  • paginate inbox endpoints consistently
  • translate domain errors into clear HTTP responses
  • avoid direct model mutation in view actions

Future extensions

Possible future view improvements include:

  • dedicated list vs detail serializers
  • approval history endpoint
  • bulk decision endpoints
  • bulk delegation actions
  • inbox filtering by kind/status
  • approval timeline endpoint
  • delegated-only or forwarded-only inbox filters

Summary

The approvals views expose the API surface for:

  • approval listing and retrieval
  • approval inboxes
  • decision actions
  • forwarding actions
  • delegation management

They ensure the approvals app is:

  • org-scoped
  • permission-aware
  • paginated
  • service-driven
  • ready for richer workflow expansion