From 9338dff85f5c0534443a2b0064f1b5d6dea566cb Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Wed, 14 Feb 2024 12:27:36 +0100 Subject: [PATCH 1/5] :bug: [#2827] Fix crash when assigning variable now to a field Another one for the types big topic :) --- .../submissions/models/submission_value_variable.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/openforms/submissions/models/submission_value_variable.py b/src/openforms/submissions/models/submission_value_variable.py index 4ea91aaca9..748f839736 100644 --- a/src/openforms/submissions/models/submission_value_variable.py +++ b/src/openforms/submissions/models/submission_value_variable.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from datetime import datetime, time +from datetime import date, datetime, time from typing import TYPE_CHECKING, Any from django.core.serializers.json import DjangoJSONEncoder @@ -353,8 +353,10 @@ def to_python(self) -> Any: return self.value if self.value and data_type == FormVariableDataTypes.date: - date = format_date_value(self.value) - naive_date = parse_date(date) + if isinstance(self.value, date): + return self.value + formatted_date = format_date_value(self.value) + naive_date = parse_date(formatted_date) if naive_date is not None: aware_date = timezone.make_aware(datetime.combine(naive_date, time.min)) return aware_date.date() @@ -368,6 +370,8 @@ def to_python(self) -> Any: return timezone.make_aware(maybe_naive_datetime).date() if self.value and data_type == FormVariableDataTypes.datetime: + if isinstance(self.value, datetime): + return self.value maybe_naive_datetime = parse_datetime(self.value) if maybe_naive_datetime is None: return From fb39b918bc75f1b950e2a0439fa994365b6b4c99 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 18 Mar 2024 08:26:41 +0100 Subject: [PATCH 2/5] :test_tube: [#2827] Add regression test for setting static variable value --- .../tests/form_logic/test_submission_logic.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/openforms/submissions/tests/form_logic/test_submission_logic.py b/src/openforms/submissions/tests/form_logic/test_submission_logic.py index f44ca80f28..2e3d2817dd 100644 --- a/src/openforms/submissions/tests/form_logic/test_submission_logic.py +++ b/src/openforms/submissions/tests/form_logic/test_submission_logic.py @@ -907,6 +907,59 @@ def test_component_values_hidden_fieldset_used_in_subsequent_logic(self): data = response.json() self.assertEqual(data["step"]["data"], {}) + @tag("gh-2827") + def test_component_value_set_to_now(self): + """ + Assert that the 'now' variable can be assigned to a component. + """ + form = FormFactory.create( + generate_minimal_setup=True, + formstep__form_definition__configuration={ + "components": [ + { + "key": "datetime", + "type": "datetime", + "label": "Now", + }, + ] + }, + ) + FormLogicFactory.create( + form=form, + json_logic_trigger=True, + actions=[ + { + "variable": "datetime", + "action": { + "type": "variable", + "value": {"var": "now"}, + }, + } + ], + ) + submission = SubmissionFactory.create(form=form) + self._add_submission_to_session(submission) + logic_check_endpoint = reverse( + "api:submission-steps-logic-check", + kwargs={ + "submission_uuid": submission.uuid, + "step_uuid": form.formstep_set.get().uuid, + }, + ) + + response = self.client.post( + logic_check_endpoint, + { + "data": { + "radio": "show", + "textfield1": "foo", + "textfield2": "bar", + } + }, + ) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + def is_valid_expression(expr: dict): try: From 8170cf97de3778fcd49f3bfc06cbad2d9afb8dc6 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 18 Mar 2024 08:34:05 +0100 Subject: [PATCH 3/5] :white_check_mark: [#2827] Add test for expected (truncated) timestamp behaviour --- .../tests/form_logic/test_submission_logic.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/openforms/submissions/tests/form_logic/test_submission_logic.py b/src/openforms/submissions/tests/form_logic/test_submission_logic.py index 2e3d2817dd..8248198d6d 100644 --- a/src/openforms/submissions/tests/form_logic/test_submission_logic.py +++ b/src/openforms/submissions/tests/form_logic/test_submission_logic.py @@ -947,19 +947,18 @@ def test_component_value_set_to_now(self): }, ) - response = self.client.post( - logic_check_endpoint, - { - "data": { - "radio": "show", - "textfield1": "foo", - "textfield2": "bar", - } - }, - ) + with freeze_time("2024-03-18T08:31:08+01:00"): + response = self.client.post(logic_check_endpoint, {"data": {}}) self.assertEqual(response.status_code, status.HTTP_200_OK) + new_value = response.json()["step"]["data"]["datetime"] + # check that the seconds/ms are truncated to prevent infinite logic bouncing. + # Note that this doesn't make the problem go away 100% - you will get an + # additional check if the minute value changes, but that should settle after one + # extra logic check. + self.assertEqual(new_value, "2024-03-18T07:31:00Z") + def is_valid_expression(expr: dict): try: From ab2f54d282f68ff4b6544084b01a8c89fc50a9dc Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 18 Mar 2024 09:16:01 +0100 Subject: [PATCH 4/5] :adhesive_bandage: [#2827] Remove seconds/microsecond resolution from 'now' static variable This resolution is typically not important, as the frontend UI widgets also already truncate the seconds (bringing the time/datetime resolution to minutes). Ensuring that multiple logic check calls within the same minute yield the same value breaks the logic check/check schedule infinite loop and resolves the reported problem. A better fix would be to prevent these loops in the SDK in the first place, but for that we need the new renderer with a smarter way to do the logic checks. --- docs/manual/forms/basics.rst | 1 + .../variables/static_variables/static_variables.py | 7 ++++++- .../variables/tests/test_static_variables.py | 14 ++++++++------ 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/docs/manual/forms/basics.rst b/docs/manual/forms/basics.rst index a4788fd3ff..be225bfe40 100644 --- a/docs/manual/forms/basics.rst +++ b/docs/manual/forms/basics.rst @@ -294,6 +294,7 @@ waarde. Variabele Type Voorbeeld waarde Toelichting =============== ========= =========================== ==================================================================================================================== now datetime ``2022-09-09 18:29:00`` Datum van vandaag. Hier zijn :ref:`verschillende weergaven ` van mogelijk. + Seconden en milliseconden zijn altijd 0. environment string ``production`` De waarde die tijdens de installatie gezet is als ``ENVIRONMENT``. Zie: :ref:`installation_environment_config`. form_name string ``Paspoort aanvragen`` De naam van het formulier. form_id string ``1c453fc8-b10f-4510-``... Het unieke ID van het formulier. diff --git a/src/openforms/variables/static_variables/static_variables.py b/src/openforms/variables/static_variables/static_variables.py index 03af67f56a..ef5899f23d 100644 --- a/src/openforms/variables/static_variables/static_variables.py +++ b/src/openforms/variables/static_variables/static_variables.py @@ -15,7 +15,12 @@ class Now(BaseStaticVariable): data_type = FormVariableDataTypes.datetime def get_initial_value(self, submission: Submission | None = None): - return timezone.now() + # Issue #2827 - the frontend schedules a new logic check when data is changed, + # but the value of 'now' changes every time that it's called, so this leads to + # infinite logic checking. As a workaround, we truncate the value of seconds/ + # milliseconds to break this loop. + now = timezone.now() + return now.replace(second=0, microsecond=0) @register_static_variable("today") diff --git a/src/openforms/variables/tests/test_static_variables.py b/src/openforms/variables/tests/test_static_variables.py index aac551f11d..6081898597 100644 --- a/src/openforms/variables/tests/test_static_variables.py +++ b/src/openforms/variables/tests/test_static_variables.py @@ -1,7 +1,7 @@ +from datetime import datetime, timezone from uuid import UUID from django.test import TestCase, override_settings -from django.utils import timezone from freezegun import freeze_time @@ -14,19 +14,21 @@ def _get_variable(key: str, **kwargs): return register[key].get_static_variable(**kwargs) -@freeze_time("2022-08-29T17:10:00+02:00") +@freeze_time("2022-08-29T17:10:55+02:00") class NowTests(TestCase): def test_with_submission(self): submission = SubmissionFactory.build() variable = _get_variable("now", submission=submission) - self.assertEqual(variable.initial_value, timezone.now()) + expected = datetime(2022, 8, 29, 15, 10, 0).replace(tzinfo=timezone.utc) + self.assertEqual(variable.initial_value, expected) def test_without_submission(self): variable = _get_variable("now") - self.assertEqual(variable.initial_value, timezone.now()) + expected = datetime(2022, 8, 29, 15, 10, 0).replace(tzinfo=timezone.utc) + self.assertEqual(variable.initial_value, expected) @freeze_time("2022-08-29T17:10:00+02:00") @@ -36,12 +38,12 @@ def test_with_submission(self): variable = _get_variable("current_year", submission=submission) - self.assertEqual(variable.initial_value, timezone.now().year) + self.assertEqual(variable.initial_value, 2022) def test_without_submission(self): variable = _get_variable("current_year") - self.assertEqual(variable.initial_value, timezone.now().year) + self.assertEqual(variable.initial_value, 2022) class EnvironmentTests(TestCase): From 990ac505a43ba5f7ae821435db3955e727811332 Mon Sep 17 00:00:00 2001 From: Sergei Maertens Date: Mon, 18 Mar 2024 09:27:28 +0100 Subject: [PATCH 5/5] :white_check_mark: [#2828] Add test to ensure today can be assigned --- .../tests/form_logic/test_submission_logic.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/openforms/submissions/tests/form_logic/test_submission_logic.py b/src/openforms/submissions/tests/form_logic/test_submission_logic.py index 8248198d6d..39c5b0f402 100644 --- a/src/openforms/submissions/tests/form_logic/test_submission_logic.py +++ b/src/openforms/submissions/tests/form_logic/test_submission_logic.py @@ -959,6 +959,50 @@ def test_component_value_set_to_now(self): # extra logic check. self.assertEqual(new_value, "2024-03-18T07:31:00Z") + def test_component_value_set_to_today(self): + form = FormFactory.create( + generate_minimal_setup=True, + formstep__form_definition__configuration={ + "components": [ + { + "key": "date", + "type": "date", + "label": "Today", + }, + ] + }, + ) + FormLogicFactory.create( + form=form, + json_logic_trigger=True, + actions=[ + { + "variable": "date", + "action": { + "type": "variable", + "value": {"var": "today"}, + }, + } + ], + ) + submission = SubmissionFactory.create(form=form) + self._add_submission_to_session(submission) + logic_check_endpoint = reverse( + "api:submission-steps-logic-check", + kwargs={ + "submission_uuid": submission.uuid, + "step_uuid": form.formstep_set.get().uuid, + }, + ) + + with freeze_time("2024-03-18T08:31:08+01:00"): + response = self.client.post(logic_check_endpoint, {"data": {}}) + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + new_value = response.json()["step"]["data"]["date"] + self.assertEqual(new_value, "2024-03-18") + def is_valid_expression(expr: dict): try: