Skip to content

Commit

Permalink
Merge pull request #2383 from open-formulieren/fix/2251-date-issues-a…
Browse files Browse the repository at this point in the history
…ttempt2

[#2251] Fix date logic issue (attempt 2)
  • Loading branch information
sergei-maertens authored Nov 24, 2022
2 parents b1911f2 + dcda0a9 commit f3ce945
Show file tree
Hide file tree
Showing 20 changed files with 212 additions and 24 deletions.
2 changes: 1 addition & 1 deletion requirements/base.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ celery-once
defusedxml
furl
glom
git+https://github.com/maykinmedia/json-logic-py.git@f877f5326231bdcf4621783278f22c8565ec7913#egg=maykin-json-logic-py
git+https://github.com/maykinmedia/json-logic-py.git@160816204f8157e84a3442e4c02508fc1f7cc91d#egg=maykin-json-logic-py
html5lib
# see https://github.com/onelogin/python3-saml/issues/292 and
# https://bugs.launchpad.net/lxml/+bug/1960668 -> we can avoid this by compiling lxml
Expand Down
2 changes: 1 addition & 1 deletion requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ mail-parser==3.15.0
# via django-yubin
maykin-django-two-factor-auth[phonenumbers]==2.0.4
# via -r requirements/base.in
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@f877f5326231bdcf4621783278f22c8565ec7913
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@160816204f8157e84a3442e4c02508fc1f7cc91d
# via -r requirements/base.in
maykin-python3-saml==1.14.0.post0
# via django-digid-eherkenning
Expand Down
2 changes: 1 addition & 1 deletion requirements/ci.txt
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ maykin-django-two-factor-auth[phonenumbers]==2.0.4
# via
# -c requirements/base.txt
# -r requirements/base.txt
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@f877f5326231bdcf4621783278f22c8565ec7913
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@160816204f8157e84a3442e4c02508fc1f7cc91d
# via
# -c requirements/base.txt
# -r requirements/base.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ maykin-django-two-factor-auth[phonenumbers]==2.0.4
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@f877f5326231bdcf4621783278f22c8565ec7913
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@160816204f8157e84a3442e4c02508fc1f7cc91d
# via
# -c requirements/ci.txt
# -r requirements/ci.txt
Expand Down
2 changes: 1 addition & 1 deletion requirements/extensions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,7 @@ maykin-django-two-factor-auth[phonenumbers]==2.0.4
# via
# -c requirements/base.in
# -r requirements/base.txt
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@f877f5326231bdcf4621783278f22c8565ec7913
maykin-json-logic-py @ git+https://github.com/maykinmedia/json-logic-py.git@160816204f8157e84a3442e4c02508fc1f7cc91d
# via
# -c requirements/base.in
# -r requirements/base.txt
Expand Down
1 change: 1 addition & 0 deletions src/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5324,6 +5324,7 @@ components:
- float
- datetime
- time
- date
type: string
Date:
type: object
Expand Down
2 changes: 1 addition & 1 deletion src/openforms/formio/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
COMPONENT_DATATYPES = {
"date": "datetime",
"date": "date",
"time": "time",
"file": "array",
"currency": "float",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Generated by Django 3.2.16 on 2022-11-23 11:14

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("forms", "0056_merge_20221122_1513"),
]

operations = [
migrations.AlterField(
model_name="formvariable",
name="data_type",
field=models.CharField(
choices=[
("string", "String"),
("boolean", "Boolean"),
("object", "Object"),
("array", "Array"),
("int", "Integer"),
("float", "Float"),
("datetime", "Datetime"),
("time", "Time"),
("date", "Date"),
],
help_text="The type of the value that will be associated with this variable",
max_length=50,
verbose_name="data type",
),
),
]
14 changes: 13 additions & 1 deletion src/openforms/forms/tests/variables/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def test_user_defined_variables_cast_initial_value(self):
FormVariableDataTypes.float: [None, "", 1.4],
FormVariableDataTypes.datetime: [
"",
"Invalid date",
"Invalid datetime",
"2022-09-05",
"2022-09-08T00:00:00+02:00",
],
Expand All @@ -122,6 +122,12 @@ def test_user_defined_variables_cast_initial_value(self):
"11:30:00",
"2022-09-08T11:30:00+02:00",
],
FormVariableDataTypes.date: [
"",
"Invalid date",
"2022-09-05",
"2022-09-08T00:00:00+02:00",
],
}

expected_values = {
Expand Down Expand Up @@ -149,6 +155,12 @@ def test_user_defined_variables_cast_initial_value(self):
"11:30:00",
"2022-09-08T11:30:00+02:00",
],
FormVariableDataTypes.date: [
"",
"",
"2022-09-05",
"2022-09-08T00:00:00+02:00",
],
}

for data_type, data_type_label in FormVariableDataTypes.choices:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';

import {TextInput, NumberInput, DateInput} from 'components/admin/forms/Inputs';
import {TextInput, NumberInput, DateInput, DateTimeInput} from 'components/admin/forms/Inputs';
import Select from 'components/admin/forms/Select';
import ArrayInput from 'components/admin/forms/ArrayInput';
import JsonWidget from 'components/admin/forms/JsonWidget';
Expand Down Expand Up @@ -57,10 +57,48 @@ WrappedJsonWidget.propTypes = {
onChange: PropTypes.func,
};

const WrappedDatetimeInput = ({name, value, onChange}) => {
const getFormattedTimezone = offset => {
const hoursOffset = ('0' + Math.floor(offset / 60)).slice(-2);
const minutesOffset = ('0' + (offset % 60)).slice(-2);
return `${offset >= 0 ? '+' : '-'}${hoursOffset}:${minutesOffset}`;
};

const formatDatetime = selectedDatetime => {
// When converting to a ISOString, it is converted to UTC+00. We format it using the timezone of the server.
selectedDatetime.setMinutes(
selectedDatetime.getMinutes() - selectedDatetime.getTimezoneOffset()
);

// Use the timezone of the server
let iso_datetime = selectedDatetime.toISOString();
const serverOffset = document.body.dataset.adminUtcOffset / 60; // in minutes
return iso_datetime.replace('Z', getFormattedTimezone(serverOffset));
};

return (
<DateTimeInput
name={name}
value={value}
formatDatetime={formatDatetime}
onChange={onChange}
defaultDate={value || null}
enableTime={true}
/>
);
};

WrappedDatetimeInput.propTypes = {
name: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
};

const TYPE_TO_INPUT_TYPE = {
float: NumberInput,
string: TextInput,
datetime: DateInput,
datetime: WrappedDatetimeInput,
date: DateInput,
boolean: CheckboxChoices,
array: WrapperArrayInput,
object: WrappedJsonWidget,
Expand Down
23 changes: 19 additions & 4 deletions src/openforms/js/components/admin/form_design/logic/Trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ const parseJsonLogic = (logic, allVariablesKeys) => {
operand = compareValue.date.var ? compareValue.date.var : compareValue.date;
break;
}
case 'datetime': {
operandType = compareValue.datetime.var ? 'variable' : 'literal';
operand = compareValue.datetime.var ? compareValue.datetime.var : compareValue.datetime;
break;
}
case '+':
case '-': {
operandType = 'today';
Expand Down Expand Up @@ -190,10 +195,20 @@ const Trigger = ({name, logic, onChange, error, withDSLPreview = false, children
onChange={onTriggerChange}
/>
);
if (triggerVariable?.dataType === 'datetime') {
compareValue = {date: operand};
} else {
compareValue = operand;

const dataType = triggerVariable?.dataType;
switch (dataType) {
case 'date': {
compareValue = {date: operand};
break;
}
case 'datetime': {
compareValue = {datetime: operand};
break;
}
default: {
compareValue = operand;
}
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const TYPE_TO_OPERATORS = {
float: ['==', '!=', '>', '>=', '<', '<='],
string: ['==', '!=', 'in'],
datetime: ['==', '!=', '>', '>=', '<', '<='],
date: ['==', '!=', '>', '>=', '<', '<='],
_default: ['==', '!='],
};

Expand Down Expand Up @@ -92,7 +93,8 @@ const ACTIONS_WITH_OPTIONS = ['property'];
const TYPE_TO_OPERAND_TYPE = {
float: ['literal', 'variable'],
string: ['literal', 'variable', 'array'],
datetime: ['literal', 'variable', 'today'],
datetime: ['literal', 'variable'],
date: ['literal', 'variable', 'today'],
object: ['literal'],
_default: ['literal'],
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {defineMessage} from 'react-intl';

const COMPONENT_DATATYPES = {
date: 'datetime',
date: 'date',
time: 'time',
file: 'array',
currency: 'float',
Expand Down Expand Up @@ -63,6 +63,13 @@ const DATATYPES_CHOICES = [
defaultMessage: 'Datetime',
}),
],
[
'date',
defineMessage({
description: 'data type date',
defaultMessage: 'Date',
}),
],
[
'time',
defineMessage({
Expand Down
53 changes: 51 additions & 2 deletions src/openforms/js/components/admin/forms/Inputs.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, {useContext} from 'react';
import React, {useContext, useEffect, useRef} from 'react';
import PropTypes from 'prop-types';

import {PrefixContext} from './Context';
import {defineMessage, useIntl} from 'react-intl';

const Input = ({type = 'text', name, ...extraProps}) => {
const prefix = useContext(PrefixContext);
Expand All @@ -28,6 +29,54 @@ const NumberInput = props => <Input type="number" {...props} />;

const DateInput = props => <Input type="date" {...props} />;

const DateTimeInput = ({name, value, formatDatetime, onChange, ...extraProps}) => {
const datetimePickerRef = useRef();
const intl = useIntl();

const wrappedOnChange = (selectedDates, dateStr, instance) => {
let formattedDatetime = dateStr;
if (formatDatetime) formattedDatetime = formatDatetime(selectedDates[0]);

const fakeEvent = {
target: {name: name, value: formattedDatetime},
};
onChange(fakeEvent);
};

useEffect(() => {
const flatpickrInstance = flatpickr(datetimePickerRef.current, {
onChange: wrappedOnChange,
...extraProps,
});

return function cleanup() {
flatpickrInstance.destroy();
};
}, []);

const placeHolder =
extraProps.placeholder ??
intl.formatMessage(
defineMessage({
description: 'datetime input widget placeholder',
defaultMessage: 'Select date/time...',
})
);

return (
<div className="datetime-input">
<input ref={datetimePickerRef} placeholder={placeHolder} />
</div>
);
};

DateTimeInput.propTypes = {
name: PropTypes.string,
value: PropTypes.string,
formatDatetime: PropTypes.func,
onChange: PropTypes.func,
};

const Checkbox = ({name, label, helpText, ...extraProps}) => {
const prefix = useContext(PrefixContext);
name = prefix ? `${prefix}-${name}` : name;
Expand Down Expand Up @@ -68,4 +117,4 @@ Radio.propTypes = {
helpText: PropTypes.node,
};

export {Input, TextInput, TextArea, NumberInput, DateInput, Checkbox, Radio};
export {Input, TextInput, TextArea, NumberInput, DateInput, DateTimeInput, Checkbox, Radio};
14 changes: 11 additions & 3 deletions src/openforms/submissions/models/submission_value_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,22 +341,30 @@ def to_python(self) -> Any:
):
return self.value

if self.value and data_type == FormVariableDataTypes.datetime:
if self.value and data_type == FormVariableDataTypes.date:
date = format_date_value(self.value)
naive_date = parse_date(date)
if naive_date is not None:
aware_date = timezone.make_aware(datetime.combine(naive_date, time.min))
return aware_date

# not a date - try parsing a datetime then
maybe_naive_datetime = parse_datetime(self.value)
if maybe_naive_datetime is not None:
if timezone.is_aware(maybe_naive_datetime):
return maybe_naive_datetime.date()
return timezone.make_aware(maybe_naive_datetime).date()

raise ValueError(f"Could not parse date '{self.value}'") # pragma: nocover

if self.value and data_type == FormVariableDataTypes.datetime:
maybe_naive_datetime = parse_datetime(self.value)
if maybe_naive_datetime is not None:
if timezone.is_aware(maybe_naive_datetime):
return maybe_naive_datetime
return timezone.make_aware(maybe_naive_datetime)

raise ValueError(
f"Could not parse date/datetime '{self.value}'"
f"Could not parse datetime '{self.value}'"
) # pragma: nocover

if self.value and data_type == FormVariableDataTypes.time:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def test_dynamic_date_component_config_based_on_variables(self):
submission=submission,
key="userDefinedDate",
form_variable__user_defined=True,
form_variable__data_type=FormVariableDataTypes.datetime,
form_variable__data_type=FormVariableDataTypes.date,
value="2022-12-31",
)
endpoint = reverse(
Expand Down
Loading

0 comments on commit f3ce945

Please sign in to comment.