diff --git a/src/openapi.yaml b/src/openapi.yaml index 0565bc4eb4..78e6163975 100644 --- a/src/openapi.yaml +++ b/src/openapi.yaml @@ -8564,6 +8564,7 @@ components: * `main` - Main * `authorizee` - Authorizee + prefillOptions: {} dataType: allOf: - $ref: '#/components/schemas/DataTypeEnum' diff --git a/src/openforms/forms/admin/form_variable.py b/src/openforms/forms/admin/form_variable.py index 64674c37be..99402fa0dd 100644 --- a/src/openforms/forms/admin/form_variable.py +++ b/src/openforms/forms/admin/form_variable.py @@ -18,6 +18,7 @@ class FormVariableAdmin(admin.ModelAdmin): "prefill_plugin", "prefill_attribute", "prefill_identifier_role", + "prefill_options", "data_type", "is_sensitive_data", "initial_value", @@ -31,6 +32,7 @@ class FormVariableAdmin(admin.ModelAdmin): "prefill_plugin", "prefill_attribute", "prefill_identifier_role", + "prefill_options", "data_type", "data_format", "is_sensitive_data", diff --git a/src/openforms/forms/api/serializers/form_variable.py b/src/openforms/forms/api/serializers/form_variable.py index 27abc70275..d4c07b08c5 100644 --- a/src/openforms/forms/api/serializers/form_variable.py +++ b/src/openforms/forms/api/serializers/form_variable.py @@ -8,6 +8,7 @@ from openforms.api.fields import RelatedFieldFromContext from openforms.api.serializers import ListWithChildSerializer +from openforms.prefill.registry import register from openforms.variables.api.serializers import ServiceFetchConfigurationSerializer from openforms.variables.constants import FormVariableSources from openforms.variables.models import ServiceFetchConfiguration @@ -110,6 +111,7 @@ class FormVariableSerializer(serializers.HyperlinkedModelSerializer): service_fetch_configuration = ServiceFetchConfigurationSerializer( required=False, allow_null=True ) + prefill_options = serializers.JSONField(required=False) class Meta: model = FormVariable @@ -124,6 +126,7 @@ class Meta: "prefill_plugin", "prefill_attribute", "prefill_identifier_role", + "prefill_options", "data_type", "data_format", "is_sensitive_data", @@ -177,18 +180,47 @@ 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." + ), + } + ) + + # check the specific validation options of the prefill plugin + if prefill_plugin: + plugin = register[prefill_plugin] + serializer = plugin.options(data=prefill_options) + try: + serializer.is_valid(raise_exception=True) + except serializers.ValidationError as e: + detail = {"options": e.detail} + raise serializers.ValidationError(detail) from e + return attrs diff --git a/src/openforms/forms/migrations/0102_remove_formvariable_prefill_config_empty_or_complete_and_more.py b/src/openforms/forms/migrations/0102_remove_formvariable_prefill_config_empty_or_complete_and_more.py new file mode 100644 index 0000000000..c0a6476c65 --- /dev/null +++ b/src/openforms/forms/migrations/0102_remove_formvariable_prefill_config_empty_or_complete_and_more.py @@ -0,0 +1,51 @@ +# Generated by Django 4.2.16 on 2024-10-18 10:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("forms", "0101_fix_empty_default_value"), + ] + + 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", ""), + models.Q(("prefill_options", {}), _negated=True), + ("source", "user_defined"), + ), + models.Q( + models.Q(("prefill_plugin", ""), _negated=True), + models.Q(("prefill_attribute", ""), _negated=True), + ("prefill_options", {}), + ), + _connector="OR", + ) + ), + name="prefill_config_component_or_user_defined", + ), + ), + ] diff --git a/src/openforms/forms/models/form_variable.py b/src/openforms/forms/models/form_variable.py index 1aa0493cd8..7c94fd7297 100644 --- a/src/openforms/forms/models/form_variable.py +++ b/src/openforms/forms/models/form_variable.py @@ -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 @@ -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"), @@ -197,10 +208,24 @@ 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 + ) ), - name="prefill_config_empty_or_complete", + name="prefill_config_component_or_user_defined", ), CheckConstraint( check=~Q( diff --git a/src/openforms/forms/tests/variables/test_model.py b/src/openforms/forms/tests/variables/test_model.py index d666666d06..5eca0756cf 100644 --- a/src/openforms/forms/tests/variables/test_model.py +++ b/src/openforms/forms/tests/variables/test_model.py @@ -5,17 +5,85 @@ from openforms.variables.constants import FormVariableDataTypes, FormVariableSources from openforms.variables.tests.factories import ServiceFetchConfigurationFactory -from ..factories import FormFactory, FormVariableFactory +from ..factories import FormFactory, FormStepFactory, FormVariableFactory class FormVariableModelTests(TestCase): - def test_prefill_plugin_empty_prefill_attribute_filled(self): + # valid cases (constraint: prefill_config_component_or_user_defined) + def test_prefill_plugin_prefill_attribute_prefill_options_empty(self): + # user defined + FormVariableFactory.create( + prefill_plugin="", + prefill_attribute="", + prefill_options={}, + ) + # component + FormStepFactory.create( + form_definition__configuration={ + "components": [ + { + "type": "textfield", + "key": "test-key", + "label": "Test label", + "prefill": {"plugin": "", "attribute": ""}, + } + ] + } + ) + + def test_prefill_options_empty(self): + FormVariableFactory.create( + prefill_plugin="demo", + prefill_attribute="demo", + prefill_options={}, + ) + + def test_prefill_attribute_empty(self): + FormVariableFactory.create( + prefill_plugin="demo", + prefill_attribute="", + prefill_options={"variables_mapping": [{"variable_key": "data"}]}, + ) + + # invalid cases (constraint: prefill_config_component_or_user_defined) + def test_prefill_plugin_prefill_attribute_empty(self): with self.assertRaises(IntegrityError): - FormVariableFactory.create(prefill_plugin="", prefill_attribute="demo") + FormVariableFactory.create( + prefill_plugin="", + prefill_attribute="", + prefill_options={"variables_mapping": [{"variable_key": "data"}]}, + ) - def test_prefill_plugin_filled_prefill_attribute_empty(self): + def test_prefill_plugin_prefill_options_empty(self): with self.assertRaises(IntegrityError): - FormVariableFactory.create(prefill_plugin="demo", prefill_attribute="") + FormVariableFactory.create( + prefill_plugin="", + prefill_attribute="demo", + prefill_options={}, + ) + + def test_prefill_plugin_prefill_attribute_prefill_options_not_empty(self): + with self.assertRaises(IntegrityError): + FormVariableFactory.create( + prefill_plugin="demo", + prefill_attribute="demo", + prefill_options={"variables_mapping": [{"variable_key": "data"}]}, + ) + + def test_prefill_attribute_prefill_options_empty(self): + with self.assertRaises(IntegrityError): + FormStepFactory.create( + form_definition__configuration={ + "components": [ + { + "type": "textfield", + "key": "test-key", + "label": "Test label", + "prefill": {"plugin": "demo", "attribute": ""}, + } + ] + } + ) def test_valid_prefill_plugin_config(self): try: