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¶
listretrievecreateinboxdecideforwardunforward
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¶
listretrievecreatedestroymedisable
Permissions¶
The approvals app uses a mix of generic and domain-specific permissions.
Common permissions¶
Most endpoints use:
IsAuthenticatedHasCurrentOrg
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¶
- parse
include_delegatedquery param - call inbox query service
- paginate queryset
- precompute timesheet target metadata
- precompute delegation display metadata
- serialize results
- 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]
¶
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¶
- load approval
- check decide permission
- validate decision payload
- call
decide_approval(...) - map domain errors to HTTP responses
- 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]
¶
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]
¶
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
¶
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 --> GDelegation 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]
¶
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