Manuals — Serializers¶
Overview¶
The serializers layer in the manuals app defines the API contract for:
- taxonomy data
- manuals and chapters
- sections and notes
- step groups and steps
- callouts
- media uploads
- ordered chapter items
It is responsible for:
- input validation
- output shaping
- lightweight normalization
- calling service-layer orchestration for nested writes
It is not responsible for:
- deep sync logic
- nested content orchestration
- media cleanup workflows
- PDF generation
- storage lifecycle behavior
Those responsibilities belong to the services layer.
Serializer package structure¶
The serializers package is split by concern:
common.pytaxonomy.pymedia.pycallouts.pysteps.pysections.pyitems.pychapters.pymanuals.py
This keeps read/write contracts easier to understand and maintain.
High-level dependency flow¶
flowchart TD
Common --> Taxonomy
Media --> Steps
Media --> Sections
Callouts --> Steps
Callouts --> Sections
Sections --> Items
Callouts --> Items
Steps --> Items
Media --> Chapters
Callouts --> Chapters
Items --> Chapters
Taxonomy --> Manuals
Common --> Manuals
Media --> Manuals
Callouts --> Manuals
Chapters --> Manuals
Common serializers¶
OrganizationBriefSerializer¶
Location:
- serializers/common.py
Purpose:
- lightweight organization representation inside manual responses
Fields:
- id
- name
- slug
Used by:
- ManualListSerializer
- ManualDetailSerializer
Taxonomy serializers¶
CategorySerializer¶
Purpose:
- serialize manual categories
Fields:
- id
- name
- slug
TopicSerializer¶
Purpose:
- serialize manual topics
Fields:
- id
- name
- slug
- color
Media serializers¶
ManualMediaSerializer¶
Purpose:
- read serializer for attached media
Fields:
- id
- file
- file_url
- caption
- type
- order
Notes
file_url is derived dynamically.
If a request object is present in serializer context:
- absolute URL is returned
Otherwise:
- raw file URL is returned
ManualMediaUploadSerializer¶
Purpose:
- validate and create a media attachment
Write-only parent fields:
- manual_id
- chapter_id
- section_id
- step_group_id
- step_id
Validation rules:
- uploaded file must pass upload validation
- exactly one parent id must be provided
Responsibilities
This serializer validates:
- file presence and type constraints
- attachment target shape
It does not validate org ownership of the parent.
That belongs in the media service / view workflow.
Callout serializers¶
CalloutPresetSerializer¶
Purpose:
- serialize reusable callout style presets
Fields:
- id
- name
- slug
- icon
- color
- is_active
- order
CalloutSerializer¶
Purpose:
- serialize callouts attached to manuals or content blocks
Fields include:
- parent references
- order
- display fields
- preset reference
- resolved icon/color metadata
Important computed fields:
- icon_resolved
- color_resolved
- display_name
Notes
display_name resolves to:
- preset name when preset is present
- otherwise the callout’s own name
This serializer is mainly a transport serializer.
Parent consistency rules are enforced by the model and service layer.
Step serializers¶
ManualStepSerializer¶
Purpose:
- serialize individual manual steps
Includes nested read-only relations:
- media
- callouts
Fields:
- id
- order
- title
- body_text
- media
- callouts
StepGroupSerializer¶
Purpose:
- serialize step groups with nested steps
Includes nested read-only relations:
- steps
- media
- callouts
Fields:
- id
- order
- title
- steps
- media
- callouts
Section serializers¶
SectionSerializer¶
Purpose:
- serialize rich sections inside chapters
Includes nested read-only relations:
- media
- callouts
Fields:
- id
- order
- title
- description_json
- media
- callouts
Notes
The serializer intentionally exposes description_json rather than HTML.
This matches the current rich-text architecture.
SectionNoteSerializer¶
Purpose:
- serialize note blocks
Fields:
- id
- order
- title
- body
Chapter item serializer¶
ChapterItemSerializer¶
Purpose:
- serialize the ordered display stream for a chapter
Nested read-only targets:
- section
- note
- callout
- step_group
Computed field:
- is_media_grid
Fields:
- id
- order
- kind
- section
- note
- callout
- step_group
- is_media_grid
Notes
This serializer does not expose write-side target selection logic.
It is read-only and presentation-focused.
Chapter serializer¶
ChapterSerializer¶
Purpose:
- serialize chapter detail including nested content stream
Includes nested read-only relations:
- media
- callouts
- items
Fields:
- id
- order
- title
- description
- cover_image
- media
- callouts
- items
Notes
This serializer does not directly expose sections, notes, and step groups as standalone chapter children.
Instead, chapter presentation is primarily driven by items.
ChapterWriteSerializer¶
Location:
- serializers/chapters.py
Purpose:
- dedicated write serializer for chapter create/update operations
Fields:
-
id (read-only)
-
manual (required, FK)
-
order
-
title
-
description
-
cover_image
Notes
This serializer exists to separate:
-
write concerns (manual assignment, basic fields)
-
from read concerns (nested media, callouts, items)
It ensures that:
-
manualis always explicitly provided during creation -
database integrity constraints are respected
-
viewsets can safely handle create/update without leaking nested read complexity
Chapter serializer split¶
The chapter API now uses two serializers:
| Serializer | Purpose |
|---|---|
| ChapterSerializer | read-only, nested presentation |
| ChapterWriteSerializer | create/update input |
View behavior
ChapterViewSet dynamically selects serializer:
-
create/update → ChapterWriteSerializer
-
retrieve/list → ChapterSerializer
Architectural improvement¶
Explicit read vs write separation¶
Previously:
-
ChapterSerializer was implicitly used for both read and write
-
This caused missing required fields (e.g. manual) during create
Now:
-
read and write paths are clearly separated
-
serializers are smaller and more predictable
-
API contracts are explicit
Impact on serializer responsibilities¶
Updated serializer responsibilities:
Serializer layer now additionally owns¶
-
clear separation between read and write contracts
-
enforcing required foreign key relationships on write
Service layer remains responsible for¶
-
nested orchestration
-
synchronization logic
-
content integrity beyond simple field validation
Best practice update¶
Add to best practices:
-
use separate write serializers when:
-
required FK fields are not part of read output
-
nested read serializers would pollute write contracts
-
create/update flows differ significantly from read shape
Manual serializers¶
ManualListSerializer¶
Purpose:
- lightweight serializer for manual listings
Includes:
- taxonomy
- org summary
- publishing metadata
Fields:
- id
- title
- description
- cover_image
- category
- topic
- org
- is_public_external
- published_at
- order
Use case
Used when the API only needs:
- manual cards
- list views
- search/filter results
ManualDetailSerializer¶
Purpose:
- full serializer for manual detail and manual write operations
Read fields¶
Includes nested read-only relations:
- category
- topic
- org
- media
- callouts
- chapters
Write fields¶
Supports write-only helpers:
- category_id
- topic_id
- chapters_write
- chapters_strategy
- callouts_write
- callouts_strategy
Nested write strategy fields¶
Supported strategies:
- merge
- replace
These determine how nested content sync behaves for:
- chapters
- manual-level callouts
Input normalization¶
The serializer normalizes JSON-ish fields for:
- chapters_write
- callouts_write
If the incoming value is a JSON string, it is parsed before normal validation.
Create/update behavior¶
The serializer delegates nested synchronization to services.
Flow:
- validate base fields
- parse nested write payloads
- create or update the Manual
- call service-layer sync functions
- refresh the instance
- return the updated object
Manual write flow¶
flowchart TD
A[Incoming manual payload] --> B[ManualDetailSerializer]
B --> C[Validate scalar fields]
C --> D[Parse chapters_write / callouts_write]
D --> E[Create or update Manual]
E --> F[Call sync_chapters]
E --> G[Call sync_manual_callouts]
F --> H[Refresh manual instance]
G --> H
H --> I[Return serialized manual]
Serializer responsibilities by layer¶
Serializer layer owns¶
- field declarations
- input validation
- nested payload shape validation
- JSON-ish payload parsing
- response shaping
- lightweight computed fields
Service layer owns¶
- chapter synchronization
- chapter item synchronization
- step synchronization
- callout sync
- inline media cleanup
- nested manual update orchestration
Model layer owns¶
- data constraints
- field types
- relational integrity
- model-level validation
Why the split matters¶
Before cleanup, the serializer layer was doing too much:
- parsing
- orchestration
- deep nested sync
- rich content cleanup
That made serializers:
- too long
- hard to test
- hard to reason about
- tightly coupled to content internals
After the split:
- serializers define the API surface
- services perform the heavy synchronization work
This is a much cleaner separation.
Validation patterns¶
Parent selection validation¶
ManualMediaUploadSerializer enforces:
- exactly one parent id must be provided
This prevents ambiguous media placement.
Cover image normalization¶
ManualDetailSerializer normalizes empty cover values so that:
- empty string can be treated as null-like input
This is useful for multipart/form-data workflows.
JSON payload normalization¶
Nested write payloads may arrive as:
- parsed lists
- JSON strings
The serializer accepts both by normalizing string payloads first.
This makes frontend integration more robust.
Read-model enrichment¶
The serializers are designed to support rich nested responses.
Examples:
- a step includes media and callouts
- a section includes media and callouts
- a chapter includes items
- a manual includes chapters, callouts, media, and taxonomy
This makes the API convenient for manual-rendering clients.
Design principles¶
Split by domain concern¶
The serializer package is organized by content type instead of being one large file.
Thin serializers, rich services¶
Serializers should validate and shape data, not own orchestration.
Read-friendly nested output¶
The API favors rich nested reads for manual rendering.
Explicit write helpers¶
Write-only fields such as chapters_write and callouts_write make nested updates explicit.
Keep transport separate from storage¶
Serializers expose URLs and IDs, but storage and file lifecycle rules stay elsewhere.
Best practices¶
- keep serializers focused on transport concerns
- put nested sync logic in services
- use read-only nested serializers for rendering-heavy responses
- keep computed fields lightweight
- avoid business workflow logic inside serializer methods
- validate shape here, validate ownership/workflow in services or views
Future extensions¶
Possible future improvements include:
- dedicated write serializers for nested chapter content
- dedicated manual publish/unpublish serializers
- explicit serializer for chapter item writes
- separate public/external serializers
- more compact serializers for mobile/manual preview use cases
Summary¶
The manuals serializer layer provides the API contract for:
- taxonomy
- manuals
- chapters
- sections
- notes
- step groups
- steps
- callouts
- media
- chapter display items
It is designed to be:
- split by concern
- nested for read convenience
- thin on orchestration
- aligned with the service layer
The key idea is:
serializers describe and validate the API shape; services perform the content synchronization.