Skip to content

Commit

Permalink
♻️ [#1068] -- bring dynamic config mutations into main component/regi…
Browse files Browse the repository at this point in the history
…stry

This deletes the dynamic config-specific registry, while mostly keeping
the public API intact.
  • Loading branch information
sergei-maertens committed Nov 24, 2022
1 parent 2e0116b commit 9e312b0
Show file tree
Hide file tree
Showing 12 changed files with 147 additions and 190 deletions.
1 change: 0 additions & 1 deletion src/openforms/conf/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@
"openforms.config",
"openforms.emails",
"openforms.formio",
"openforms.formio.dynamic_config",
"openforms.formio.rendering",
"openforms.forms",
"openforms.multidomain",
Expand Down
26 changes: 25 additions & 1 deletion src/openforms/formio/components/custom.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import logging

from ..formatters.custom import MapFormatter
from openforms.typing import DataMapping
from openforms.utils.date import format_date_value

from ..dynamic_config.date import FormioDateComponent, mutate as mutate_date
from ..formatters.custom import DateFormatter, MapFormatter
from ..formatters.formio import TextFieldFormatter
from ..registry import BasePlugin, register
from ..typing import Component
Expand All @@ -9,6 +13,26 @@
logger = logging.getLogger(__name__)


@register("date")
class Date(BasePlugin):
formatter = DateFormatter

@staticmethod
def normalizer(component: FormioDateComponent, value: str) -> str:
return format_date_value(value)

def mutate_config_dynamically(
self, component: FormioDateComponent, data: DataMapping
) -> None:
"""
Implement the behaviour for our custom date component options.
In the JS, this component type inherits from Formio datetime component. See
``src/openforms/js/components/form/date.js`` for the various configurable options.
"""
mutate_date(component, data)


@register("map")
class Map(BasePlugin):
formatter = MapFormatter
Expand Down
13 changes: 0 additions & 13 deletions src/openforms/formio/components/vanilla.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,9 @@
Custom component types (defined by us or third parties) need to be organized in the
adjacent custom.py module.
"""
from openforms.utils.date import format_date_value

from ..formatters.formio import (
CheckboxFormatter,
CurrencyFormatter,
DateFormatter,
DefaultFormatter,
EmailFormatter,
FileFormatter,
Expand All @@ -25,7 +22,6 @@
TimeFormatter,
)
from ..registry import BasePlugin, register
from ..typing import Component


@register("default")
Expand All @@ -47,15 +43,6 @@ class Email(BasePlugin):
formatter = EmailFormatter


@register("date")
class Date(BasePlugin):
formatter = DateFormatter

@staticmethod
def normalizer(component: Component, value: str) -> str:
return format_date_value(value)


@register("time")
class Time(BasePlugin):
formatter = TimeFormatter
Expand Down
21 changes: 21 additions & 0 deletions src/openforms/formio/dynamic_config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,24 @@
This will eventually replace ``openforms.forms.custom_field_types``.
"""
from typing import Optional

from openforms.typing import DataMapping

from ..datastructures import FormioConfigurationWrapper
from ..registry import register

__all__ = ["apply_dynamic_configuration"]


def apply_dynamic_configuration(
configuration_wrapper: FormioConfigurationWrapper,
data: Optional[DataMapping] = None,
) -> FormioConfigurationWrapper:
"""
Loop over the formio configuration and mutate components in place.
"""
data = data or {} # normalize
for component in configuration_wrapper:
register.update_config(component, data=data)
return configuration_wrapper
9 changes: 0 additions & 9 deletions src/openforms/formio/dynamic_config/apps.py

This file was deleted.

172 changes: 79 additions & 93 deletions src/openforms/formio/dynamic_config/date.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
from typing import Literal, Optional, TypedDict, Union, cast

from django.utils import timezone
from django.utils.translation import gettext_lazy as _

from dateutil.relativedelta import relativedelta
from glom import assign, glom

from openforms.typing import DataMapping

from ..typing import Component
from .registry import BasePlugin, register

NOW_VARIABLE = "now"

Expand Down Expand Up @@ -45,99 +43,87 @@ class FormioDateComponent(Component):
datePicker: Optional[DatePickerConfig]


@register("date")
class DateComponent(BasePlugin):
"""
Implement behaviour for our custom date component
In the JS, this component type inherits from Formio datetime component. See
``src/openforms/js/components/form/date.js`` for the various configurable options.
"""

verbose_name = _("Date component")

@classmethod
def mutate(cls, component: FormioDateComponent, data: DataMapping) -> None:
for key in ("minDate", "maxDate"):
config = cast(
DateConstraintConfiguration,
glom(component, f"openForms.{key}", default=None),
)
if config is None:
continue

if (mode := config.get("mode")) is None:
continue

# formio has set the datePicker.{key} value
if mode == "fixedValue":
continue

config = cls.normalize_config(config)
value = cls.calculate_delta(config, data)
if value and isinstance(value, datetime):
value = value.isoformat()
assign(component, f"datePicker.{key}", value, missing=dict)

@staticmethod
def normalize_config(
config: DateConstraintConfiguration,
) -> DateConstraintConfiguration:
mode = config["mode"]
assert mode in ("future", "past", "relativeToVariable")
if mode == "relativeToVariable":
return config

# mode is now future or past -> convert that to a relative delta config
config["mode"] = "relativeToVariable"
config["variable"] = NOW_VARIABLE
include_today = cast(bool, glom(config, "includeToday", default=False))
config["operator"] = "add" if mode == "future" else "subtract"

delta = cast(
DateConstraintDelta,
{
"years": 0,
"months": 0,
"days": 0 if include_today else 1,
},
def mutate(component: FormioDateComponent, data: DataMapping) -> None:
for key in ("minDate", "maxDate"):
config = cast(
DateConstraintConfiguration,
glom(component, f"openForms.{key}", default=None),
)
config["delta"] = delta
return config
if config is None:
continue

@staticmethod
def calculate_delta(
config: DateConstraintConfiguration,
data: DataMapping,
) -> Optional[datetime]:
assert config["mode"] == "relativeToVariable"
if (mode := config.get("mode")) is None:
continue

base_value = cast(
Optional[Union[datetime, str]],
glom(data, config["variable"], default=None),
)
# can't do calculations on values that don't exist or are empty
if not base_value:
return None

# if it's not empty-ish, it's a datetime
base_value = cast(datetime, base_value)

assert (
base_value.tzinfo is not None
), "Expected the input variable to be timezone aware!"
base_date = timezone.localtime(value=base_value).date()

delta = relativedelta(
years=cast(int, glom(config, "delta.years", default=None) or 0),
months=cast(int, glom(config, "delta.months", default=None) or 0),
days=cast(int, glom(config, "delta.days", default=None) or 0),
)
# formio has set the datePicker.{key} value
if mode == "fixedValue":
continue

add_or_subtract = glom(config, "operator", default="add")
func = operator.add if add_or_subtract == "add" else operator.sub
value = func(base_date, delta)
config = normalize_config(config)
value = calculate_delta(config, data)
if value and isinstance(value, datetime):
value = value.isoformat()
assign(component, f"datePicker.{key}", value, missing=dict)


def normalize_config(
config: DateConstraintConfiguration,
) -> DateConstraintConfiguration:
mode = config["mode"]
assert mode in ("future", "past", "relativeToVariable")
if mode == "relativeToVariable":
return config

# convert to datetime at midnight for the date in the local timezone
naive_value = datetime.combine(value, time.min)
return timezone.make_aware(naive_value)
# mode is now future or past -> convert that to a relative delta config
config["mode"] = "relativeToVariable"
config["variable"] = NOW_VARIABLE
include_today = cast(bool, glom(config, "includeToday", default=False))
config["operator"] = "add" if mode == "future" else "subtract"

delta = cast(
DateConstraintDelta,
{
"years": 0,
"months": 0,
"days": 0 if include_today else 1,
},
)
config["delta"] = delta
return config


def calculate_delta(
config: DateConstraintConfiguration,
data: DataMapping,
) -> Optional[datetime]:
assert config["mode"] == "relativeToVariable"

base_value = cast(
Optional[Union[datetime, str]],
glom(data, config["variable"], default=None),
)
# can't do calculations on values that don't exist or are empty
if not base_value:
return None

# if it's not empty-ish, it's a datetime
base_value = cast(datetime, base_value)

assert (
base_value.tzinfo is not None
), "Expected the input variable to be timezone aware!"
base_date = timezone.localtime(value=base_value).date()

delta = relativedelta(
years=cast(int, glom(config, "delta.years", default=None) or 0),
months=cast(int, glom(config, "delta.months", default=None) or 0),
days=cast(int, glom(config, "delta.days", default=None) or 0),
)

add_or_subtract = glom(config, "operator", default="add")
func = operator.add if add_or_subtract == "add" else operator.sub
value = func(base_date, delta)

# convert to datetime at midnight for the date in the local timezone
naive_value = datetime.combine(value, time.min)
return timezone.make_aware(naive_value)
40 changes: 0 additions & 40 deletions src/openforms/formio/dynamic_config/registry.py

This file was deleted.

24 changes: 0 additions & 24 deletions src/openforms/formio/dynamic_config/service.py

This file was deleted.

8 changes: 8 additions & 0 deletions src/openforms/formio/formatters/custom.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
# TODO implement: iban, bsn, postcode, licenseplate, npFamilyMembers, cosign
from django.template.defaultfilters import date as fmt_date
from django.utils.dateparse import parse_date

from ..typing import Component
from .base import FormatterBase


class DateFormatter(FormatterBase):
def format(self, component: Component, value: str) -> str:
return fmt_date(parse_date(value))


class MapFormatter(FormatterBase):
def format(self, component: Component, value: list[float]) -> str:
# use a comma here since its a single data element
Expand Down
Loading

0 comments on commit 9e312b0

Please sign in to comment.