From 38293b37c1e6871676437d42fe319fd981fc76b0 Mon Sep 17 00:00:00 2001 From: Steven Bal Date: Mon, 9 Dec 2024 13:32:03 +0100 Subject: [PATCH] :bug: [#4824] Schedule task to create FormVariables after saving FormStep this ensures that the Form cannot be left in a broken state with regards to FormVariables, because every component will always have a FormVariable this is a bandaid fix, because ideally we would have a single endpoint and the FormStep wouldn't be updated if there are errors with variables --- src/openforms/forms/api/viewsets.py | 29 +++++++++++++++++++++++++++++ src/openforms/forms/tasks.py | 23 +++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/openforms/forms/api/viewsets.py b/src/openforms/forms/api/viewsets.py index e0a81e87e2..9d6013c420 100644 --- a/src/openforms/forms/api/viewsets.py +++ b/src/openforms/forms/api/viewsets.py @@ -21,6 +21,7 @@ from openforms.api.pagination import PageNumberPagination from openforms.api.serializers import ExceptionSerializer, ValidationErrorSerializer +from openforms.forms.tasks import create_form_variables_for_components from openforms.translations.utils import set_language_cookie from openforms.utils.patches.rest_framework_nested.viewsets import NestedViewSetMixin from openforms.utils.urls import is_admin_request @@ -102,6 +103,34 @@ def get_serializer_context(self): ) return context + @transaction.atomic() + def create(self, request, *args, **kwargs): + response = super().create(request, *args, **kwargs) + + form = get_object_or_404(Form, uuid=self.kwargs["form_uuid_or_slug"]) + transaction.on_commit( + lambda: create_form_variables_for_components.apply_async( + args=(form.id,), + countdown=60, + ) + ) + + return response + + @transaction.atomic() + def update(self, request, *args, **kwargs): + response = super().update(request, *args, **kwargs) + + form = get_object_or_404(Form, uuid=self.kwargs["form_uuid_or_slug"]) + transaction.on_commit( + lambda: create_form_variables_for_components.apply_async( + args=(form.id,), + countdown=60, + ) + ) + + return response + _FORMSTEP_ADMIN_FIELDS_MARKDOWN = get_admin_fields_markdown(FormStepSerializer) FormStepViewSet.__doc__ = inspect.getdoc(FormStepViewSet).format( diff --git a/src/openforms/forms/tasks.py b/src/openforms/forms/tasks.py index c8c21203dd..e5f9b17c01 100644 --- a/src/openforms/forms/tasks.py +++ b/src/openforms/forms/tasks.py @@ -96,6 +96,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."""