Dynamic forms (how it works)
This page explains how Dynamic Forms work end-to-end, in plain language.
Developer? See Dynamic Forms - Developer Guide for how to register new hydration targets and extend the system.
The big picture
Dynamic Forms add a configurable layer on top of the platform: 1. A form is defined (and published) per municipality 2. A submission is stored as an immutable record 3. Optionally, the submission is “hydrated” into a real domain object (e.g., an Occurrence)
The three stages
1) Define
Administrators build a form using the Form Builder and publish versions.
Key ideas: - Draft schema is editable - Publish creates an immutable version (a snapshot)
2) Collect
When a user submits a form: - A Form Submission is saved (payload + metadata like IP/user agent) - The submission always references the exact form version used
3) Hydrate (optional)
Hydration is the “conversion step”: turning the submission payload into an actual model instance.
Hydration is controlled by: - a Use Case (what you’re trying to create), - a list of Field Bindings (how payload keys map to target fields), - and a hydration target implementation registered by the destination app.
What “registry-based hydration” means
Dynamic Forms stay generic by using a registry: - Target apps (like Occurrences) register themselves as “hydration targets” - Each target provides: - field discovery (what can be mapped), - optional suggestions, - and the hydrate function (how to create the target object).
In practice:
- The Use Case stores a target model path (example: occurrences.Occurrence)
- The registry resolves that model path to the correct hydration handler
Field mapping UX
The mapping page is designed to avoid memorizing internal field names: - Form fields are read directly from the current form schema - Target fields come from the registered hydration target and are grouped: - required - auto-filled - optional - dynamic (type-specific)
Suggested mappings are computed using simple patterns (e.g., “email” → reporter email).
Dynamic fields (Occurrence example)
Occurrences can have type-specific dynamic fields.
How it works:
- The administrator selects an Occurrence Type (or configures it in the Use Case)
- The target field list is expanded with that type’s dynamic fields
- Mappings can target keys like dynamic:<field_name>
Audit trail
Every hydration attempt can store a result record:
- success/failure
- target object reference (e.g., occurrences.Occurrence:126)
- error details when hydration fails
Files (uploads) and “file tokens”
To avoid sending large files inside the same JSON request as the submission, Dynamic Forms use a two-step pattern:
- The portal uploads the file first (either directly to Django in LOCAL mode, or to S3/GCS via a presigned URL).
- The upload returns a
file_token. - The portal includes that
file_tokeninside the submission payload. - When the submission is created, the backend consumes those tokens into
SubmissionFilerows.
Payload convention:
- A file reference is an object containing a file_token, for example:
- {"file_token": "<uuid>", "name": "photo.jpg", "content_type": "image/jpeg", "size": 12345}
Preset portal blocks (map + attachments)
For high-UX inputs like maps, it is common to:
- render a Leaflet map outside Form.io, and
- write values into hidden payload keys like location.latitude / location.longitude / location.address.
Attachments follow the same idea: the portal manages selection + upload and writes attachments[] token objects into the payload.
Behind the scenes (grounded in code)
- Models:
multivertical_django/apps/dynamic_forms/models.py - Public API:
- schema fetch:
multivertical_django/apps/dynamic_forms/views.py(FormPublicViewSet.retrieve) - submission create:
multivertical_django/apps/dynamic_forms/views.py(FormPublicViewSet.submissions) - Hydration orchestration:
multivertical_django/apps/dynamic_forms/services.py - Upload flow + token consumption:
multivertical_django/apps/dynamic_forms/uploads.py - Registry:
- definitions:
multivertical_django/apps/dynamic_forms/registry.py - occurrence target:
multivertical_django/apps/occurrences/hydration.py - Admin mapping UI + APIs:
- template:
multivertical_django/apps/dynamic_forms/templates/admin/dynamic_forms/formdefinition/mappings.html - endpoints:
multivertical_django/apps/dynamic_forms/admin_api.py