Formulieren — tips & valkuilen¶
Praktische aandachtspunten bij het bouwen en registreren van Open Formulieren in
deze stack. Het formulier "Laravel NAW-formulier" is het levende voorbeeld:
definitie in config/forms/laravel-naw/form.py,
opbouw via config/forms/laravel-naw/deploy.sh.
De meeste van deze punten gelden omdat wij formulieren via een script (de
manage.py shell) opbouwen i.p.v. via de admin-UI. De UI vult dan namelijk een
aantal dingen automatisch in die wij zelf moeten zetten.
0. Een formulier live krijgen (deployen)¶
Een formulier "ontwikkelen" = de definitie schrijven in een *-form.py-script.
Live krijgen = dat script uitvoeren tegen de juiste omgeving. De definitie zet zelf
al active=True en maintenance_mode=False, dus na een succesvolle run staat het
formulier publiek op https://<of-host>/<form-slug>.
Stappen voor een nieuw formulier:
-
Map + definitie schrijven. Maak
config/forms/<slug>/aan — daar staat alles van één formulier bij elkaar. Kopieerconfig/forms/skeleton-form.pynaarconfig/forms/<slug>/form.pyen vul de TODO-blokken (velden, logica, categorie, registratie). Voegconfig/forms/<slug>/objecttype.json(objecttype- definitie) enconfig/forms/<slug>/deploy.sh(kopie van een bestaand formulier) toe. Houd de valkuilen hieronder aan. -
Randvoorwaarden klaarzetten (eenmalig per omgeving, in deze volgorde):
- Objecttype waarin de inzending wordt geregistreerd →
scripts/bootstrap-objecttype.sh <env>(scantconfig/objecttypes/*.jsonénconfig/forms/*/objecttype.json; zet deOBJECTTYPE_*_UUIDin.env). - Services + permissies (objects-groep, DRC/Catalogi) →
./commonground-run.sh <env> setup. -
Documenttypen (PDF "Aanvraag", upload "Bijlage", evt. "Brief") in de catalogus →
scripts/bootstrap-informatieobjecttype.sh <env>. -
Het script uitvoeren in de draaiende
openforms-container (idempotent — je kunt het zo vaak draaien als je wilt, het doetupdate_or_create):
docker compose --env-file .env exec -T \
-e OBJECTTYPE_UUID="<uuid van je objecttype>" \
-e OBJECTS_GROUP="objects" \
-e CATALOGUS_DOMEIN="LAR" -e CATALOGUS_RSIN="123456782" \
-e IOT_SUBMISSION_REPORT="Aanvraag" -e IOT_ATTACHMENT="Bijlage" \
openforms python src/manage.py shell < config/forms/<slug>/form.py
Maak hiervoor bij voorkeur een eigen wrapper naar het model van
config/forms/laravel-naw/deploy.sh: die
leest .env, controleert de randvoorwaarden en draait bovenstaande regel. Zo is
de deploy reproduceerbaar en per omgeving (test/acc/prod) hetzelfde.
- Controleren. Open
https://<of-host>/<form-slug>en doe een testinzending (zie §6); controleer dat het object in de Objects-API en de PDF/bijlagen in de DRC landen.
Bestaand formulier wijzigen: pas het *-form.py-script aan en draai stap 3
opnieuw. Omdat alles via update_or_create (en het opnieuw genereren van stappen,
variabelen en logica) loopt, is het script de bron van waarheid — wijzig dus het
script, niet de admin-UI, anders raakt de volgende deploy je handmatige wijziging kwijt.
OTAP: draai dezelfde wrapper met test → acc → prod. Per omgeving horen een
eigen .env, objecttype-UUID en catalogus erbij; het *-form.py-script zelf is identiek.
Tijdelijk offline halen kan zónder her-deploy via de admin (formulier op onderhoudsmodus of niet actief), maar bij de volgende scriptrun worden
active/maintenance_modeweer op de scriptwaarden gezet.
1. Upload/file-component moet volledig zijn¶
Een handmatig aangemaakt file-component met een leeg "file": {} laat het hele
formulier in de browser crashen:
De SDK-renderer leest file.type als array (o.a. file.type.map(...)). Ontbreekt
die, dan klapt het validatieschema om en wordt het formulier niet getoond. Zet
minimaal:
{"type": "file", "key": "bijlage", ...,
"file": {"type": ["*"], "allowedTypesLabels": ["alle bestandstypen"]},
"filePattern": "*", "useConfigFiletypes": False, "url": ""}
Cosmetische console-meldingen zoals
cookiebar.js … Loading the font 'data:font…' violates … font-srcofblocked-uri: evalzijn geen blokkers — die breken de render niet. Zoek bij "formulier wordt niet getoond" altijd naar de échte rodeTypeError.
1b. Keuze-componenten hebben openForms.dataSrc nodig¶
Een handmatig aangemaakt radio-, select- of selectboxes-component met alleen
de Form.io-opties (values / data.values) toont het formulier prima aan de
invuller, maar laat de bouwer-editmodal in de admin crashen zodra je de
component-details opent:
De admin-bouwer leest component.openForms.dataSrc om te bepalen waar de opties
vandaan komen (vaste lijst / variabele / referentielijst). Ontbreekt de
openForms-namespace, dan klapt de editmodal om en zie je niets. Zet bij elk
keuze-component minimaal:
{"type": "selectboxes", "key": "faciliteiten", "label": "...",
"values": [...],
"openForms": {"dataSrc": "manual", "translations": {}}}
dataSrc: "manual" = een vaste, in het component opgenomen optielijst. Een handige
normalisatie bij script-matig bouwen (zie ../config/forms/evenementenvergunning/form.py):
CHOICE_TYPES = {"radio", "select", "selectboxes"}
def normaliseer(components):
for c in components:
if c.get("type") in CHOICE_TYPES:
of = c.setdefault("openForms", {})
of.setdefault("dataSrc", "manual")
of.setdefault("translations", {})
return components
Voorwaardelijke OPTIES binnen één selectboxes: dataSrc: variable + itemsExpression¶
De opties van een keuzecomponent laten afhangen van een eerder antwoord is een
OF-native feature: zet op het component openForms.dataSrc = "variable" en
openForms.itemsExpression = een JsonLogic-expressie die de optielijst teruggeeft.
Bijvoorbeeld (uitgebreide lijst bij locatie_type = buiten, anders basis):
"openForms": {"dataSrc": "variable", "itemsExpression": {"if": [
{"==": [{"var": "locatie_type"}, "buiten"]},
[["ehbo", "EHBO-post"], ["tent", "Tent"]], # buiten
[["ehbo", "EHBO-post"]], # anders
]}}
⚠️ Itemsformaat (dé valkuil): elk item moet een scalar zijn (waarde = label)
of een 2-elementen-lijst [waarde, label]. Een dict {"value":.., "label":..}
laat json-logic crashen met "Unrecognized operation value" → HTTP 500 op de
stap-API. (OF leest items via normalise_option in
formio/dynamic_config/dynamic_options.py: een lijst → [0]=waarde, [1]=label,
anders → scalar.)
Doe dit niet via een logica-regel met een property-actie die de eigenschap
valuesoverschrijft: dan krijgt de invuller "ongeldige invoer" bij het indienen en is de regel in de admin onzichtbaar. Een prima alternatief blijft twee aparte selectboxes (basis altijd zichtbaar, "extra" via de component-conditional) als je liever helemaal geen variabele-opties gebruikt.
2. Registratie Objects-API: gebruik v2 (Variabelekoppelingen)¶
De plugin kent twee varianten:
| Variant | version |
Hoe het object wordt opgebouwd |
|---|---|---|
| Variabelekoppelingen (aanbevolen) | 2 |
variables_mapping: koppel variabelen aan paden |
| Verouderd (sjabloon) | 1 |
content_json Django-template |
v1 werkt nog, maar is gemarkeerd als verouderd. Gebruik v2 met
variables_mapping, bijvoorbeeld:
"variables_mapping": [
{"variable_key": "public_reference", "target_path": ["of_referentie"]},
{"variable_key": "pdf_url", "target_path": ["pdf"]},
{"variable_key": "attachment_urls", "target_path": ["bijlagen"]},
{"variable_key": "voornaam", "target_path": ["naw", "voornaam"]},
# ...
]
Beschikbare registratie-variabelen (naast de component-variabelen):
public_reference, pdf_url, csv_url, attachment_urls, submission_id,
plus de payment_*-variabelen.
3. Documenttypen: catalogus + omschrijving (niet de URL-velden)¶
De keuzelijsten onder "Documenttypen" in het registratiescherm worden gevuld door
de catalogus + omschrijving-variant. De directe URL-velden
(informatieobjecttype_*) zijn legacy (verdwijnen in OF 3.0) en laten die
keuzelijsten leeg — ook al werken ze functioneel wel. Gebruik dus:
"catalogue": {"domain": "LAR", "rsin": "123456782"},
"iot_submission_report": "Aanvraag", # documenttype inzendings-PDF
"iot_attachment": "Bijlage", # documenttype geüploade bijlagen
De omschrijving moet exact overeenkomen met een gepubliceerd
informatieobjecttype in die catalogus (zie
scripts/bootstrap-informatieobjecttype.sh).
De upload naar de Documenten-API gebeurt op basis van iot_attachment —
onafhankelijk van of het file-component in de mapping staat.
Afwijkend documenttype per upload-component (override)¶
iot_attachment is het standaard documenttype voor álle uploads. Wil je voor
een specifiek upload-component een ánder documenttype, zet dat dan op het
file-component zelf via registration.documentType:
{"type": "file", "key": "brief", "label": "Brief", "multiple": True,
"storage": "url", "url": "", "useConfigFiletypes": False,
"file": {"type": ["*"], "allowedTypesLabels": ["alle bestandstypen"]},
"filePattern": "*",
"registration": {"documentType": {
"description": "Brief", # omschrijving van het IOT
"catalogue": {"domain": "LAR", "rsin": "123456782"},
}}}
Uploads uit dit component krijgen dan documenttype "Brief" in de Documenten-API;
componenten zónder override vallen terug op iot_attachment. Ook de override-
omschrijving moet een gepubliceerd IOT in die catalogus zijn.
Documenttype ≠ plek in het object. Het documenttype wordt bij de upload bepaald; waar de URL's in het object terechtkomen bepaalt de mapping. Wil je alle uploads (uit meerdere componenten) in één node verzamelen, koppel dan de registratie-variabele
attachment_urlsaan dat pad (bv.["bijlagen"]). Wil je ze juist per component scheiden, koppel dan elk file-component apart (de component-variabele levert dan alleen de eigen upload-URL's). De documenttypen blijven in beide gevallen per upload correct.
4. Statische en samengestelde waarden → gebruikersvariabelen¶
v2 koppelt alleen variabelen; er is geen sjabloon. Voor waarden die niet één-op-één een formulierveld zijn:
- Statische waarde (bv. het verplichte
type-veld): een gebruikersvariabele met een vasteinitial_value. - Samengestelde waarde (bv. volledige naam): een gebruikersvariabele die door een logica-regel wordt gevuld met een JsonLogic-expressie:
Koppel die variabelen daarna gewoon mee in variables_mapping.
5. Logica-regels via script — koppel ze aan de stap!¶
Dé valkuil bij script-matig aanmaken: de (nieuwe) logica-evaluatie haalt regels op
via de M2M FormStep.logic_rules. Zonder die koppeling wordt een regel nooit
geëvalueerd (de waarde blijft dan leeg, zonder foutmelding). De admin-UI vult dit
automatisch via regel-analyse; bij directe model-aanmaak moet je het zelf doen:
rule = FormLogic.objects.create(form=form, ...)
rule.form_steps.set(list(FormStep.objects.filter(form=form)))
Andere velden van een logica-regel:
is_advanced=True— gebruik dit bij niet-triviale triggers/waarden. De visuele (eenvoudige) editor kan een samengesteldecat/if-waarde niet weergeven en geeft anders een renderfout in de admin.description— vrij in te vullen label van de regel (bv. "Stel volledige naam samen"). Dit is wat je als naam in de admin ziet.json_logic_trigger— de leesbare tekst eronder ("Als …") wordt automatisch uit de expressie afgeleid; die is niet los in te typen. Een betekenisvolle trigger ({"!!": [{"var": "achternaam"}]}, "zodra achternaam is ingevuld") leest dus prettiger dan een lege truc als{"==": [1, 1]}.trigger_from_step— vanaf welke stap de regel mag triggeren.
"Deze regel wordt op alle stappen uitgevoerd" — geen fout¶
Die melding wordt afgeleid uit form_steps: OF toont "alle stappen" zodra de
regel aan álle stappen van het formulier hangt. Bij een formulier met één
stap is "alle stappen" gelijk aan die ene stap — daar is niets aan te doen en
niets mis mee. De specifieke stapnaam verschijnt pas bij meerdere stappen waarbij
de regel aan een deel daarvan hangt. form_steps is read-only/automatisch en
moét gevuld blijven (zie hierboven).
6. Testinzending via de API (zonder browser)¶
Handig om end-to-end te testen. Belangrijke punten:
- Gebruik Django's DRF
APIClient(enforce_csrf_checks=False)binnen de container (manage.py shell); dan hoef je niet met CSRF-cookies te stoeien. - De volgorde:
POST /api/v2/submissions→ bijlage uploaden viaPOST /api/v2/formio/fileupload→PUT …/steps/{uuid}met de stapdata →POST …/steps/{uuid}/_check-logic→POST …/submissions/{uuid}/_complete→ status pollen op destatusUrl. - De
_check-logic-stap is nodig om logica-gezette gebruikersvariabelen (zoals de samengestelde naam) te laten evalueren en persisteren — zoals de echte SDK ook doet. Sla je die over, dan blijft zo'n variabele leeg. - Het file-component in de stapdata is een formio-bestandsobject dat verwijst naar
de tijdelijke upload, met een niet-lege
data.baseUrl(bv.{OF}/api/v2).
Snelle checklist bij "het werkt niet"¶
- Formulier toont niet → zoek de rode
TypeErrorin de console (vaak een onvolledig component, niet de CSP-meldingen). - Veld komt leeg in het object → staat de variabele in
variables_mapping? En bij een logica-variabele: is de regel aan de stap gekoppeld (form_steps)? - Keuzelijsten documenttypen leeg → gebruik je
catalogue+iot_*i.p.v. de URL-velden? - Document upload mist → bestaat het informatieobjecttype (omschrijving) en is het gepubliceerd?