Skip to content

Commit

Permalink
🦺 [#72] Implement time component validations
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-maertens committed Mar 29, 2024
1 parent 87bcbd4 commit 4f27452
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 8 deletions.
66 changes: 60 additions & 6 deletions src/openforms/formio/components/vanilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,23 +147,77 @@ def build_serializer_field(
return serializers.ListField(child=base) if multiple else base


class FormioTimeField(serializers.TimeField):
def validate_empty_values(self, data):
is_empty, data = super().validate_empty_values(data)
# base field only treats `None` as empty, but formio uses empty strings
if data == "":
if self.required:
self.fail("required")

Check warning on line 156 in src/openforms/formio/components/vanilla.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/formio/components/vanilla.py#L156

Added line #L156 was not covered by tests
return (True, "")
return is_empty, data


class TimeBetweenValidator:

def __init__(self, min_time: time, max_time: time) -> None:
self.min_time = min_time
self.max_time = max_time

def __call__(self, value: time):
# same day - straight forward comparison
if self.min_time < self.max_time:
if value < self.min_time:
raise serializers.ValidationError(
_("Value is before minimum time"),
code="min_value",
)
if value > self.max_time:
raise serializers.ValidationError(
_("Value is after maximum time"),
code="max_value",
)

# min time is on the day before the max time applies (e.g. 20:00 -> 04:00)
else:
if value < self.min_time and value > self.max_time:
raise serializers.ValidationError(
_("Value is not between mininum and maximum time."), code="invalid"
)


@register("time")
class Time(BasePlugin[Component]):
formatter = TimeFormatter

def build_serializer_field(
self, component: Component
) -> serializers.TimeField | serializers.ListField:
) -> FormioTimeField | serializers.ListField:
multiple = component.get("multiple", False)
validate = component.get("validate", {})
required = validate.get("required", False)

validators = []
if min_time := validate.get("minTime"):
validators.append(MinValueValidator(time.fromisoformat(min_time)))
if max_time := validate.get("maxTime"):
validators.append(MaxValueValidator(time.fromisoformat(max_time)))
base = serializers.TimeField(

match (
min_time := validate.get("minTime"),
max_time := validate.get("maxTime"),
):
case (None, None):
pass
case (str(), None):
validators.append(MinValueValidator(time.fromisoformat(min_time)))

Check warning on line 209 in src/openforms/formio/components/vanilla.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/formio/components/vanilla.py#L209

Added line #L209 was not covered by tests
case (None, str()):
validators.append(MaxValueValidator(time.fromisoformat(max_time)))

Check warning on line 211 in src/openforms/formio/components/vanilla.py

View check run for this annotation

Codecov / codecov/patch

src/openforms/formio/components/vanilla.py#L211

Added line #L211 was not covered by tests
case (str(), str()):
validators.append(
TimeBetweenValidator(
time.fromisoformat(min_time),
time.fromisoformat(max_time),
)
)

base = FormioTimeField(
required=required,
allow_null=not required,
validators=validators,
Expand Down
51 changes: 49 additions & 2 deletions src/openforms/formio/tests/validation/test_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def test_min_max_time(self):
"label": "Foo",
"validate": {
"required": False,
"minTime": "10:00",
"maxTime": "12:00",
"minTime": "10:00", # type:ignore
"maxTime": "12:00", # type:ignore
},
}

Expand Down Expand Up @@ -73,3 +73,50 @@ def test_multiple(self):
self.assertIn(component["key"], errors)
error = extract_error(errors, component["key"])
self.assertEqual(error.code, "invalid")

def test_empty_default_value(self):
component: Component = {
"type": "time",
"key": "time",
"label": "Optional time",
"validate": {"required": False},
}

is_valid, _ = validate_formio_data(component, {"time": ""})

self.assertTrue(is_valid)

def test_min_max_time_different_days(self):
# Special validation logic - if both min and max time are provided and
# min is > max, then it is assumed they are swapped because they cross
# mignight
component: Component = {
"type": "time",
"key": "time",
"label": "Optional time",
"validate": {
"minTime": "20:00", # type:ignore
"maxTime": "04:00", # type:ignore
},
}

with self.subTest("valid - day 1"):
valid_value = "23:00"

is_valid, _ = validate_formio_data(component, {"time": valid_value})

self.assertTrue(is_valid)

with self.subTest("valid - day 2"):
valid_value = "04:00"

is_valid, _ = validate_formio_data(component, {"time": valid_value})

self.assertTrue(is_valid)

with self.subTest("invalid"):
invalid_value = "18:00"

is_valid, _ = validate_formio_data(component, {"time": invalid_value})

self.assertFalse(is_valid)

0 comments on commit 4f27452

Please sign in to comment.