Skip to content

Commit

Permalink
Improve the datetimeComponent date parsing. Add component_class_map…
Browse files Browse the repository at this point in the history
…ping to Form instantiation.
  • Loading branch information
bobslee committed Oct 6, 2023
1 parent 7d05740 commit 7cd80cf
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 59 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 1.2.3

Improve the `datetimeComponent` to properly parse a date with a custom format, when the `enableTime` (new property) is False.

Provide the `component_class_mapping` (interface) in the keyword arguments of the Form (class) instantiation.

## 1.2.2

Refactored the `Component` class `conditionally_visible` method, to call the following 2 methods which can be extended in component subclasses:
Expand Down
140 changes: 84 additions & 56 deletions formiodata/components/datetime.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
# Copyright Nova Code (http://www.novacode.nl)
# See LICENSE file for full licensing details.

from copy import copy
from datetime import datetime

from .component import Component


class datetimeComponent(Component):

@property
def enableTime(self):
return self.raw.get('enableTime')

def _format_mappings(self):
"""
Dictionary of mappings between Formio Datetime component
(key) to Python format (value).
Formio uses the format codes referenced in:
Formio uses the (JS uibDateParser) format codes referenced in:
https://github.com/angular-ui/bootstrap/tree/master/src/dateparser/docs#uibdateparsers-format-codes
"""
return {
Expand Down Expand Up @@ -64,61 +69,84 @@ def value(self, value):
return value

component = self.component_owner.input_components.get(self.key)
dt = self._fromisoformat(value)
py_dt_format = formio_dt_format = component.raw.get('format')
mapping = self._format_mappings()

# year
done = False
for formio, py in mapping['year'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# month
done = False
for formio, py in mapping['month'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# day
done = False
for formio, py in mapping['day'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# hour
done = False
for formio, py in mapping['hour'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# minute
done = False
for formio, py in mapping['minute'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# second
done = False
for formio, py in mapping['second'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# 12 hours AM/PM
done = False
for formio, py in mapping['am_pm'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

val = dt.strftime(py_dt_format)
super(self.__class__, self.__class__).value.fset(self, val)

# if not component.raw.get('enableTime'):
if not self.enableTime:
# OMG some parsing to deal with the ISO format (storage).
try:
datetime.fromisoformat(value)
super(self.__class__, self.__class__).value.fset(self, value)
except ValueError:
dt_format = self.raw.get('format')
py_format = copy(dt_format)
for date_part, mapping in self._format_mappings().items():
done_date_part = False
for fm_formio, fm_py in mapping.items():
# fm_formio are (JS) uibDateParser codes, see comment
# in _format_mappings
if not done_date_part and fm_formio in dt_format:
py_format = py_format.replace(fm_formio, fm_py)
done_date_part = True
py_dt = datetime.strptime(value, py_format)
val = datetime.fromisoformat(py_dt)
super(self.__class__, self.__class__).value.fset(self, val)
return
else:
dt = self._fromisoformat(value)
py_dt_format = formio_dt_format = component.raw.get('format')
mapping = self._format_mappings()

# year
done = False
for formio, py in mapping['year'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# month
done = False
for formio, py in mapping['month'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# day
done = False
for formio, py in mapping['day'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# hour
done = False
for formio, py in mapping['hour'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# minute
done = False
for formio, py in mapping['minute'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# second
done = False
for formio, py in mapping['second'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

# 12 hours AM/PM
done = False
for formio, py in mapping['am_pm'].items():
if not done and formio in formio_dt_format:
py_dt_format = py_dt_format.replace(formio, py)
done = True

val = dt.strftime(py_dt_format)
super(self.__class__, self.__class__).value.fset(self, val)

def to_datetime(self):
if not self.raw_value:
Expand Down
15 changes: 13 additions & 2 deletions formiodata/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@
class Form:

def __init__(
self, form_json, builder=None, builder_schema_json=None, lang="en", **kwargs
self,
form_json,
builder=None,
builder_schema_json=None,
lang="en",
component_class_mapping={},
**kwargs
):
"""
@param form_json
Expand All @@ -29,6 +35,7 @@ def __init__(
self.builder = builder
self.builder_schema_json = builder_schema_json
self.lang = lang
self.component_class_mapping = component_class_mapping

if self.builder and self.builder_schema_json:
raise Exception("Constructor accepts either builder or builder_schema_json.")
Expand Down Expand Up @@ -56,7 +63,11 @@ def __init__(
self._input = self._data = FormInput(self)

def set_builder_by_builder_schema_json(self):
self.builder = Builder(self.builder_schema_json, self.lang)
self.builder = Builder(
self.builder_schema_json,
language=self.lang,
component_class_mapping=self.component_class_mapping
)

def load_components(self):
for key, component in self.builder.components.items():
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "formio-data"
version = "1.2.2"
version = "1.2.3"
homepage = "https://github.com/novacode-nl/python-formio-data"
description = "formio.js JSON-data API"
readme = "README.md"
Expand Down

0 comments on commit 7cd80cf

Please sign in to comment.