diff --git a/docs/installation/upgrade-300.rst b/docs/installation/upgrade-300.rst index 8c81815704..6bbca2d57b 100644 --- a/docs/installation/upgrade-300.rst +++ b/docs/installation/upgrade-300.rst @@ -75,6 +75,13 @@ The import conversion of StUF-ZDS plugin extensions, back to the default StUF-ZD has been removed. We recommend re-creating the exports on a newer version of Open Forms, or manually changing the plugin to `stuf-zds-create-zaak` in the export files. +Removal of single registration conversion +----------------------------------------- + +The legacy format (from before Open Forms 2.3) for registration backend will no longer be +converted to the current standard. When importing a form with this configuration, +the form will be created without registration backends. + Removal of /api/v2/location/get-street-name-and-city endpoint ============================================================= diff --git a/src/openapi.yaml b/src/openapi.yaml index 0791ef66e7..7d709adced 100644 --- a/src/openapi.yaml +++ b/src/openapi.yaml @@ -1325,8 +1325,6 @@ paths: - `internalName` - `registrationBackends` - - `registrationBackend` - - `registrationBackendOptions` - `authenticationBackendOptions` - `paymentBackend` - `paymentBackendOptions` @@ -1399,8 +1397,6 @@ paths: - `internalName` - `registrationBackends` - - `registrationBackend` - - `registrationBackendOptions` - `authenticationBackendOptions` - `paymentBackend` - `paymentBackendOptions` @@ -1917,8 +1913,6 @@ paths: - `internalName` - `registrationBackends` - - `registrationBackend` - - `registrationBackendOptions` - `authenticationBackendOptions` - `paymentBackend` - `paymentBackendOptions` @@ -1997,8 +1991,6 @@ paths: - `internalName` - `registrationBackends` - - `registrationBackend` - - `registrationBackendOptions` - `authenticationBackendOptions` - `paymentBackend` - `paymentBackendOptions` @@ -2081,8 +2073,6 @@ paths: - `internalName` - `registrationBackends` - - `registrationBackend` - - `registrationBackendOptions` - `authenticationBackendOptions` - `paymentBackend` - `paymentBackendOptions` @@ -7522,7 +7512,7 @@ components: Note that this schema is used for both non-admin users filling out forms and admin users designing forms. The fields that are only relevant for admin users are: - `internalName`, `registrationBackends`, `registrationBackend`, `registrationBackendOptions`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. + `internalName`, `registrationBackends`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. properties: uuid: type: string @@ -7544,12 +7534,6 @@ components: type: array items: $ref: '#/components/schemas/FormRegistrationBackend' - registrationBackend: - type: string - deprecated: true - registrationBackendOptions: - nullable: true - deprecated: true authenticationBackends: type: array items: @@ -8035,7 +8019,7 @@ components: Note that this schema is used for both non-admin users filling out forms and admin users designing forms. The fields that are only relevant for admin users are: - `internalName`, `registrationBackends`, `registrationBackend`, `registrationBackendOptions`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. + `internalName`, `registrationBackends`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. properties: name: type: string @@ -8206,7 +8190,7 @@ components: Note that this schema is used for both non-admin users filling out forms and admin users designing forms. The fields that are only relevant for admin users are: - `internalName`, `registrationBackends`, `registrationBackend`, `registrationBackendOptions`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. + `internalName`, `registrationBackends`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. properties: name: type: string @@ -9140,7 +9124,7 @@ components: Note that this schema is used for both non-admin users filling out forms and admin users designing forms. The fields that are only relevant for admin users are: - `internalName`, `registrationBackends`, `registrationBackend`, `registrationBackendOptions`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. + `internalName`, `registrationBackends`, `authenticationBackendOptions`, `paymentBackend`, `paymentBackendOptions`, `priceVariableKey`, `product`, `category`, `theme`, `activateOn`, `deactivateOn`, `isDeleted`, `submissionConfirmationTemplate`, `askPrivacyConsent`, `askStatementOfTruth`, `submissionsRemovalOptions`, `confirmationEmailTemplate`, `sendConfirmationEmail`, `displayMainWebsiteLink`, `includeConfirmationPageContentInPdf`, `translations`, `brpPersonenRequestOptions`. properties: uuid: type: string @@ -9162,12 +9146,6 @@ components: type: array items: $ref: '#/components/schemas/FormRegistrationBackend' - registrationBackend: - type: string - deprecated: true - registrationBackendOptions: - nullable: true - deprecated: true authenticationBackends: type: array items: diff --git a/src/openforms/fixtures/demo.json b/src/openforms/fixtures/demo.json index 3ff026dbf9..4d6e6ed41e 100644 --- a/src/openforms/fixtures/demo.json +++ b/src/openforms/fixtures/demo.json @@ -7,8 +7,7 @@ "name": "\u2615\ufe0f", "slug": "koffie", "active": true, - "product": null, - "registration_backend": "openforms.core.backends.console_backend" + "product": null } }, { @@ -19,8 +18,18 @@ "name": "Demo multiple steps", "slug": "demo-multiple-steps", "active": true, - "product": null, - "registration_backend": "" + "product": null + } +}, +{ + "model": "forms.form_formregistrationbackend", + "pk": 1, + "fields": { + "key": "backend", + "name": "backend", + "backend": "openforms.core.backends.console_backend", + "options": {}, + "form": 1 } }, { diff --git a/src/openforms/forms/api/serializers/form.py b/src/openforms/forms/api/serializers/form.py index 07c938a3b1..cbaa1b4bc8 100644 --- a/src/openforms/forms/api/serializers/form.py +++ b/src/openforms/forms/api/serializers/form.py @@ -1,5 +1,3 @@ -import warnings - from django.db import transaction from django.utils.translation import gettext_lazy as _ @@ -100,8 +98,6 @@ def validate(self, attrs): @extend_schema_serializer( deprecate_fields=[ - "registration_backend", - "registration_backend_options", "cosign_login_info", ] ) @@ -224,12 +220,6 @@ class FormSerializer(PublicFieldsSerializerMixin, serializers.ModelSerializer): registration_backends = FormRegistrationBackendSerializer(many=True, required=False) - # deprecated fields - registration_backend = serializers.CharField(required=False, allow_blank=True) - registration_backend_options = serializers.JSONField( - required=False, allow_null=True - ) - class Meta: model = Form fields = ( @@ -239,8 +229,6 @@ class Meta: "login_required", "translation_enabled", "registration_backends", - "registration_backend", - "registration_backend_options", "authentication_backends", "authentication_backend_options", "login_options", @@ -342,26 +330,6 @@ def create(self, validated_data): ) registration_backends = validated_data.pop("registration_backends", []) - registration_backend_options = ( - validated_data.pop("registration_backend_options", None) or {} - ) - if "registration_backend" in validated_data: - warnings.warn( - ( - "Form 'registration_backend' and 'registration_backend_options' " - "are deprecated, use 'registration_backends' instead." - ), - DeprecationWarning, - ) - registration_backends.append( - { - "key": "default", - "name": _("Default"), - "backend": validated_data.pop("registration_backend"), - "options": registration_backend_options, - } - ) - instance = super().create(validated_data) ConfirmationEmailTemplate.objects.set_for_form( form=instance, data=confirmation_email_template @@ -376,33 +344,6 @@ def create(self, validated_data): ) return instance - def _update_v2_registration_backend(self, form, validated_data): - new_data = {} - if "registration_backend" in validated_data: - new_data["backend"] = validated_data.pop("registration_backend") - if "registration_backend_options" in validated_data: - new_data["options"] = validated_data.pop("registration_backend_options") - if not any(v is not None for v in new_data.values()): - return - - warnings.warn( - ( - "Form 'registration_backend' and 'registration_backend_options' " - "are deprecated, use 'registration_backends' instead." - ), - DeprecationWarning, - ) - - backend = form.registration_backends.first() or FormRegistrationBackend( - form=form, name=_("Default"), key="default" - ) - serializer = FormRegistrationBackendSerializer( - instance=backend, data=new_data, context=self.context, partial=self.partial - ) - # validated_data ought to be correct already - assert serializer.is_valid() - serializer.save() - @transaction.atomic() def update(self, instance, validated_data): confirmation_email_template = validated_data.pop( @@ -413,8 +354,6 @@ def update(self, instance, validated_data): ) registration_backends = validated_data.pop("registration_backends", None) - self._update_v2_registration_backend(instance, validated_data) - instance = super().update(instance, validated_data) ConfirmationEmailTemplate.objects.set_for_form( form=instance, data=confirmation_email_template @@ -446,43 +385,11 @@ def get_fields(self): ("", "") ] + payment_register.get_choices() - # adapt fields for v2 backwards compatibility - if "registration_backends" in fields: - v2_backend = fields["registration_backend"] - v3_backend = fields["registration_backends"].child.get_fields()["backend"] - v2_backend.choices = v3_backend.choices - return fields def validate(self, attrs): super().validate(attrs) - if "registration_backend" in attrs and "registration_backends" in attrs: - raise serializers.ValidationError( - { - "registration_backend": ErrorDetail( - _( - "registration_backend is deprecated, please use just registration_backends", - ), - code="invalid", - ), - "registration_backend_options": ErrorDetail( - _( - "registration_backend is deprecated, please use just registration_backends", - ), - code="invalid", - ), - } - ) - - if "registration_backends" not in attrs: - self.validate_backend_options( - attrs, - "registration_backend", - "registration_backend_options", - registration_register, - ) - self.validate_backend_options( attrs, "payment_backend", "payment_backend_options", payment_register ) @@ -509,7 +416,7 @@ def validate_backend_options(self, attrs, backend_field, options_field, registry except serializers.ValidationError as e: # wrap detail in dict so we can attach it to the field # DRF will create the .invalidParams with a dotted path to nested fields - # like registrationBackendOptions.toEmails.0 if the first email was invalid + # like registrationBackends.0.options.toEmails.0 if the first email was invalid detail = {options_field: e.detail} raise serializers.ValidationError(detail) from e # serializer does some normalization, so make sure to update the data @@ -623,10 +530,6 @@ def get_fields(self): del fields["payment_options"] if "authentication_backends" in fields: fields["authentication_backends"].write_only = False - if "registration_backend" in fields: - del fields["registration_backend"] - if "registration_backend_options" in fields: - del fields["registration_backend_options"] return fields diff --git a/src/openforms/forms/models/form.py b/src/openforms/forms/models/form.py index b08c994c73..fc795376c6 100644 --- a/src/openforms/forms/models/form.py +++ b/src/openforms/forms/models/form.py @@ -394,20 +394,6 @@ def get_absolute_url(self): def get_api_url(self): return reverse("api:form-detail", kwargs={"uuid": self.uuid}) - @property - def registration_backend(self) -> str | None: - "For backwards compatibility" - return ( - backend.backend if (backend := self.registration_backends.first()) else None - ) - - @property - def registration_backend_options(self) -> str | None: - "For backwards compatibility" - return ( - backend.options if (backend := self.registration_backends.first()) else None - ) - def get_registration_backend_display(self) -> str: return ( ", ".join( diff --git a/src/openforms/forms/tests/test_api_form_plugin_options.py b/src/openforms/forms/tests/test_api_form_plugin_options.py index 50407a7ed7..2f1d3324b2 100644 --- a/src/openforms/forms/tests/test_api_form_plugin_options.py +++ b/src/openforms/forms/tests/test_api_form_plugin_options.py @@ -173,10 +173,16 @@ def test_overwrite_only_registration_email_subject_templates(self): response = self.client.patch( url, data={ - "registration_backend_options": { - "to_emails": ["test@test.nl"], - "email_subject": "Custom subject", - } + "registration_backends": [ + { + "key": "fst", + "backend": "email", + "options": { + "to_emails": ["test@test.nl"], + "email_subject": "Custom subject", + }, + } + ] }, ) @@ -205,26 +211,33 @@ def test_overwrite_both_registration_email_html_and_text_templates(self): response = self.client.patch( url, data={ - "registration_backend_options": { - "to_emails": ["test@test.nl"], - "email_content_template_html": "Custom HTML template", - "email_content_template_text": "Custom text template", - } + "registration_backends": [ + { + "backend": "email", + "options": { + "to_emails": ["test@test.nl"], + "email_content_template_html": "Custom HTML template", + "email_content_template_text": "Custom text template", + }, + } + ] }, ) self.assertEqual(response.status_code, 200) data = response.json() + self.assertEqual(len(data["registrationBackends"]), 1) + backend = data["registrationBackends"][0] - self.assertNotIn("emailSubject", data["registrationBackendOptions"]) - self.assertNotIn("paymentSubject", data["registrationBackendOptions"]) + self.assertNotIn("emailSubject", backend["options"]) + self.assertNotIn("paymentSubject", backend["options"]) self.assertEqual( - data["registrationBackendOptions"]["emailContentTemplateHtml"], + backend["options"]["emailContentTemplateHtml"], "Custom HTML template", ) self.assertEqual( - data["registrationBackendOptions"]["emailContentTemplateText"], + backend["options"]["emailContentTemplateText"], "Custom text template", ) @@ -242,10 +255,15 @@ def test_cannot_overwrite_only_registration_email_html_template(self): response = self.client.patch( url, data={ - "registration_backend_options": { - "to_emails": ["test@test.nl"], - "email_content_template_html": "Custom HTML template", - }, + "registration_backends": [ + { + "backend": "email", + "options": { + "to_emails": ["test@test.nl"], + "email_content_template_html": "Custom HTML template", + }, + } + ], }, ) diff --git a/src/openforms/forms/tests/test_copy.py b/src/openforms/forms/tests/test_copy.py index 3f5be01f15..07a5323112 100644 --- a/src/openforms/forms/tests/test_copy.py +++ b/src/openforms/forms/tests/test_copy.py @@ -33,7 +33,8 @@ def test_form_copy_with_reusable_definition(self): self.assertNotEqual(copied_form.pk, form.pk) self.assertNotEqual(copied_form.uuid, str(form.uuid)) self.assertEqual(copied_form.active, form.active) - self.assertEqual(copied_form.registration_backend, form.registration_backend) + self.assertEqual(form.registration_backends.count(), 0) + self.assertEqual(copied_form.registration_backends.count(), 0) self.assertEqual(copied_form.name, _("{name} (copy)").format(name=form.name)) self.assertIsNone(copied_form.product) self.assertEqual(copied_form.slug, _("{slug}-copy").format(slug=form.slug)) @@ -141,12 +142,19 @@ def test_copy_form_with_registration_backends(self): form_copy = form.copy() forms = Form.objects.all() + # The backend_registration id and form are expected to be different, + # compare only the values that should be the same + backend_registrations = list( + form.registration_backends.values("key", "name", "backend", "options") + ) + copied_backend_registrations = list( + form_copy.registration_backends.values("key", "name", "backend", "options") + ) self.assertEqual(2, forms.count()) - registration_backends_copy = form_copy.registration_backends.all() - - self.assertEqual(registration_backends_copy.count(), 2) + self.assertEqual(form_copy.registration_backends.count(), 2) + self.assertQuerysetEqual(backend_registrations, copied_backend_registrations) def test_copy_form_with_logic_rules_has_correct_formstep_uuid_in_actions(self): form = FormFactory.create() diff --git a/src/openforms/forms/tests/test_import_export.py b/src/openforms/forms/tests/test_import_export.py index 6940f67dee..dd5b178026 100644 --- a/src/openforms/forms/tests/test_import_export.py +++ b/src/openforms/forms/tests/test_import_export.py @@ -277,15 +277,27 @@ def test_import(self): forms = Form.objects.all() imported_form = forms.last() + + # The backend_registration id and form are expected to be different, + # compare only the values that should be the same + backend_registrations = list( + form.registration_backends.values("key", "name", "backend", "options") + ) + imported_backend_registrations = list( + imported_form.registration_backends.values( + "key", "name", "backend", "options" + ) + ) + self.assertEqual(forms.count(), 2) self.assertNotEqual(imported_form.pk, form_pk) self.assertNotEqual(imported_form.uuid, str(form.uuid)) self.assertEqual(imported_form.active, False) - self.assertEqual(imported_form.registration_backend, form.registration_backend) self.assertEqual( - imported_form.registration_backend_options, - form.registration_backend_options, + imported_form.registration_backends.count(), + form.registration_backends.count(), ) + self.assertQuerysetEqual(backend_registrations, imported_backend_registrations) self.assertEqual(imported_form.name, form.name) self.assertIsNone(imported_form.product) self.assertEqual(imported_form.slug, old_form_slug) @@ -406,11 +418,27 @@ def test_import_form_definition_uuid_already_exists_configuration_duplicate(self forms = Form.objects.all() imported_form = forms.last() + + # The backend_registration id and form are expected to be different, + # compare only the values that should be the same + backend_registrations = list( + form.registration_backends.values("key", "name", "backend", "options") + ) + imported_backend_registrations = list( + imported_form.registration_backends.values( + "key", "name", "backend", "options" + ) + ) + self.assertEqual(forms.count(), 2) self.assertNotEqual(imported_form.pk, form_pk) self.assertNotEqual(imported_form.uuid, form.uuid) self.assertEqual(imported_form.active, False) - self.assertEqual(imported_form.registration_backend, form.registration_backend) + self.assertEqual( + imported_form.registration_backends.count(), + form.registration_backends.count(), + ) + self.assertQuerysetEqual(backend_registrations, imported_backend_registrations) self.assertEqual(imported_form.name, form.name) self.assertEqual(imported_form.internal_name, form.internal_name) self.assertIsNone(imported_form.product) @@ -473,11 +501,26 @@ def test_import_form_definition_uuid_already_exists_configuration_different(self forms = Form.objects.all() imported_form = forms.last() + # The backend_registration id and form are expected to be different, + # compare only the values that should be the same + backend_registrations = list( + form.registration_backends.values("key", "name", "backend", "options") + ) + imported_backend_registrations = list( + imported_form.registration_backends.values( + "key", "name", "backend", "options" + ) + ) + self.assertEqual(forms.count(), 2) self.assertNotEqual(imported_form.pk, form_pk) self.assertNotEqual(imported_form.uuid, form.uuid) self.assertEqual(imported_form.active, False) - self.assertEqual(imported_form.registration_backend, form.registration_backend) + self.assertEqual( + imported_form.registration_backends.count(), + form.registration_backends.count(), + ) + self.assertEqual(imported_backend_registrations, backend_registrations) self.assertEqual(imported_form.name, form.name) self.assertEqual(imported_form.internal_name, form.internal_name) self.assertIsNone(imported_form.product) diff --git a/src/openforms/forms/tests/test_serializers.py b/src/openforms/forms/tests/test_serializers.py index c7dbc81741..d77f864a4b 100644 --- a/src/openforms/forms/tests/test_serializers.py +++ b/src/openforms/forms/tests/test_serializers.py @@ -19,8 +19,6 @@ FormStepFactory, FormVariableFactory, ) -from openforms.registrations.base import BasePlugin as BaseRegistrationPlugin -from openforms.registrations.registry import Registry as RegistrationPluginRegistry from openforms.tests.search_strategies import json_primitives from openforms.variables.constants import FormVariableDataTypes, FormVariableSources @@ -270,9 +268,6 @@ def test_patching_registrations_deleting_the_first(self): ) context = {"request": None} data = FormSerializer(context=context).to_representation(form) - # remove v2 data - del data["registration_backend"] - del data["registration_backend_options"] # delete the first line assert data["registration_backends"][0]["key"] == "backend1" @@ -338,84 +333,3 @@ def test_patching_registrations_with_a_booboo(self): self.assertEqual(backend2.name, "#2") self.assertEqual(backend2.backend, "email") self.assertEqual(backend2.options["to_emails"], ["me@example.com"]) - - def test_patching_registration_passing_none_options(self): - # deprecated case - context = {"request": None} - data = FormSerializer(context=context).to_representation( - instance=FormFactory.create(slug="unicorn-slug") - ) - # not a v3 call - del data["registration_backends"] - # options v2 are nullable - data["registration_backend"] = "nullable-unicorn" - data["registration_backend_options"] = None - data["slug"] = "another-slug" - - mock_register = RegistrationPluginRegistry() - - @mock_register("nullable-unicorn") - class UnicornPlugin(BaseRegistrationPlugin): - # This doesn't pass registry.check_plugin - # configuration_options = None - - def register_submission(self, *args, **kwargs): - pass - - # this - # UnicornPlugin.configuration_options.allow_null = True - # still raises in FormSerializer.validate_backend_options: - # ValidationError({'non_field_errors': [ErrorDetail(string='No data provided', code='null')]}) - - # In theory, a 3rd party could do - UnicornPlugin.configuration_options = None - - with patch(f"{FormSerializer.__module__}.registration_register", mock_register): - serializer = FormSerializer(context=context, data=data) - self.assertTrue(serializer.is_valid()) - form = serializer.save() - - backends = list(form.registration_backends.all()) - self.assertEqual(len(backends), 1) - self.assertEqual(backends[0].backend, "nullable-unicorn") - self.assertFalse(backends[0].options) - - def test_patching_registrations_backend(self): - # testing v2 patching - form = FormFactory.create() - FormRegistrationBackendFactory.create(form=form, backend="demo-failing") - context = {"request": None} - - serializer = FormSerializer( - instance=form, - context=context, - data={"registration_backend": "demo"}, - partial=True, - ) - self.assertTrue(serializer.is_valid()) - serializer.save() - form.refresh_from_db() - - self.assertEqual(form.registration_backends.count(), 1) - backend = form.registration_backends.first() - self.assertEqual(backend.backend, "demo") - - def test_patching_registrations_backend_with_new_instance(self): - # testing v2 patching - form = FormFactory.create() - context = {"request": None} - - serializer = FormSerializer( - instance=form, - context=context, - data={"registration_backend": "demo", "registration_backend_options": {}}, - partial=True, - ) - - self.assertTrue(serializer.is_valid(raise_exception=True)) - serializer.save() - form.refresh_from_db() - - self.assertEqual(form.registration_backends.count(), 1) - backend = form.registration_backends.first() - self.assertEqual(backend.backend, "demo") diff --git a/src/openforms/registrations/contrib/camunda/tests/test_backend_options.py b/src/openforms/registrations/contrib/camunda/tests/test_backend_options.py index 2f81643065..55db5f6e26 100644 --- a/src/openforms/registrations/contrib/camunda/tests/test_backend_options.py +++ b/src/openforms/registrations/contrib/camunda/tests/test_backend_options.py @@ -55,9 +55,6 @@ def test_happy_flow(self): """ self.maxDiff = None detail_response = self.client.get(self.endpoint).json() - # remove v2 attributes - del detail_response["registrationBackend"] - del detail_response["registrationBackendOptions"] data = { **detail_response, @@ -253,26 +250,32 @@ def test_process_vars_incomplete_data(self): ] expected_errors = [ { - "registrationBackendOptions.processVariables.0.enabled": "required", - "registrationBackendOptions.processVariables.0.componentKey": "required", + "registrationBackends.0.options.processVariables.0.enabled": "required", + "registrationBackends.0.options.processVariables.0.componentKey": "required", }, { - "registrationBackendOptions.processVariables": "null", + "registrationBackends.0.options.processVariables": "null", }, { - "registrationBackendOptions.processVariables.0.componentKey": "blank", + "registrationBackends.0.options.processVariables.0.componentKey": "blank", }, ] for invalid_var, expected_error_codes in zip(invalid_vars, expected_errors): with self.subTest(invalid_var=invalid_var): data = { - "registrationBackendOptions": { - "processDefinition": "invoice", - "processDefinitionVersion": None, - "processVariables": [invalid_var], - "complexProcessVariables": [], - }, + "registrationBackends": [ + { + "backend": "camunda", + "key": "test1", + "options": { + "processDefinition": "invoice", + "processDefinitionVersion": None, + "processVariables": [invalid_var], + "complexProcessVariables": [], + }, + } + ] } response = self.client.patch(self.endpoint, data) @@ -295,37 +298,43 @@ def test_complex_process_vars_incomplete_data(self): ] expected_errors = [ { - "registrationBackendOptions.complexProcessVariables.0.enabled": "required", - "registrationBackendOptions.complexProcessVariables.0.alias": "required", - "registrationBackendOptions.complexProcessVariables.0.type": "required", + "registrationBackends.0.options.complexProcessVariables.0.enabled": "required", + "registrationBackends.0.options.complexProcessVariables.0.alias": "required", + "registrationBackends.0.options.complexProcessVariables.0.type": "required", }, { - "registrationBackendOptions.complexProcessVariables": "null", + "registrationBackends.0.options.complexProcessVariables": "null", }, { - "registrationBackendOptions.complexProcessVariables.0.alias": "blank", - "registrationBackendOptions.complexProcessVariables.0.type": "required", + "registrationBackends.0.options.complexProcessVariables.0.alias": "blank", + "registrationBackends.0.options.complexProcessVariables.0.type": "required", }, { - "registrationBackendOptions.complexProcessVariables.0.type": "invalid_choice", + "registrationBackends.0.options.complexProcessVariables.0.type": "invalid_choice", }, { - "registrationBackendOptions.complexProcessVariables.0.definition": "null", + "registrationBackends.0.options.complexProcessVariables.0.definition": "null", }, { - "registrationBackendOptions.complexProcessVariables.0.definition": "not_a_dict", + "registrationBackends.0.options.complexProcessVariables.0.definition": "not_a_dict", }, ] for invalid_var, expected_error_codes in zip(invalid_vars, expected_errors): with self.subTest(invalid_var=invalid_var): data = { - "registrationBackendOptions": { - "processDefinition": "invoice", - "processDefinitionVersion": None, - "processVariables": [], - "complexProcessVariables": [invalid_var], - }, + "registrationBackends": [ + { + "backend": "camunda", + "key": "test1", + "options": { + "processDefinition": "invoice", + "processDefinitionVersion": None, + "processVariables": [], + "complexProcessVariables": [invalid_var], + }, + } + ] } response = self.client.patch(self.endpoint, data) diff --git a/src/openforms/submissions/admin.py b/src/openforms/submissions/admin.py index 5f67dc9800..9a41a71a02 100644 --- a/src/openforms/submissions/admin.py +++ b/src/openforms/submissions/admin.py @@ -361,7 +361,7 @@ def successfully_processed(self, obj) -> bool | None: @admin.display(description=_("Registration backend")) def get_registration_backend(self, obj): - return obj.form.registration_backend or "-" + return obj.registration_backend or "-" @admin.display(description=_("Appointment status")) def get_appointment_status(self, obj): diff --git a/src/openforms/submissions/tests/test_admin.py b/src/openforms/submissions/tests/test_admin.py index 8d8bf98f53..9327a2dbf2 100644 --- a/src/openforms/submissions/tests/test_admin.py +++ b/src/openforms/submissions/tests/test_admin.py @@ -12,16 +12,24 @@ from maykin_2fa.test import disable_admin_mfa from openforms.accounts.tests.factories import UserFactory -from openforms.forms.tests.factories import FormVariableFactory +from openforms.forms.tests.factories import ( + FormFactory, + FormLogicFactory, + FormRegistrationBackendFactory, + FormStepFactory, + FormVariableFactory, +) from openforms.logging.logevent import submission_start from openforms.logging.models import TimelineLogProxy from openforms.submissions.models.submission import Submission from openforms.variables.constants import FormVariableDataTypes from ...config.models import GlobalConfiguration +from ...forms.constants import LogicActionTypes from ..admin import SubmissionAdmin, SubmissionTimeListFilter from ..constants import PostSubmissionEvents, RegistrationStatuses -from .factories import SubmissionFactory +from ..form_logic import evaluate_form_logic +from .factories import SubmissionFactory, SubmissionStepFactory @disable_admin_mfa() @@ -191,14 +199,18 @@ def test_change_view_displays_logs_if_not_avg(self): # add regular submission log submission_start(self.submission_1) - # viewing this generates an AVG log + # viewing this generates an AVG log, + # and a log for trying and failing to fetch a registration backend response = self.app.get( reverse( "admin:submissions_submission_change", args=(self.submission_1.pk,) ), user=self.user, ) - start_log, avg_log = TimelineLogProxy.objects.order_by("pk") + + start_log, avg_log, registration_backend_log = ( + TimelineLogProxy.objects.order_by("pk") + ) # regular log visible self.assertContains(response, start_log.get_message()) @@ -206,6 +218,9 @@ def test_change_view_displays_logs_if_not_avg(self): # avg log not visible self.assertNotContains(response, avg_log.get_message()) + # registration backend log not visible + self.assertNotContains(response, registration_backend_log.get_message()) + def test_search(self): list_url = furl(reverse("admin:submissions_submission_changelist")) list_url.args["q"] = "some value" @@ -248,6 +263,116 @@ def test_change_view_with_broken_price_variable_config(self): self.assertEqual(change_page.status_code, 200) + def test_changing_registration_backend_with_form_logic_is_correctly_displayed_in_admin( + self, + ): + form = FormFactory.create() + email = FormRegistrationBackendFactory.create( + form=form, + backend="email", + key="email", + name="Email", + ) + objects_api = FormRegistrationBackendFactory.create( + form=form, + backend="objects_api", + key="objects_api", + name="Objects api", + ) + form_step = FormStepFactory.create( + form=form, + form_definition__configuration={ + "components": [ + { + "type": "textfield", + "key": "text1", + } + ] + }, + ) + FormLogicFactory.create( + form=form, + json_logic_trigger={"==": [{"var": "text1"}, "trigger-rule"]}, + actions=[ + { + "action": { + "type": LogicActionTypes.set_registration_backend, + "value": f"{objects_api.key}", + }, + }, + ], + ) + + with self.subTest( + "Submission doesn't trigger logic and uses the default registration backend" + ): + # Without triggering the form logic, the submission should use + # the first backend registration of the form + submission = SubmissionFactory.create(form=form, completed=True) + submission_step = SubmissionStepFactory.create( + submission=submission, form_step=form_step, data={"text1": "test"} + ) + + # Evaluate the logic, and save the changes + evaluate_form_logic(submission, submission_step, submission.data) + submission.save() + + change_url = reverse( + "admin:submissions_submission_change", + kwargs={"object_id": submission.pk}, + ) + + change_page = self.app.get(change_url, user=self.user) + self.assertEqual(change_page.status_code, 200) + + # The email registration should be used + self.assertEqual(submission.registration_backend, email) + + # The admin page should show the email registration as the one being used + registration_backend_field = change_page.pyquery.find( + ".form-row.field-get_registration_backend > div > div" + ) + self.assertEqual( + registration_backend_field.text(), + f"Registratie backend:\nEmail van {form.name}", + ) + + with self.subTest( + "Submission that does trigger the logic and gets the logic defined backend registration" + ): + # When triggering the form logic, the submission should use + # the objects_api backend registration of the form + submission = SubmissionFactory.create(form=form, completed=True) + submission_step = SubmissionStepFactory.create( + submission=submission, + form_step=form_step, + data={"text1": "trigger-rule"}, + ) + + # Evaluate the logic, and save the changes + evaluate_form_logic(submission, submission_step, submission.data) + submission.save() + + change_url = reverse( + "admin:submissions_submission_change", + kwargs={"object_id": submission.pk}, + ) + + change_page = self.app.get(change_url, user=self.user) + self.assertEqual(change_page.status_code, 200) + + # The objects api registration should be used + self.assertEqual(submission.registration_backend, objects_api) + + # The admin page should show the objects api registration as the one being used + registration_backend_field = change_page.pyquery.find( + ".form-row.field-get_registration_backend > div > div" + ) + self.assertEqual( + registration_backend_field.text(), + f"Registratie backend:\nObjects api van {form.name}", + ) + class TestSubmissionTimeListFilterAdmin(TestCase): def test_time_filtering(self):