Skip to content

Commit

Permalink
Merge pull request #3932 from open-formulieren/issue/2827-crash-on-no…
Browse files Browse the repository at this point in the history
…w-in-logic

🐛 [#2827] Fix crash when assigning variable now to a field
  • Loading branch information
sergei-maertens authored Mar 19, 2024
2 parents 1bbaeed + 990ac50 commit 77602e1
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 10 deletions.
1 change: 1 addition & 0 deletions docs/manual/forms/basics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <manual_templates_formatting_of_variables>` 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.
Expand Down
10 changes: 7 additions & 3 deletions src/openforms/submissions/models/submission_value_variable.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,102 @@ 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,
},
)

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 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:
Expand Down
7 changes: 6 additions & 1 deletion src/openforms/variables/static_variables/static_variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
14 changes: 8 additions & 6 deletions src/openforms/variables/tests/test_static_variables.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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")
Expand All @@ -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):
Expand Down

0 comments on commit 77602e1

Please sign in to comment.