Skip to content

Dynamic forms (Administrator setup)

This page explains how municipality administrators can create Dynamic Forms that: - collect structured data from citizens (or staff), - store it as a form submission, and - optionally hydrate it into a real domain record (for example, an Occurrence).

What this enables (in plain language)

  • Create new intake forms without code changes.
  • Run different forms per municipality and per use case (e.g., “Pothole report”).
  • Map fields visually (pick from lists) so staff don’t need to remember internal field names.
  • Turn a submission into a real object (e.g., create an Occurrence) through hydration.

Key objects you’ll see in Django Admin

  • Form Workspace: a namespace for forms inside a municipality.
  • Form Definition: the form you build (draft + published state).
  • Form Version: an immutable snapshot created when you publish.
  • Use Case: what the submission should become (e.g., create an Occurrence) + configuration.
  • Field Bindings: saved mappings from form fields → target fields.
  • Hydration Result: audit record of hydration success/failure per submission.

1) Create a workspace (per municipality)

In Django Admin: 1. Create a Form Workspace 2. Set the Municipality, Key, and Name

Why: Workspaces scope forms to a municipality and prevent key collisions.

2) Create a form definition

  1. Create a Form Definition in that workspace
  2. Set:
  3. Key (example: occurrence:pothole)
  4. Title (example: “Pothole report”)
  5. Schema Format: Form.io

Notes: - Use stable, human-readable keys (they become part of the form’s URL). - Don’t worry about “publishing” yet—start in Draft.

3) Build the form (Builder)

From the Form Definition list, click Builder.

In the builder: - Add fields for what you want to collect (text, dropdowns, etc.) - Give each field a clear label - Save the draft and publish when ready

Practical tip: keep the field keys readable (e.g., description, email, contact_name, location.latitude).

Preset blocks (Occurrence portal fit)

For the Occurrence portal, we typically want a “best-in-class” UX for certain inputs (especially maps and attachments).

In practice, that means: - The portal renders a Leaflet map and an attachments picker outside Form.io. - The portal writes the selected values into the submission payload under stable keys (see below). - Your Form.io schema should include those keys as Hidden components (optional but recommended for validation UX).

Suggested payload keys (used by the hydration + standard mappings): - location.latitude, location.longitude, location.address - attachments[] (array of objects containing a file_token)

4) Publish the form

Publishing creates a Form Version (a snapshot of the schema) and makes it available to the public API.

After publishing: - The form can be fetched by the portal/renderer using the form key + municipality. - Submissions are always tied to a specific published version (for auditing and replayability).

5) Create (or choose) a Use Case

A Use Case describes what this submission should become.

Example (Occurrence intake): - Key: occurrence:create - Target Model: “Occurrence” (chosen from a dropdown of registered targets) - Target Config: options used to “auto-fill” some target fields (example: set an occurrence_type_id)

Why Use Case exists: - Dynamic forms are generic; the Use Case tells the system which business object to create.

6) Map fields (Mappings)

From the Form Definition list, click Mappings.

On the mapping page: 1. Select the Use Case 2. (If applicable) select the Occurrence Type to include type-specific dynamic fields 3. Pick a Form field (Source) on the left 4. Pick a Target field on the right 5. Click Connect

What you’ll see on the right: - Required: fields the target normally needs - Auto-filled: fields the system usually sets automatically - Optional: extra fields you can map if you want - Dynamic Fields: fields that depend on the configured occurrence type

You can also use Suggested mappings to auto-fill obvious matches (email → reporter email, etc.).

7) Test end-to-end (submit → hydrate)

Once you have: - a published form, - a Use Case, and - mappings,

then any new submission can be hydrated into the target model immediately.

What happens on submit: 1. A Form Submission is saved (payload + metadata) 2. Any file_token values in the payload are consumed into SubmissionFile rows 2. The system finds the correct Use Case for the form 3. The system loads saved Field Bindings 4. The system calls the target app’s hydration handler (e.g., Occurrence hydration) 5. A Hydration Result is stored (success/failure + target object reference)

7a) Files (uploads)

If you include attachments in the portal flow: - The portal uploads files before creating the submission. - The upload endpoint returns a file token (file_token). - The submission payload references that token.

Two supported modes: - LOCAL (development / simple deployments): portal uploads multipart to Django. - S3/GCS (production): portal requests a presigned URL, uploads directly to the bucket, then submits the token.

8) Troubleshooting checklist

If the form renders but hydration doesn’t create a target record: - Confirm you published the form (Draft-only forms won’t be served publicly). - Confirm a Use Case exists and is selected in the mapping UI. - Confirm at least basic mappings exist for required fields (title/description for occurrences). - Check Hydration Results in Django Admin for error details.

If the mapping UI shows no target fields: - The target app may not have registered itself as a hydration target. - The Use Case target model may not match any registered target.

Behind the scenes (grounded in code)

  • Dynamic forms models: multivertical_django/apps/dynamic_forms/models.py
  • Public form API: multivertical_django/apps/dynamic_forms/views.py (FormPublicViewSet)
  • Mapping admin UI: multivertical_django/apps/dynamic_forms/admin.py + template .../templates/admin/dynamic_forms/formdefinition/mappings.html
  • Admin mapping API: multivertical_django/apps/dynamic_forms/admin_api.py
  • Hydration orchestration: multivertical_django/apps/dynamic_forms/services.py
  • Uploads (tokens, presign, consumption): multivertical_django/apps/dynamic_forms/uploads.py
  • Hydration registry: multivertical_django/apps/dynamic_forms/registry.py
  • Occurrence hydration implementation: multivertical_django/apps/occurrences/hydration.py