Skip to content

Approvals — Serializers

Overview

The serializers in the approvals app define the API contract between backend and frontend.

They are responsible for:

  • validating incoming request payloads
  • shaping approval and delegation responses
  • exposing enriched metadata for frontend cards
  • keeping transport concerns separate from workflow logic

The serializer layer should remain thin.

Business logic such as:

  • deciding approvals
  • forwarding approvals
  • creating delegations
  • assigning users
  • applying side effects
  • sending notifications

belongs in services, not in serializers.


Main serializers

ApprovalAssigneeOut

Represents a single approval assignee in API responses.

This serializer enriches the raw assignee row with frontend-friendly user metadata.

Fields

  • user
  • user_label
  • user_meta
  • reason
  • created_at
  • created_by
  • created_by_label
  • created_by_meta

Purpose

Used to show:

  • who is assigned to the approval
  • why they are assigned
  • who created the assignment

Notes

This serializer is read-only in practice and is nested inside ApprovalSerializer.


ApprovalSerializer

Represents the main approval payload returned by the API.

It supports both:

  • creation input
  • enriched read output

Write-only input fields

  • target_type
  • target_id

These allow clients to create an approval for a generic target object.

Read-only target fields

  • content_type
  • object_id
  • target_label
  • target_meta

These help frontend clients render meaningful approval cards.

Additional enriched fields

  • assignees
  • delegated
  • delegated_from_meta
  • delegated_until
  • forwarded
  • forwarded_to_meta
  • forwarded_by_meta
  • forwarded_at

Responsibilities

  • validate org context exists
  • parse generic target input
  • expose generic target information
  • expose delegation/forwarding metadata
  • include nested assignee output
  • create Approval rows from validated payloads

Notes

This serializer supports timesheet-specific target enrichment through context, but remains generic enough to support future approval targets.


ApprovalDecisionSerializer

Validates decision requests for existing approvals.

Fields

  • decision
  • allowed values:

    • approve
    • reject
  • reason

  • optional for approval
  • required for rejection

Responsibilities

  • validate decision choice
  • enforce rejection reason requirement

Purpose

Used by the decision endpoint to validate user actions before calling workflow services.


ApprovalDelegationSerializer

Represents approval delegation rows and validates delegation creation input.

Fields

  • id
  • org
  • from_user
  • from_user_label
  • from_user_meta
  • to_user
  • to_user_label
  • to_user_meta
  • kind
  • starts_at
  • ends_at
  • is_active
  • created_at
  • disabled_at

Responsibilities

  • validate org context
  • normalize delegation kind
  • enforce self-service behavior for non-admin users
  • validate from/to user membership in org
  • validate time window consistency
  • call delegation service for creation
  • expose frontend-friendly labels and user metadata

Notes

Delegation creation is service-backed.
The serializer should validate request structure and context, then delegate workflow behavior to approvals.services.delegations.


ApprovalForwardSerializer

Validates forwarding requests for approvals.

Fields

  • to_user
  • note

Responsibilities

  • validate recipient user reference
  • validate optional note payload

Purpose

Used by:

  • forward endpoint
  • unforward endpoint

Read-model enrichment

The approvals serializers expose several frontend-oriented helper fields.

These are intentionally transport-layer enrichments, not business logic.

Target enrichment

ApprovalSerializer provides:

  • target_label
  • target_meta

This allows clients to render useful approval cards without re-querying related objects.

For example:

  • a weekly timesheet approval can return serialized weekly timesheet metadata
  • future approval types can provide their own target metadata later

Delegation enrichment

ApprovalSerializer provides:

  • delegated
  • delegated_from_meta
  • delegated_until

These fields help the frontend distinguish:

  • directly assigned approvals
  • approvals visible via delegation

This data is provided through serializer context rather than computed inside the serializer from scratch.


Forwarding enrichment

ApprovalSerializer provides:

  • forwarded
  • forwarded_to_meta
  • forwarded_by_meta
  • forwarded_at

This helps the UI show forwarded approvals clearly.

The serializer derives this from prefetched assignee rows.


Serializer context expectations

Some serializers depend on context data supplied by views.

ApprovalSerializer context

Expected optional keys include:

  • request
  • ts_meta_by_id
  • delegation_info_by_approval_id

These are used for:

  • org resolution validation
  • target metadata lookup
  • delegation metadata lookup

Why this matters

This keeps serializers efficient by avoiding unnecessary DB queries during serialization.


Responsibilities

Serializer layer

  • validate request payload shape
  • normalize and expose data for API consumers
  • derive convenience fields for frontend usage
  • convert service/model output into stable response structures

Service layer

  • create delegations
  • decide approvals
  • forward/unforward approvals
  • compute assignees
  • apply side effects
  • send notifications

View layer

  • parse request
  • call serializers
  • call services
  • return HTTP responses

Data flow

flowchart TD
    A[HTTP Request] --> B[Serializer validation]
    B --> C[View]
    C --> D[Service layer]
    D --> E[Models]
    E --> F[Serializer output]
    F --> G[HTTP Response]

Approval creation flow

flowchart TD
    A[Client sends target_type + target_id] --> B[ApprovalSerializer]
    B --> C[Resolve ContentType]
    C --> D[Validate org context]
    D --> E[Create Approval row]
    E --> F[Return enriched approval payload]

Delegation creation flow

flowchart TD
    A[Client submits delegation payload] --> B[ApprovalDelegationSerializer]
    B --> C[Validate org context]
    C --> D[Validate user membership and dates]
    D --> E[Call delegation service]
    E --> F[Create or replace active delegation]
    F --> G[Return delegation payload]

What serializers do not do

Serializers do not:

  • determine who may view an approval
  • determine who may decide an approval
  • compute assignees
  • determine delegation visibility rules
  • apply decision side effects
  • send notifications directly
  • enforce workflow transitions

Those responsibilities belong to:

  • permissions
  • services
  • models
  • views

Validation philosophy

The serializers follow a simple rule:

  • structural validation stays in serializers
  • workflow validation stays in services

Examples

Serializer-level validation

  • request has a valid decision value
  • rejection includes a reason
  • target_type follows app_label.model
  • to_user exists
  • ends_at > starts_at

Service-level validation

  • whether an approval may be forwarded
  • whether a user may decide an approval
  • whether a delegation should replace an existing one
  • whether notifications should be sent
  • what side effects must happen after approval

Best practices

  • keep serializers focused on transport concerns
  • avoid embedding approval workflow logic in serializers
  • pass precomputed context for expensive metadata
  • use explicit fields rather than exposing raw model internals
  • keep nested serializers shallow and UI-friendly
  • treat serializer convenience fields as read-model enrichments, not workflow logic

Future extensions

Possible future serializer improvements include:

  • dedicated list serializer vs detail serializer
  • target-specific serializers per approval kind
  • richer delegation status metadata
  • approval history serializers
  • comment / note serializers
  • serializer separation into serializers/approvals.py and serializers/delegations.py

Summary

The approvals serializer layer provides the API-facing structure for:

  • approval records
  • assignee metadata
  • decision payloads
  • delegation payloads
  • forwarding requests

It ensures that the API remains:

  • predictable
  • frontend-friendly
  • efficient
  • cleanly separated from business workflow logic