Merge lp:~openerp-dev/openerp-web/7.0-today-filter-mat into lp:openerp-web/7.0

Proposed by Martin Trigaux (OpenERP)
Status: Needs review
Proposed branch: lp:~openerp-dev/openerp-web/7.0-today-filter-mat
Merge into: lp:openerp-web/7.0
Diff against target: 224 lines (+110/-8)
4 files modified
addons/web/static/src/js/corelib.js (+22/-2)
addons/web/static/src/js/data.js (+8/-2)
addons/web/static/src/js/pyeval.js (+45/-4)
addons/web/static/test/evals.js (+35/-0)
To merge this branch: bzr merge lp:~openerp-dev/openerp-web/7.0-today-filter-mat
Reviewer Review Type Date Requested Status
OpenERP Core Team Pending
Review via email: mp+203589@code.launchpad.net

Description of the change

backport of trunk revision 3919, not to be merged in 7.0

[MERGE] [ADD] ways to correctly handle local datetimes in e.g. domains

* Alter datetime.now(), generate a local datetime (add utcnow() which generates a UTC datetime)
* Implement datetime.replace() to manipulate local datetimes
* Implement date.today(), generates a local date
* Implement datetime.toJSON(), returns a javascript Date (assumes datetime attributes are local)
* Add conversion hook in JSON and JSONP handlers, automatically converts a Date object to a UTC datetime formatted according to server formats

Should allow the generation of correctly working (from the end-user's POV) [Today] filters, amongst other things.
Eg: a local expression in a domain for 'Today 00:00:00' would now be expressed as 'datetime.datetime.now().replace(hour=0, minute=0, second=0)' (no .strftime) and will be converted to UTC when sent to the server

To post a comment you must log in.
4129. By Martin Trigaux (OpenERP)

[ADD] backport part of trunk rev 3918.3923: allow to use filtres defined with field:day

4130. By Martin Trigaux (OpenERP)

[fix] grouping_field can be undefined

4131. By Martin Trigaux (OpenERP)

[MERGE] sync with 7.0 up to rev 4159

Unmerged revisions

4131. By Martin Trigaux (OpenERP)

[MERGE] sync with 7.0 up to rev 4159

4130. By Martin Trigaux (OpenERP)

[fix] grouping_field can be undefined

4129. By Martin Trigaux (OpenERP)

[ADD] backport part of trunk rev 3918.3923: allow to use filtres defined with field:day

4128. By Martin Trigaux (OpenERP)

[ADD] filters: backport of trunk rev 3919

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'addons/web/static/src/js/corelib.js'
--- addons/web/static/src/js/corelib.js 2013-03-01 10:22:23 +0000
+++ addons/web/static/src/js/corelib.js 2014-03-13 15:59:47 +0000
@@ -950,6 +950,26 @@
950 }950 }
951});951});
952952
953/**
954 * Replacer function for JSON.stringify, serializes Date objects to UTC
955 * datetime in the OpenERP Server format.
956 *
957 * However, if a serialized value has a toJSON method that method is called
958 * *before* the replacer is invoked. Date#toJSON exists, and thus the value
959 * passed to the replacer is a string, the original Date has to be fetched
960 * on the parent object (which is provided as the replacer's context).
961 *
962 * @param {String} k
963 * @param {Object} v
964 * @returns {Object}
965 */
966function date_to_utc(k, v) {
967 var value = this[k];
968 if (!(value instanceof Date)) { return v; }
969
970 return instance.web.datetime_to_str(value);
971}
972
953instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {973instance.web.JsonRPC = instance.web.Class.extend(instance.web.PropertiesMixin, {
954 triggers: {974 triggers: {
955 'request': 'Request sent',975 'request': 'Request sent',
@@ -1055,7 +1075,7 @@
1055 type: "POST",1075 type: "POST",
1056 dataType: 'json',1076 dataType: 'json',
1057 contentType: 'application/json',1077 contentType: 'application/json',
1058 data: JSON.stringify(payload),1078 data: JSON.stringify(payload, date_to_utc),
1059 processData: false1079 processData: false
1060 }, url);1080 }, url);
1061 if (this.synch)1081 if (this.synch)
@@ -1088,7 +1108,7 @@
1088 }, url);1108 }, url);
1089 if (this.synch)1109 if (this.synch)
1090 ajax.async = false;1110 ajax.async = false;
1091 var payload_str = JSON.stringify(payload);1111 var payload_str = JSON.stringify(payload, date_to_utc);
1092 var payload_url = $.param({r:payload_str});1112 var payload_url = $.param({r:payload_str});
1093 if(payload_url.length < 2000) {1113 if(payload_url.length < 2000) {
1094 // Direct jsonp request1114 // Direct jsonp request
10951115
=== modified file 'addons/web/static/src/js/data.js'
--- addons/web/static/src/js/data.js 2014-03-12 15:04:35 +0000
+++ addons/web/static/src/js/data.js 2014-03-13 15:59:47 +0000
@@ -127,10 +127,14 @@
127 return null;127 return null;
128 }128 }
129129
130 var raw_fields = _.map(grouping.concat(this._fields || []), function (field) {
131 return field.split(':')[0];
132 });
133
130 var self = this;134 var self = this;
131 return this._model.call('read_group', {135 return this._model.call('read_group', {
132 groupby: grouping,136 groupby: grouping,
133 fields: _.uniq(grouping.concat(this._fields || [])),137 fields: _.uniq(raw_fields),
134 domain: this._model.domain(this._filter),138 domain: this._model.domain(this._filter),
135 context: ctx,139 context: ctx,
136 offset: this._offset,140 offset: this._offset,
@@ -230,12 +234,14 @@
230234
231 var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0;235 var group_size = fixed_group[grouping_field + '_count'] || fixed_group.__count || 0;
232 var leaf_group = fixed_group.__context.group_by.length === 0;236 var leaf_group = fixed_group.__context.group_by.length === 0;
237 var raw_field = grouping_field && grouping_field.split(':')[0];
238
233 this.attributes = {239 this.attributes = {
234 folded: !!(fixed_group.__fold),240 folded: !!(fixed_group.__fold),
235 grouped_on: grouping_field,241 grouped_on: grouping_field,
236 // if terminal group (or no group) and group_by_no_leaf => use group.__count242 // if terminal group (or no group) and group_by_no_leaf => use group.__count
237 length: group_size,243 length: group_size,
238 value: fixed_group[grouping_field],244 value: fixed_group[raw_field],
239 // A group is open-able if it's not a leaf in group_by_no_leaf mode245 // A group is open-able if it's not a leaf in group_by_no_leaf mode
240 has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']),246 has_children: !(leaf_group && fixed_group.__context['group_by_no_leaf']),
241247
242248
=== modified file 'addons/web/static/src/js/pyeval.js'
--- addons/web/static/src/js/pyeval.js 2013-07-02 14:16:00 +0000
+++ addons/web/static/src/js/pyeval.js 2014-03-13 15:59:47 +0000
@@ -379,13 +379,28 @@
379 this[key] = asJS(args[key]);379 this[key] = asJS(args[key]);
380 }380 }
381 },381 },
382 replace: function () {
383 var args = py.PY_parseArgs(arguments, [
384 ['year', py.None], ['month', py.None], ['day', py.None],
385 ['hour', py.None], ['minute', py.None], ['second', py.None],
386 ['microsecond', py.None] // FIXME: tzinfo, can't use None as valid input
387 ]);
388 var params = {};
389 for(var key in args) {
390 if (!args.hasOwnProperty(key)) { continue; }
391
392 var arg = args[key];
393 params[key] = (arg === py.None ? this[key] : asJS(arg));
394 }
395 return py.PY_call(datetime.datetime, params);
396 },
382 strftime: function () {397 strftime: function () {
383 var self = this;398 var self = this;
384 var args = py.PY_parseArgs(arguments, 'format');399 var args = py.PY_parseArgs(arguments, 'format');
385 return py.str.fromJSON(args.format.toJSON()400 return py.str.fromJSON(args.format.toJSON()
386 .replace(/%([A-Za-z])/g, function (m, c) {401 .replace(/%([A-Za-z])/g, function (m, c) {
387 switch (c) {402 switch (c) {
388 case 'Y': return self.year;403 case 'Y': return _.str.sprintf('%04d', self.year);
389 case 'm': return _.str.sprintf('%02d', self.month);404 case 'm': return _.str.sprintf('%02d', self.month);
390 case 'd': return _.str.sprintf('%02d', self.day);405 case 'd': return _.str.sprintf('%02d', self.day);
391 case 'H': return _.str.sprintf('%02d', self.hour);406 case 'H': return _.str.sprintf('%02d', self.hour);
@@ -396,6 +411,17 @@
396 }));411 }));
397 },412 },
398 now: py.classmethod.fromJSON(function () {413 now: py.classmethod.fromJSON(function () {
414 var d = new Date;
415 return py.PY_call(datetime.datetime, [
416 d.getFullYear(), d.getMonth() + 1, d.getDate(),
417 d.getHours(), d.getMinutes(), d.getSeconds(),
418 d.getMilliseconds() * 1000]);
419 }),
420 today: py.classmethod.fromJSON(function () {
421 var dt_class = py.PY_getAttr(datetime, 'datetime');
422 return py.PY_call(py.PY_getAttr(dt_class, 'now'));
423 }),
424 utcnow: py.classmethod.fromJSON(function () {
399 var d = new Date();425 var d = new Date();
400 return py.PY_call(datetime.datetime,426 return py.PY_call(datetime.datetime,
401 [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),427 [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
@@ -412,7 +438,17 @@
412 py.PY_getAttr(args.time, 'minute'),438 py.PY_getAttr(args.time, 'minute'),
413 py.PY_getAttr(args.time, 'second')439 py.PY_getAttr(args.time, 'second')
414 ]);440 ]);
415 })441 }),
442 toJSON: function () {
443 return new Date(
444 this.year,
445 this.month - 1,
446 this.day,
447 this.hour,
448 this.minute,
449 this.second,
450 this.microsecond / 1000);
451 },
416 });452 });
417 datetime.date = py.type('date', null, {453 datetime.date = py.type('date', null, {
418 __init__: function () {454 __init__: function () {
@@ -467,7 +503,12 @@
467 },503 },
468 fromJSON: function (year, month, day) {504 fromJSON: function (year, month, day) {
469 return py.PY_call(datetime.date, [year, month, day])505 return py.PY_call(datetime.date, [year, month, day])
470 }506 },
507 today: py.classmethod.fromJSON(function () {
508 var d = new Date;
509 return py.PY_call(datetime.date, [
510 d.getFullYear(), d.getMonth() + 1, d.getDate()]);
511 }),
471 });512 });
472 /**513 /**
473 Returns the current local date, which means the date on the client (which can be different514 Returns the current local date, which means the date on the client (which can be different
@@ -498,7 +539,7 @@
498 time.strftime = py.PY_def.fromJSON(function () {539 time.strftime = py.PY_def.fromJSON(function () {
499 var args = py.PY_parseArgs(arguments, 'format');540 var args = py.PY_parseArgs(arguments, 'format');
500 var dt_class = py.PY_getAttr(datetime, 'datetime');541 var dt_class = py.PY_getAttr(datetime, 'datetime');
501 var d = py.PY_call(py.PY_getAttr(dt_class, 'now'));542 var d = py.PY_call(py.PY_getAttr(dt_class, 'utcnow'));
502 return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);543 return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
503 });544 });
504545
505546
=== modified file 'addons/web/static/test/evals.js'
--- addons/web/static/test/evals.js 2013-06-24 05:59:29 +0000
+++ addons/web/static/test/evals.js 2014-03-13 15:59:47 +0000
@@ -418,6 +418,41 @@
418 });418 });
419 });419 });
420 });420 });
421 test('datetime.tojson', function (instance) {
422 var result = py.eval(
423 'datetime.datetime(2012, 2, 15, 1, 7, 31)',
424 instance.web.pyeval.context());
425 ok(result instanceof Date);
426 equal(result.getFullYear(), 2012);
427 equal(result.getMonth(), 1);
428 equal(result.getDate(), 15);
429 equal(result.getHours(), 1);
430 equal(result.getMinutes(), 7);
431 equal(result.getSeconds(), 31);
432 });
433 test('datetime.combine', function (instance) {
434 var result = py.eval(
435 'datetime.datetime.combine(datetime.date(2012, 2, 15),' +
436 ' datetime.time(1, 7, 13))' +
437 ' .strftime("%Y-%m-%d %H:%M:%S")',
438 instance.web.pyeval.context());
439 equal(result, "2012-02-15 01:07:13");
440
441 result = py.eval(
442 'datetime.datetime.combine(datetime.date(2012, 2, 15),' +
443 ' datetime.time())' +
444 ' .strftime("%Y-%m-%d %H:%M:%S")',
445 instance.web.pyeval.context());
446 equal(result, '2012-02-15 00:00:00');
447 });
448 test('datetime.replace', function (instance) {
449 var result = py.eval(
450 'datetime.datetime(2012, 2, 15, 1, 7, 13)' +
451 ' .replace(hour=0, minute=0, second=0)' +
452 ' .strftime("%Y-%m-%d %H:%M:%S")',
453 instance.web.pyeval.context());
454 equal(result, "2012-02-15 00:00:00");
455 });
421});456});
422openerp.testing.section('eval.edc.nonliterals', {457openerp.testing.section('eval.edc.nonliterals', {
423 dependencies: ['web.data'],458 dependencies: ['web.data'],