Skip to content

Commit

Permalink
[#4396] Added prefill_options field to the form variable
Browse files Browse the repository at this point in the history
  • Loading branch information
vaszig authored and sergei-maertens committed Oct 11, 2024
1 parent 1ae8370 commit 217cc5e
Show file tree
Hide file tree
Showing 7 changed files with 427 additions and 44 deletions.
40 changes: 40 additions & 0 deletions src/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8554,6 +8554,8 @@ components:
* `main` - Main
* `authorizee` - Authorizee
prefillOptions:
$ref: '#/components/schemas/FormVariableOptions'
dataType:
allOf:
- $ref: '#/components/schemas/DataTypeEnum'
Expand Down Expand Up @@ -8585,6 +8587,24 @@ components:
- key
- name
- source
FormVariableOptions:
type: object
properties:
objectsApiGroup:
type: integer
description: Which Objects API group to use.
objecttypeUuid:
type: string
format: uuid
title: objecttype
description: 'UUID of the objecttype in the Objecttypes API. '
objecttypeVersion:
type: integer
description: Version of the objecttype in the Objecttypes API.
variablesMapping:
type: array
items:
$ref: '#/components/schemas/ObjecttypeVariableMapping'
FormVersion:
type: object
properties:
Expand Down Expand Up @@ -9138,6 +9158,26 @@ components:
- namePlural
- url
- uuid
ObjecttypeVariableMapping:
type: object
description: A mapping between a form variable key and the corresponding Objecttype
attribute.
properties:
variableKey:
type: string
description: The 'dotted' path to a form variable key. The format should
comply to how Formio handles nested component keys.
pattern: ^(\w|\w[\w.\-]*\w)$
targetPath:
type: array
items:
type: string
title: Segment of a JSON path
description: Representation of the JSON target location as a list of string
segments.
required:
- targetPath
- variableKey
ObjecttypeVersion:
type: object
properties:
Expand Down
2 changes: 2 additions & 0 deletions src/openforms/forms/admin/form_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class FormVariableAdmin(admin.ModelAdmin):
"prefill_plugin",
"prefill_attribute",
"prefill_identifier_role",
"prefill_options",
"data_type",
"is_sensitive_data",
"initial_value",
Expand All @@ -31,6 +32,7 @@ class FormVariableAdmin(admin.ModelAdmin):
"prefill_plugin",
"prefill_attribute",
"prefill_identifier_role",
"prefill_options",
"data_type",
"data_format",
"is_sensitive_data",
Expand Down
101 changes: 94 additions & 7 deletions src/openforms/forms/api/serializers/form_variable.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
from collections import defaultdict

from django.db.models import Q
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from drf_spectacular.utils import OpenApiExample, extend_schema_serializer
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from openforms.api.fields import RelatedFieldFromContext
from openforms.api.fields import (
PrimaryKeyRelatedAsChoicesField,
RelatedFieldFromContext,
)
from openforms.api.serializers import ListWithChildSerializer
from openforms.formio.api.fields import FormioVariableKeyField
from openforms.registrations.contrib.objects_api.models import ObjectsAPIGroupConfig
from openforms.utils.mixins import JsonSchemaSerializerMixin
from openforms.variables.api.serializers import ServiceFetchConfigurationSerializer
from openforms.variables.constants import FormVariableSources
from openforms.variables.models import ServiceFetchConfiguration
Expand All @@ -16,6 +24,64 @@
from ...models import Form, FormDefinition, FormVariable


@extend_schema_serializer(
examples=[
OpenApiExample(
name="Variable mapping example",
value={
"variable_key": "a_component_variable",
"target_path": ["path", "to.the", "target"],
},
)
]
)
class ObjecttypeVariableMappingSerializer(serializers.Serializer):
"""A mapping between a form variable key and the corresponding Objecttype attribute."""

variable_key = FormioVariableKeyField(
label=_("variable key"),
help_text=_(
"The 'dotted' path to a form variable key. The format should comply to how Formio handles nested component keys."
),
)
target_path = serializers.ListField(
child=serializers.CharField(label=_("Segment of a JSON path")),
label=_("target path"),
help_text=_(
"Representation of the JSON target location as a list of string segments."
),
)


class FormVariableOptionsSerializer(JsonSchemaSerializerMixin, serializers.Serializer):
objects_api_group = PrimaryKeyRelatedAsChoicesField(
queryset=ObjectsAPIGroupConfig.objects.exclude(
Q(objects_service=None)
| Q(objecttypes_service=None)
| Q(drc_service=None)
| Q(catalogi_service=None)
),
label=("Objects API group"),
required=False,
help_text=_("Which Objects API group to use."),
)
objecttype_uuid = serializers.UUIDField(
label=_("objecttype"),
required=False,
help_text=_("UUID of the objecttype in the Objecttypes API. "),
)
objecttype_version = serializers.IntegerField(
label=_("objecttype version"),
required=False,
help_text=_("Version of the objecttype in the Objecttypes API."),
)
variables_mapping = ObjecttypeVariableMappingSerializer(
label=_("variables mapping"),
many=True,
required=False,
)


class FormVariableListSerializer(ListWithChildSerializer):
def get_child_serializer_class(self):
return FormVariableSerializer
Expand Down Expand Up @@ -110,6 +176,7 @@ class FormVariableSerializer(serializers.HyperlinkedModelSerializer):
service_fetch_configuration = ServiceFetchConfigurationSerializer(
required=False, allow_null=True
)
prefill_options = FormVariableOptionsSerializer(required=False)

class Meta:
model = FormVariable
Expand All @@ -124,6 +191,7 @@ class Meta:
"prefill_plugin",
"prefill_attribute",
"prefill_identifier_role",
"prefill_options",
"data_type",
"data_format",
"is_sensitive_data",
Expand Down Expand Up @@ -177,18 +245,37 @@ def validate(self, attrs):
}
)

# prefill plugin and attribute must both or both not be set
# Check the combination of the provided prefill-attributes (see the model constraints)
source = attrs.get("source") or ""
prefill_plugin = attrs.get("prefill_plugin") or ""
prefill_attribute = attrs.get("prefill_attribute") or ""
if (prefill_plugin and not prefill_attribute) or (
not prefill_plugin and prefill_attribute
):
prefill_options = attrs.get("prefill_options")

if prefill_plugin and prefill_options and prefill_attribute:
raise ValidationError(
{
"prefill_attribute": _(
"Prefill plugin and attribute must both be specified."
"prefill_attribute_options": _(
"Prefill plugin, attribute and options can not be specified at the same time."
),
}
)

if source == FormVariableSources.component:
if prefill_options:
raise ValidationError(
{
"component_prefill_attribute": _(
"Prefill options should not be specified for component variables."
),
}
)
if not prefill_plugin and prefill_attribute:
raise ValidationError(
{
"component_prefill_attribute": _(
"Prefill attribute cannot be specified without prefill plugin for component variables."
),
}
)

return attrs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Generated by Django 4.2.16 on 2024-10-02 07:06

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("forms", "0100_merge_20240920_1816"),
]

operations = [
migrations.RemoveConstraint(
model_name="formvariable",
name="prefill_config_empty_or_complete",
),
migrations.AddField(
model_name="formvariable",
name="prefill_options",
field=models.JSONField(
blank=True, default=dict, verbose_name="prefill options"
),
),
migrations.AddConstraint(
model_name="formvariable",
constraint=models.CheckConstraint(
check=models.Q(
models.Q(
models.Q(
("prefill_plugin", ""),
("prefill_attribute", ""),
("prefill_options", {}),
),
models.Q(
models.Q(("prefill_plugin", ""), _negated=True),
("prefill_attribute", ""),
("prefill_options", {}),
("source", "user_defined"),
),
models.Q(
models.Q(("prefill_plugin", ""), _negated=True),
models.Q(("prefill_attribute", ""), _negated=True),
("prefill_options", {}),
),
models.Q(
models.Q(("prefill_plugin", ""), _negated=True),
("prefill_attribute", ""),
models.Q(("prefill_options", {}), _negated=True),
("source", "user_defined"),
),
_connector="OR",
)
),
name="prefill_config_component_or_user_defined",
),
),
]
59 changes: 56 additions & 3 deletions src/openforms/forms/models/form_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
from .form_step import FormStep


EMPTY_PREFILL_PLUGIN = Q(prefill_plugin="")
EMPTY_PREFILL_ATTRIBUTE = Q(prefill_attribute="")
EMPTY_PREFILL_OPTIONS = Q(prefill_options={})
USER_DEFINED = Q(source=FormVariableSources.user_defined)


class FormVariableManager(models.Manager):
use_in_migrations = True

Expand Down Expand Up @@ -161,6 +167,11 @@ class FormVariable(models.Model):
default=IdentifierRoles.main,
max_length=100,
)
prefill_options = models.JSONField(
_("prefill options"),
default=dict,
blank=True,
)
data_type = models.CharField(
verbose_name=_("data type"),
help_text=_("The type of the value that will be associated with this variable"),
Expand Down Expand Up @@ -197,10 +208,30 @@ class Meta:
constraints = [
CheckConstraint(
check=Q(
(Q(prefill_plugin="") & Q(prefill_attribute=""))
| (~Q(prefill_plugin="") & ~Q(prefill_attribute=""))
(
EMPTY_PREFILL_PLUGIN
& EMPTY_PREFILL_ATTRIBUTE
& EMPTY_PREFILL_OPTIONS
)
| (
~EMPTY_PREFILL_PLUGIN
& EMPTY_PREFILL_ATTRIBUTE
& EMPTY_PREFILL_OPTIONS
& USER_DEFINED
)
| (
~EMPTY_PREFILL_PLUGIN
& ~EMPTY_PREFILL_ATTRIBUTE
& EMPTY_PREFILL_OPTIONS
)
| (
~EMPTY_PREFILL_PLUGIN
& EMPTY_PREFILL_ATTRIBUTE
& ~EMPTY_PREFILL_OPTIONS
& USER_DEFINED
)
),
name="prefill_config_empty_or_complete",
name="prefill_config_component_or_user_defined",
),
CheckConstraint(
check=~Q(
Expand Down Expand Up @@ -245,7 +276,29 @@ def check_data_type_and_initial_value(self):
if self.source == FormVariableSources.user_defined:
self.initial_value = check_initial_value(self.initial_value, self.data_type)

def check_if_prefill_plugin_mapping_needed(self):
"""
This is used for user_defined variables which have the mappings for pre-filling
component variables. These are not aware of the prefill_plugin so we have to update them.
"""

mappings = self.prefill_options["variables_mapping"]
form_variable_keys = [item["variable_key"] for item in mappings]
form_variables_for_update = FormVariable.objects.filter(
key__in=form_variable_keys
)
for variable in form_variables_for_update:
variable.prefill_plugin = self.prefill_plugin

FormVariable.objects.bulk_update(form_variables_for_update, ["prefill_plugin"])

def save(self, *args, **kwargs):
self.check_data_type_and_initial_value()
if (
self.source == FormVariableSources.user_defined
and self.prefill_plugin
and self.prefill_options
):
self.check_if_prefill_plugin_mapping_needed()

super().save(*args, **kwargs)
Loading

0 comments on commit 217cc5e

Please sign in to comment.