diff --git a/addons/web/__openerp__.py b/addons/web/__openerp__.py index 822d2fa3cbf..f64f853d0ea 100644 --- a/addons/web/__openerp__.py +++ b/addons/web/__openerp__.py @@ -39,6 +39,7 @@ This module provides the core of the OpenERP Web Client. "static/lib/cleditor/jquery.cleditor.js", "static/lib/py.js/lib/py.js", "static/src/js/boot.js", + "static/src/js/pyeval.js", "static/src/js/corelib.js", "static/src/js/coresetup.js", "static/src/js/dates.js", diff --git a/addons/web/static/src/js/boot.js b/addons/web/static/src/js/boot.js index 844dd88a0ac..dd014846884 100644 --- a/addons/web/static/src/js/boot.js +++ b/addons/web/static/src/js/boot.js @@ -48,7 +48,7 @@ * OpenERP Web web module split *---------------------------------------------------------*/ openerp.web = function(session) { - var files = ["corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"]; + var files = ["pyeval", "corelib","coresetup","dates","formats","chrome","data","views","search","list","form","list_editable","web_mobile","view_tree","data_export","data_import"]; for(var i=0; i 12) { - year += 1; - month -= 12; - } - } - - var lastMonthDay = new Date(year, month, 0).getDate(); - var day = asJS(this.ops.day) || asJS(other.day); - if (day > lastMonthDay) { day = lastMonthDay; } - var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0); - if (days_offset) { - day = new Date(year, month-1, day + days_offset).getDate(); - } - // TODO: leapdays? - // TODO: hours, minutes, seconds? Not used in XML domains - // TODO: weekday? - return new datetime.date(year, month, day); - }, - __radd__: function (other) { - return this.__add__(other); - }, - - __sub__: function (other) { - if (!(other instanceof datetime.date)) { - return py.NotImplemented; - } - // TODO: test this whole mess - var year = asJS(this.ops.year) || asJS(other.year); - if (asJS(this.ops.years)) { - year -= asJS(this.ops.years); - } - - var month = asJS(this.ops.month) || asJS(other.month); - if (asJS(this.ops.months)) { - month -= asJS(this.ops.months); - // FIXME: no divmod in JS? - while (month < 1) { - year -= 1; - month += 12; - } - while (month > 12) { - year += 1; - month -= 12; - } - } - - var lastMonthDay = new Date(year, month, 0).getDate(); - var day = asJS(this.ops.day) || asJS(other.day); - if (day > lastMonthDay) { day = lastMonthDay; } - var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0); - if (days_offset) { - day = new Date(year, month-1, day - days_offset).getDate(); - } - // TODO: leapdays? - // TODO: hours, minutes, seconds? Not used in XML domains - // TODO: weekday? - return new datetime.date(year, month, day); - }, - __rsub__: function (other) { - return this.__sub__(other); - } - }); - - return { - uid: new py.float(this.uid), - datetime: datetime, - time: time, - relativedelta: relativedelta, - current_date: date.today.__call__().strftime(['%Y-%m-%d']) - }; - }, /** * FIXME: Huge testing hack, especially the evaluation context, rewrite + test for real before switching */ @@ -1154,8 +1006,8 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ ''; try { // see Session.eval_context in Python - var ctx = this.test_eval_contexts( - ([this.context] || []).concat(source.contexts)); + var ctx = instance.web.pyeval.eval('contexts', + ([this.context] || []).concat(source.contexts)); if (!_.isEqual(ctx, expected.context)) { instance.webclient.notification.warn('Context mismatch, report to xmo', _.str.sprintf(match_template, { @@ -1173,7 +1025,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ } try { - var dom = this.test_eval_domains(source.domains, this.test_eval_get_context()); + var dom = instance.web.pyeval.eval('domains', source.domains); if (!_.isEqual(dom, expected.domain)) { instance.webclient.notification.warn('Domains mismatch, report to xmo', _.str.sprintf(match_template, { @@ -1191,7 +1043,7 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ } try { - var groups = this.test_eval_groupby(source.group_by_seq); + var groups = instance.web.pyeval.eval('groupbys', source.group_by_seq); if (!_.isEqual(groups, expected.group_by)) { instance.webclient.notification.warn('GroupBy mismatch, report to xmo', _.str.sprintf(match_template, { @@ -1208,79 +1060,6 @@ instance.web.JsonRPC = instance.web.CallbackEnabled.extend({ }), true); } }, - test_eval_contexts: function (contexts, evaluation_context) { - evaluation_context = evaluation_context || {}; - var self = this; - return _(contexts).reduce(function (result_context, ctx) { - // __eval_context evaluations can lead to some of `contexts`'s - // values being null, skip them as well as empty contexts - if (_.isEmpty(ctx)) { return result_context; } - var evaluated = ctx; - switch(ctx.__ref) { - case 'context': - evaluated = py.eval(ctx.__debug, evaluation_context); - break; - case 'compound_context': - var eval_context = self.test_eval_contexts([ctx.__eval_context]); - evaluated = self.test_eval_contexts( - ctx.__contexts, _.extend({}, evaluation_context, eval_context)); - break; - } - // add newly evaluated context to evaluation context for following - // siblings - _.extend(evaluation_context, evaluated); - return _.extend(result_context, evaluated); - }, _.extend({}, this.user_context)); - }, - test_eval_domains: function (domains, evaluation_context) { - var result_domain = [], self = this; - _(domains).each(function (dom) { - switch(dom.__ref) { - case 'domain': - result_domain.push.apply( - result_domain, py.eval(dom.__debug, evaluation_context)); - break; - case 'compound_domain': - var eval_context = self.test_eval_contexts([dom.__eval_context]); - result_domain.push.apply( - result_domain, self.test_eval_domains( - dom.__domains, _.extend( - {}, evaluation_context, eval_context))); - break; - default: - result_domain.push.apply( - result_domain, dom); - } - }); - return result_domain; - }, - test_eval_groupby: function (contexts) { - var result_group = [], self = this; - _(contexts).each(function (ctx) { - var group; - switch(ctx.__ref) { - case 'context': - group = py.eval(ctx.__debug).group_by; - break; - case 'compound_context': - group = self.test_eval_contexts( - ctx.__contexts, ctx.__eval_context).group_by; - break; - default: - group = ctx.group_by - } - if (!group) { return; } - if (typeof group === 'string') { - result_group.push(group); - } else if (group instanceof Array) { - result_group.push.apply(result_group, group); - } else { - throw new Error('Got invalid groupby {{' - + JSON.stringify(group) + '}}'); - } - }); - return result_group; - }, /** * Executes an RPC call, registering the provided callbacks. * diff --git a/addons/web/static/src/js/pyeval.js b/addons/web/static/src/js/pyeval.js new file mode 100644 index 00000000000..8c498c3f885 --- /dev/null +++ b/addons/web/static/src/js/pyeval.js @@ -0,0 +1,244 @@ +/* + * py.js helpers and setup + */ +openerp.web.pyeval = function (instance) { + instance.web.pyeval = {}; + + var asJS = function (arg) { + if (arg instanceof py.object) { + return arg.toJSON(); + } + return arg; + }; + + var datetime = new py.object(); + datetime.datetime = new py.type(function datetime() { + throw new Error('datetime.datetime not implemented'); + }); + var date = datetime.date = new py.type(function date(y, m, d) { + if (y instanceof Array) { + d = y[2]; + m = y[1]; + y = y[0]; + } + this.year = asJS(y); + this.month = asJS(m); + this.day = asJS(d); + }, py.object, { + strftime: function (args) { + var f = asJS(args[0]), self = this; + return new py.str(f.replace(/%([A-Za-z])/g, function (m, c) { + switch (c) { + case 'Y': return self.year; + case 'm': return _.str.sprintf('%02d', self.month); + case 'd': return _.str.sprintf('%02d', self.day); + } + throw new Error('ValueError: No known conversion for ' + m); + })); + } + }); + date.__getattribute__ = function (name) { + if (name === 'today') { + return date.today; + } + throw new Error("AttributeError: object 'date' has no attribute '" + name +"'"); + }; + date.today = new py.def(function () { + var d = new Date(); + return new date(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()); + }); + datetime.time = new py.type(function time() { + throw new Error('datetime.time not implemented'); + }); + + var time = new py.object(); + time.strftime = new py.def(function (args) { + return date.today.__call__().strftime(args); + }); + + var relativedelta = new py.type(function relativedelta(args, kwargs) { + if (!_.isEmpty(args)) { + throw new Error('Extraction of relative deltas from existing datetimes not supported'); + } + this.ops = kwargs; + }, py.object, { + __add__: function (other) { + if (!(other instanceof datetime.date)) { + return py.NotImplemented; + } + // TODO: test this whole mess + var year = asJS(this.ops.year) || asJS(other.year); + if (asJS(this.ops.years)) { + year += asJS(this.ops.years); + } + + var month = asJS(this.ops.month) || asJS(other.month); + if (asJS(this.ops.months)) { + month += asJS(this.ops.months); + // FIXME: no divmod in JS? + while (month < 1) { + year -= 1; + month += 12; + } + while (month > 12) { + year += 1; + month -= 12; + } + } + + var lastMonthDay = new Date(year, month, 0).getDate(); + var day = asJS(this.ops.day) || asJS(other.day); + if (day > lastMonthDay) { day = lastMonthDay; } + var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0); + if (days_offset) { + day = new Date(year, month-1, day + days_offset).getDate(); + } + // TODO: leapdays? + // TODO: hours, minutes, seconds? Not used in XML domains + // TODO: weekday? + return new datetime.date(year, month, day); + }, + __radd__: function (other) { + return this.__add__(other); + }, + + __sub__: function (other) { + if (!(other instanceof datetime.date)) { + return py.NotImplemented; + } + // TODO: test this whole mess + var year = asJS(this.ops.year) || asJS(other.year); + if (asJS(this.ops.years)) { + year -= asJS(this.ops.years); + } + + var month = asJS(this.ops.month) || asJS(other.month); + if (asJS(this.ops.months)) { + month -= asJS(this.ops.months); + // FIXME: no divmod in JS? + while (month < 1) { + year -= 1; + month += 12; + } + while (month > 12) { + year += 1; + month -= 12; + } + } + + var lastMonthDay = new Date(year, month, 0).getDate(); + var day = asJS(this.ops.day) || asJS(other.day); + if (day > lastMonthDay) { day = lastMonthDay; } + var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0); + if (days_offset) { + day = new Date(year, month-1, day - days_offset).getDate(); + } + // TODO: leapdays? + // TODO: hours, minutes, seconds? Not used in XML domains + // TODO: weekday? + return new datetime.date(year, month, day); + }, + __rsub__: function (other) { + return this.__sub__(other); + } + }); + + var eval_contexts = function (contexts, evaluation_context) { + evaluation_context = evaluation_context || {}; + return _(contexts).reduce(function (result_context, ctx) { + // __eval_context evaluations can lead to some of `contexts`'s + // values being null, skip them as well as empty contexts + if (_.isEmpty(ctx)) { return result_context; } + var evaluated = ctx; + switch(ctx.__ref) { + case 'context': + evaluated = py.eval(ctx.__debug, evaluation_context); + break; + case 'compound_context': + var eval_context = eval_contexts([ctx.__eval_context]); + evaluated = eval_contexts( + ctx.__contexts, _.extend({}, evaluation_context, eval_context)); + break; + } + // add newly evaluated context to evaluation context for following + // siblings + _.extend(evaluation_context, evaluated); + return _.extend(result_context, evaluated); + }, _.extend({}, instance.connection.user_context)); + }; + var eval_domains = function (domains, evaluation_context) { + var result_domain = []; + _(domains).each(function (domain) { + switch(domain.__ref) { + case 'domain': + result_domain.push.apply( + result_domain, py.eval(domain.__debug, evaluation_context)); + break; + case 'compound_domain': + var eval_context = eval_contexts([domain.__eval_context]); + result_domain.push.apply( + result_domain, eval_domains( + domain.__domains, _.extend( + {}, evaluation_context, eval_context))); + break; + default: + result_domain.push.apply(result_domain, domain); + } + }); + return result_domain; + }; + var eval_groupbys = function (contexts, evaluation_context) { + var result_group = []; + _(contexts).each(function (ctx) { + var group; + var evaluated = ctx; + switch(ctx.__ref) { + case 'context': + evaluated = py.eval(ctx.__debug), evaluation_context; + break; + case 'compound_context': + var eval_context = eval_contexts([ctx.__eval_context]); + evaluated = eval_contexts( + ctx.__contexts, _.extend({}, evaluation_context, eval_context)); + break; + } + group = evaluated.group_by; + if (!group) { return; } + if (typeof group === 'string') { + result_group.push(group); + } else if (group instanceof Array) { + result_group.push.apply(result_group, group); + } else { + throw new Error('Got invalid groupby {{' + + JSON.stringify(group) + '}}'); + } + _.extend(evaluation_context, evaluated); + }); + return result_group; + }; + + instance.web.pyeval.context = function () { + return { + uid: new py.float(instance.connection.uid), + datetime: datetime, + time: time, + relativedelta: relativedelta, + current_date: date.today.__call__().strftime(['%Y-%m-%d']), + }; + }; + + /** + * @param {String} type "domains", "contexts" or "groupbys" + * @param {Array} object domains or contexts to evaluate + * @param {Object} [context] evaluation context + */ + instance.web.pyeval.eval = function (type, object, context) { + if (!context) { context = instance.web.pyeval.context()} + switch(type) { + case 'contexts': return eval_contexts(object, context); + case 'domains': return eval_domains(object, context); + case 'groupbys': return eval_groupbys(object, context); + } + throw new Error("Unknow evaluation type " + type) + }; +}; diff --git a/addons/web/static/test/evals.js b/addons/web/static/test/evals.js index 7bc6554e074..ee4aef4bf79 100644 --- a/addons/web/static/test/evals.js +++ b/addons/web/static/test/evals.js @@ -3,16 +3,14 @@ $(document).ready(function () { module("eval.contexts", { setup: function () { - openerp = window.openerp.init([]); - window.openerp.web.corelib(openerp); - window.openerp.web.coresetup(openerp); + openerp = window.openerp.testing.instanceFor('coresetup'); } }); test('context_sequences', function () { // Context n should have base evaluation context + all of contexts // 0..n-1 in its own evaluation context var active_id = 4; - var result = openerp.connection.test_eval_contexts([ + var result = openerp.web.pyeval.eval('contexts', [ { "__contexts": [ { @@ -56,7 +54,7 @@ $(document).ready(function () { }); }); test('non-literal_eval_contexts', function () { - var result = openerp.connection.test_eval_contexts([{ + var result = openerp.web.pyeval.eval('contexts', [{ "__ref": "compound_context", "__contexts": [ {"__ref": "context", "__debug": "{'type':parent.type}", @@ -141,9 +139,8 @@ $(document).ready(function () { }); test('current_date', function () { var current_date = openerp.web.date_to_str(new Date()); - var result = openerp.connection.test_eval_domains( - [[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}], - openerp.connection.test_eval_get_context()); + var result = openerp.web.pyeval.eval('domains', + [[],{"__ref":"domain","__debug":"[('name','>=',current_date),('name','<=',current_date)]","__id":"5dedcfc96648"}]); deepEqual(result, [ ['name', '>=', current_date], ['name', '<=', current_date] diff --git a/addons/web/static/test/test.html b/addons/web/static/test/test.html index 35d57d3c3a6..91b45c44bad 100644 --- a/addons/web/static/test/test.html +++ b/addons/web/static/test/test.html @@ -28,6 +28,7 @@ + diff --git a/addons/web/static/test/testing.js b/addons/web/static/test/testing.js index e9525fc82a0..9a449a40493 100644 --- a/addons/web/static/test/testing.js +++ b/addons/web/static/test/testing.js @@ -6,7 +6,8 @@ openerp.testing = (function () { var doc = xhr.responseXML; var dependencies = { - corelib: [], + pyeval: [], + corelib: ['pyeval'], coresetup: ['corelib'], data: ['corelib', 'coresetup'], dates: [],