From 77ad33b53b32377a01dc4d97b3f7f96f8b585296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Tue, 9 Feb 2016 14:46:09 +0100 Subject: [PATCH] general: use datetime.date() in DateField (#9876) --- tests/test_form_pages.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++- tests/test_formdata.py | 3 ++- tests/test_sql.py | 2 +- tests/test_workflows.py | 2 +- wcs/fields.py | 10 +++++++- wcs/formdata.py | 10 +++++++- wcs/qommon/ezt.py | 6 +++++ wcs/qommon/misc.py | 2 ++ wcs/sql.py | 9 ++++--- 9 files changed, 98 insertions(+), 9 deletions(-) diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index af812b3..9ca3a1f 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -2048,7 +2048,7 @@ def test_form_date_field_submit(pub): assert formdef.data_class().count() == 1 data_id = formdef.data_class().select()[0].id data = formdef.data_class().get(data_id) - assert time.strftime('%Y-%m-%d', data.data['0']) == '2015-01-01' + assert data.data['0'].strftime('%Y-%m-%d') == '2015-01-01' # without filling the field resp = get_app(pub).get('/test/') @@ -2504,3 +2504,64 @@ def test_display_message(pub): assert 'message-to-submitter' in page.body assert 'message-to-nobody' not in page.body assert 'message-to-xxx-and-submitter' in page.body + +def test_formdata_dates_json(pub): + create_user(pub) + wf = Workflow(name='status') + st1 = wf.add_status('Status1', 'st1') + + display_form = FormWorkflowStatusItem() + display_form.id = '_x' + display_form.by = ['_submitter'] + display_form.varname = 'xxx' + display_form.formdef = WorkflowFormFieldsFormDef(item=display_form) + display_form.formdef.fields.append(fields.DateField(id='1', label='Date', + type='date', varname='yyy')) + st1.items.append(display_form) + display_form.parent = st1 + + display_message = DisplayMessageWorkflowStatusItem() + display_message.message = '''The form has been recorded. + [if-any form_var_ttt]the 1st day is [form_var_ttt_raw.tm_mday][end] + [if-any xxx_var_yyy]the 2nd day is [xxx_var_yyy_raw.tm_mday][end]''' + st1.items.append(display_message) + display_message.parent = st1 + + wf.store() + + formdef = create_formdef() + formdef.workflow_id = wf.id + formdef.fields = [fields.DateField(id='0', label='string', type='date', + required=False, varname='ttt')] + formdef.store() + formdef.data_class().wipe() + + resp = login(get_app(pub), username='foo', password='foo').get('/test/') + resp.form['f0'] = '2013-11-17' + resp = resp.forms[0].submit('submit') + assert 'Check values then click submit.' in resp.body + resp = resp.forms[0].submit('submit') + assert resp.status_int == 302 + resp = resp.follow() + assert 'The form has been recorded' in resp.body + assert 'the 1st day is 17' in resp.body + + resp.forms[0]['f1'] = '2014-10-23' + resp = resp.forms[0].submit('submit') + resp = resp.follow() + assert 'the 1st day is 17' in resp.body + assert 'the 2nd day is 23' in resp.body + + assert formdef.data_class().count() == 1 + formdata = formdef.data_class().select()[0] + formdata_json_reload = json.loads(formdata.export_to_json()) + assert formdata_json_reload['workflow']['data']['xxx_var_yyy_raw'].startswith('2014-10-23') + + # test backward compatibility, when time.struct_time where used for dates + formdata.data['0'] = time.strptime('2013-11-17', '%Y-%m-%d') + formdata.workflow_data['xxx_var_yyy_rawdata'] = time.strptime('2014-10-23', '%Y-%m-%d') + formdata.store() + + resp = login(get_app(pub), username='foo', password='foo').get(formdata.get_url()) + assert 'the 1st day is 17' in resp.body + assert 'the 2nd day is 23' in resp.body diff --git a/tests/test_formdata.py b/tests/test_formdata.py index e21ca76..0720b63 100644 --- a/tests/test_formdata.py +++ b/tests/test_formdata.py @@ -1,3 +1,4 @@ +import datetime import pytest import sys import shutil @@ -208,7 +209,7 @@ def test_date_field(pub): formdata.store() formdata2 = formdata.get(formdata.id) - assert formdata2.data == {'0': value} + assert formdata2.data == {'0': datetime.date(2015, 5, 12)} assert formdata2.get_substitution_variables()['form_field_date'] == '2015-05-12' pub.cfg['language'] = {'language': 'fr'} diff --git a/tests/test_sql.py b/tests/test_sql.py index aa54e0a..3670e4c 100644 --- a/tests/test_sql.py +++ b/tests/test_sql.py @@ -170,7 +170,7 @@ def test_sql_field_item(): @postgresql def test_sql_field_date(): - check_sql_field('5', datetime.date.today().timetuple()) + check_sql_field('5', datetime.date.today()) @postgresql def test_sql_field_items(): diff --git a/tests/test_workflows.py b/tests/test_workflows.py index 8b7fd8e..426c67b 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -730,7 +730,7 @@ def test_display_form(two_pubs): assert formdata.get_substitution_variables()['xxx_var_date'] == '12/05/2015' assert formdata.get_substitution_variables()['xxx_var_date_raw'] == \ - time.strptime('2015-05-12', '%Y-%m-%d') + datetime.date(2015, 5, 12) two_pubs.cfg['language'] = {'language': 'en'} diff --git a/wcs/fields.py b/wcs/fields.py index 3746e7c..0fe227a 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program; if not, see . +import datetime import time import random import re @@ -140,6 +141,8 @@ class Field(object): anonymise = True stats = None + migrate_data = None + def __init__(self, **kwargs): for k, v in kwargs.items(): setattr(self, k.replace('-', '_'), v) @@ -878,8 +881,13 @@ class DateField(WidgetField): 'minimum_date', 'minimum_is_future', 'maximum_date', 'date_in_the_past', 'date_can_be_today'] + def migrate_data(self, value): + if isinstance(value, time.struct_time): + return datetime.date(value.tm_year, value.tm_mon, value.tm_mday) + return value + def convert_value_from_str(self, value): - return time.strptime(value, date_format()) + return datetime.datetime.strptime(value, date_format()).date() def convert_value_to_str(self, value): if value is None: diff --git a/wcs/formdata.py b/wcs/formdata.py index 96a8de2..0d0e36c 100644 --- a/wcs/formdata.py +++ b/wcs/formdata.py @@ -31,7 +31,7 @@ from qommon import ezt from qommon.substitution import Substitutions from roles import Role -from fields import FileField +from fields import FileField, DateField def get_dict_with_varnames(fields, data, formdata=None, varnames_only=False): @@ -201,6 +201,14 @@ class FormData(StorableObject): if evo.status and not evo.status.startswith('wf-'): evo.status = 'wf-%s' % evo.status changed = True + if self._formdef and self._formdef.fields: + for field in self._formdef.fields: + if field.migrate_data: + current_value = self.data.get(field.id) + new_value = field.migrate_data(current_value) + if current_value != new_value: + self.data[field.id] = new_value + changed = True if changed: self.store() diff --git a/wcs/qommon/ezt.py b/wcs/qommon/ezt.py index 38b08d3..396b283 100644 --- a/wcs/qommon/ezt.py +++ b/wcs/qommon/ezt.py @@ -223,6 +223,7 @@ Directives # http://svn.webdav.org/repos/projects/ezt/trunk/ # +import datetime import string import re from types import StringType, IntType, FloatType, LongType @@ -629,6 +630,11 @@ def _get_value((refname, start, rest), ctx): # walk the rest of the dotted reference for attr in rest: + if isinstance(ob, datetime.date) or isinstance(ob, datetime.datetime): + # backward compatibility support from datetime to time.struct_time + attr = {'tm_year': 'year', 'tm_mon': 'month', 'tm_mday': 'day', + 'tm_hour': 'hour', 'tm_min': 'minute', 'tm_sec': 'second', + }.get(attr, attr) try: ob = getattr(ob, attr) except AttributeError: diff --git a/wcs/qommon/misc.py b/wcs/qommon/misc.py index 0776d63..ac64b5e 100644 --- a/wcs/qommon/misc.py +++ b/wcs/qommon/misc.py @@ -400,6 +400,8 @@ def indent_xml(elem, level=0): class JSONEncoder(json.JSONEncoder): def default(self, obj): + if isinstance(obj, datetime.date): + return obj.strftime('%Y-%m-%d') if isinstance(obj, time.struct_time): return datetime.datetime.utcfromtimestamp(time.mktime(obj)).isoformat() + 'Z' # Let the base class default method raise the TypeError diff --git a/wcs/sql.py b/wcs/sql.py index c6ce5a7..50aaa0b 100644 --- a/wcs/sql.py +++ b/wcs/sql.py @@ -873,8 +873,10 @@ class SqlMixin(object): elif sql_type == 'varchar': assert isinstance(value, basestring) elif sql_type == 'date': - assert type(value) is time.struct_time - value = datetime.datetime(value.tm_year, value.tm_mon, value.tm_mday) + if type(value) is time.struct_time: + value = datetime.datetime(value.tm_year, value.tm_mon, value.tm_mday) + elif type(value) not in (datetime.datetime, datetime.date): + assert 'invalid data type for date' elif sql_type == 'bytea': value = bytearray(cPickle.dumps(value)) elif sql_type == 'boolean': @@ -907,7 +909,8 @@ class SqlMixin(object): d[fmt] = unicode(val, 'utf-8') value = d if sql_type == 'date': - value = value.timetuple() + if isinstance(value, datetime.datetime): + value = value.date() elif sql_type == 'bytea': value = cPickle.loads(str(value)) obdata[field.id] = value -- 2.7.0