diff --git a/src/openforms/forms/api/serializers/form_step.py b/src/openforms/forms/api/serializers/form_step.py index 8fbf625c49..71bb819069 100644 --- a/src/openforms/forms/api/serializers/form_step.py +++ b/src/openforms/forms/api/serializers/form_step.py @@ -1,3 +1,6 @@ +from functools import partial + +from django.db import transaction from django.db.models import Q from rest_framework import serializers @@ -10,6 +13,7 @@ ) from ...models import FormDefinition, FormStep +from ...tasks import on_formstep_save_event from ...validators import validate_no_duplicate_keys_across_steps from ..validators import FormStepIsApplicableIfFirstValidator from .button_text import ButtonTextSerializer @@ -170,3 +174,19 @@ def validate_form_definition(self, current_form_definition): ) return current_form_definition + + @transaction.atomic() + def save(self, **kwargs): + """ + Bandaid fix for #4824 + + Ensure that the FormVariables are in line with the state of the FormDefinitions + after saving + """ + instance = super().save(**kwargs) + + transaction.on_commit( + partial(on_formstep_save_event, instance.form.id, countdown=60) + ) + + return instance diff --git a/src/openforms/forms/tasks.py b/src/openforms/forms/tasks.py index c8c21203dd..8f8d382c8a 100644 --- a/src/openforms/forms/tasks.py +++ b/src/openforms/forms/tasks.py @@ -34,6 +34,28 @@ def on_variables_bulk_update_event(form_id: int) -> None: actions_chain.delay() +def on_formstep_save_event(form_id: int, countdown: int) -> None: + """ + Celery chain of tasks to execute after saving a FormStep. + """ + create_form_variables_for_components_task = create_form_variables_for_components.si( + form_id + ) + repopulate_reusable_definition_variables_task = ( + repopulate_reusable_definition_variables_to_form_variables.si(form_id) + ) + recouple_submission_variables_task = ( + recouple_submission_variables_to_form_variables.si(form_id) + ) + + actions_chain = chain( + create_form_variables_for_components_task, + repopulate_reusable_definition_variables_task, + recouple_submission_variables_task, + ) + actions_chain.apply_async(countdown=countdown) + + @app.task(ignore_result=True) def recouple_submission_variables_to_form_variables(form_id: int) -> None: """Recouple SubmissionValueVariable to FormVariable @@ -96,6 +118,29 @@ def repopulate_reusable_definition_variables_to_form_variables(form_id: int) -> FormVariable.objects.create_for_form(form) +@app.task(ignore_result=True) +@transaction.atomic() +def create_form_variables_for_components(form_id: int) -> None: + """Create FormVariables for each component in the Form + + This task is scheduled after creating/updating a FormStep, to ensure that the saved + Form has the appropriate FormVariables, even if errors occur in the variables update + endpoint. This is done to avoid leaving the Form in a broken state. + + See also: https://github.com/open-formulieren/open-forms/issues/4824#issuecomment-2514913073 + """ + from .models import Form, FormVariable # due to circular import + + form = Form.objects.get(id=form_id) + + # delete the existing form variables, we will re-create them + FormVariable.objects.filter( + form=form_id, source=FormVariableSources.component + ).delete() + + FormVariable.objects.create_for_form(form) + + @app.task() def activate_forms(): """Activate all the forms that should be activated by the specific date and time."""