Core: form.io integration¶
Open Forms uses form.io under the hood to build and render forms, and then adds its own layers on top of that, such as:
implementing multi-step forms where every step is a form.io definition
evaluating backend logic using data from earlier steps
dynamically adapting form.io definitions as needed
This means that we process the form.io datastructures in the backend, using Python. All
the code for this is organized in the openforms.formio
package.
Changed in version 2.1.0: openforms.custom_field_types
was refactored into the openforms.formio
package,
and all of the separate registries (formatters, normalizers…) were merged into a
single compoment registry.
Supported features¶
Formatting values for display¶
Value formatting is done for displaying form submission data summaries, rendering confirmation PDFs and emails… It is aware if it’s in a HTML context or not. It is heavily used in the renderers.
Whenever a component plugin is registered, the
openforms.formio.registry.BasePlugin.formatter
class attribute must be
specified.
- openforms.formio.service.format_value(component: openforms.formio.typing.base.Component, value: Any, as_html: bool = False)
Format a submitted value in a way that is most appropriate for the component type.
Normalizing input data¶
Data for a component can be sourced from external systems that employ different formatting rules compared to what form.io expects. Normalizing this data helps to be able to make proper comparisons at different stages in the submission life-cycle.
You can opt-in to this by configuring openforms.formio.registry.BasePlugin.normalizer
.
- openforms.formio.service.normalize_value_for_component(component: openforms.formio.typing.base.Component, value: Any) Any
Given a value (actual or default value) and the component, apply the component- specific normalization.
Dynamically modifying component configuration¶
Certain component types require on-the-fly configuration rewriting, such as applying global configuration options that may change independently from when the form is actually being designed.
Dynamic rewriting is enabled by implementing
openforms.formio.registry.BasePlugin.mutate_config_dynamically()
. It receives the
current openforms.submissions.models.Submission
instance and a mapping of all
the variable names and values at the time.
- openforms.formio.service.get_dynamic_configuration(config_wrapper: openforms.formio.datastructures.FormioConfigurationWrapper, request: rest_framework.request.Request, submission: openforms.submissions.models.submission.Submission, data: dict[str, Any] | None = None) openforms.formio.datastructures.FormioConfigurationWrapper
Given a static Formio configuration, apply the hooks to dynamically transform this.
The configuration is modified in the context of the provided
submission
parameter.
For an example of a custom field type, see openforms.formio.components.custom.Date
.
Finally, the resulting resolved component definitions are evaluated with the template
engine where variable values are evaluated for compoment labels, descriptions… and
configuration based on the HTTP request is performed (see
openforms.formmio.service.rewrite_formio_components_for_request()
).
Reference¶
Public API - openforms.formio.service
¶
Expose the public openforms.formio Python API.
The service module exposes the functions/utilities that may be used by other Django apps/packages:
Try to keep this module stable and avoid breaking changes - extensions may rely on this!
Keep it small! The actual implementation should be done in specialized subpackages or submodules and only their ‘public’ API should be imported and used.
- class openforms.formio.service.FormioData(dict=None, /, **kwargs)¶
Handle formio (submission) data transparently.
Form.io supports component keys in the format ‘topLevel.nested’ which get converted to deep-setting of object properties (using
lodash.set
internally). This datastructure mimicks that interface in Python so we can more naturally perform operations like:data = FormioData() for component in iter_components(...): data[component["key"]] = ...
without having to worry about potential deep assignments or leak implementation details (such as using
glom
for this).
- openforms.formio.service.build_serializer(components: list[openforms.formio.typing.base.Component], _register: Optional[openforms.formio.registry.ComponentRegistry] = None, **kwargs)¶
Translate a sequence of Formio.js component definitions into a serializer.
This recursively builds up the serializer fields for each (nested) component and puts them into a serializer instance ready for validation.
- openforms.formio.service.format_value(component: openforms.formio.typing.base.Component, value: Any, as_html: bool = False)¶
Format a submitted value in a way that is most appropriate for the component type.
- openforms.formio.service.get_dynamic_configuration(config_wrapper: openforms.formio.datastructures.FormioConfigurationWrapper, request: rest_framework.request.Request, submission: openforms.submissions.models.submission.Submission, data: dict[str, Any] | None = None) openforms.formio.datastructures.FormioConfigurationWrapper ¶
Given a static Formio configuration, apply the hooks to dynamically transform this.
The configuration is modified in the context of the provided
submission
parameter.
- openforms.formio.service.inject_variables(configuration: openforms.formio.datastructures.FormioConfigurationWrapper, values: dict[str, Any]) None ¶
Inject the variable values into the Formio configuration.
Takes a Formio configuration and fully resolved variable state (mapping of variable name to its value as a Python object). The configuration is iterated over and every component is checked for properties that can be templated. Note that the configuration is mutated in the process!
- Parameters
configuration – A dictionary containing the static Formio configuration (from the form designer)
values – A mapping of variable key to its value (Python native objects)
- Returns
None - this function mutates the datastructures in place
Todo
Support getting non-string based configuration from variables, such as validate.required etc.
- openforms.formio.service.iterate_data_with_components(configuration: dict[str, 'JSONPrimitive | JSONObject | list[JSONValue]'], data: dict[str, 'JSONPrimitive | JSONObject | list[JSONValue]'], data_path: glom.core.Path = Path(), configuration_path: str = 'components', filter_types: Optional[list[str]] = None) Optional[Iterator[openforms.formio.utils.ComponentWithDataItem]] ¶
Iterate through a configuration and return a tuple with the component JSON, its value in the submission data and the path within the submission data.
For example, for a configuration with components:
[ {"key": "surname", "type": "textfield"}, {"key": "pets", "type": "editgrid", "components": [{"key": "name", "type": "textfield"}]} ]
And a submission data:
{"surname": "Doe", "pets": [{"name": "Qui"}, {"name": "Quo"}, {"name": "Qua"}] }
For the “Qui” item of the repeating group this function would yield:
ComponentWithDataItem({"key": "name", "type": "textfield"}, "Qui", "pets", "pets.0.name")
.
- openforms.formio.service.normalize_value_for_component(component: openforms.formio.typing.base.Component, value: Any) Any ¶
Given a value (actual or default value) and the component, apply the component- specific normalization.
- openforms.formio.service.recursive_apply(input: JSONPrimitive | JSONObject | list[JSONValue], func: Callable, transform_leaf: bool = False, *args, **kwargs)¶
Take an input - property value and recursively apply
func
to it.The
input
may be a string to be used as template, another JSON primitive that we can’t pass through the template engine or a complex JSON object to recursively render.Returns the same datatype as the input datatype, which should be ready for JSON serialization unless transform_leaf flag is set to True where func is applied to the nested value as well.
- openforms.formio.service.rewrite_formio_components_for_request(configuration_wrapper: openforms.formio.datastructures.FormioConfigurationWrapper, request: rest_framework.request.Request) openforms.formio.datastructures.FormioConfigurationWrapper ¶
Loop over the formio configuration and inject request-specific configuration.
- class openforms.formio.registry.BasePlugin(identifier: str)¶
Base class for Formio component plugins.
- formatter: type[openforms.formio.registry.FormatterProtocol[-ComponentT]]¶
Specify the callable to use for formatting.
Formatter (class) implementation, used by
openforms.formio.registry.ComponentRegistry.format()
.
- normalizer: Optional[openforms.formio.registry.NormalizerProtocol[openforms.formio.registry.ComponentT]] = None¶
Specify the normalizer callable to use for value normalization.
- rewrite_for_request: Optional[openforms.formio.registry.RewriterForRequestProtocol[openforms.formio.registry.ComponentT]] = None¶
Callback to invoke to rewrite plugin configuration for a given HTTP request.
Extending¶
Using our usual extension pattern you can register your own types.
Extensions should inherit from openforms.formio.registry.BasePlugin
or
implement the same protocol(s) and be registered with their form.io type:
from openforms.formio.formatters.formio import DefaultFormatter
from openforms.formio.registry import BasePlugin
@register("myCustomType")
class MyComponent(BasePlugin):
formatter = DefaultFormatter
You can find some examples in openforms.formio.components.custom
.
Private API¶
Module: openforms.formio.dynamic_config
¶
Implement component-specific dynamic configuration based on (submission) state.
- openforms.formio.dynamic_config.rewrite_formio_components(configuration_wrapper: openforms.formio.datastructures.FormioConfigurationWrapper, submission: openforms.submissions.models.submission.Submission, data: Optional[dict[str, Any]] = None) openforms.formio.datastructures.FormioConfigurationWrapper ¶
Loop over the formio configuration and mutate components in place.
- Parameters
configuration_wrapper – Container object holding the Formio form configuration to be updated (if applicable). The rewriting loops over all components one-by-one and applies the changes.
submission – The submission instance for which we are rewriting the configurations. This allows you to inspect state and use convenience methods that may not be available in the variables data.
data – key-value mapping of variable name to variable value. If a submission context is available, the variables of the submission are included here.
- openforms.formio.dynamic_config.rewrite_formio_components_for_request(configuration_wrapper: openforms.formio.datastructures.FormioConfigurationWrapper, request: rest_framework.request.Request) openforms.formio.datastructures.FormioConfigurationWrapper ¶
Loop over the formio configuration and inject request-specific configuration.
Module: openforms.formio.formatters
¶
Module: openforms.formio.rendering
¶
Implement the bindings with openforms.submissions.rendering.Renderer
.
Formio is currently the engine powering the form definitions/configurations which brings along some implementation details. The specifics of rendering Formio configurations together with the data of a submission are contained in this subpackage.
The module openforms.submissions.rendering.nodes
contains the (base) node
classes with their public API. For specific Formio component types, you can register
a more specific subclass using the registry. The vanilla Formio components requiring
special attention are implemented in openforms.submissions.rendering.default
.