a00fd186d58efdd8ea357cc4c6c361e65f08ee4e
[odoo/odoo.git] / addons / web / static / src / js / pyeval.js
1 /*
2  * py.js helpers and setup
3  */
4 openerp.web.pyeval = function (instance) {
5     instance.web.pyeval = {};
6
7     var obj = function () {};
8     obj.prototype = py.object;
9     var asJS = function (arg) {
10         if (arg instanceof obj) {
11             return arg.toJSON();
12         }
13         return arg;
14     };
15
16     var datetime = py.PY_call(py.object);
17     datetime.datetime = py.type('datetime', null, {
18         __init__: function () {
19             var zero = py.float.fromJSON(0);
20             var args = py.PY_parseArgs(arguments, [
21                 'year', 'month', 'day',
22                 ['hour', zero], ['minute', zero], ['second', zero],
23                 ['microsecond', zero], ['tzinfo', py.None]
24             ]);
25             for(var key in args) {
26                 if (!args.hasOwnProperty(key)) { continue; }
27                 this[key] = asJS(args[key]);
28             }
29         },
30         strftime: function () {
31             var self = this;
32             var args = py.PY_parseArgs(arguments, 'format');
33             return py.str.fromJSON(args.format.toJSON()
34                 .replace(/%([A-Za-z])/g, function (m, c) {
35                     switch (c) {
36                     case 'Y': return self.year;
37                     case 'm': return _.str.sprintf('%02d', self.month);
38                     case 'd': return _.str.sprintf('%02d', self.day);
39                     case 'H': return _.str.sprintf('%02d', self.hour);
40                     case 'M': return _.str.sprintf('%02d', self.minute);
41                     case 'S': return _.str.sprintf('%02d', self.second);
42                     }
43                     throw new Error('ValueError: No known conversion for ' + m);
44                 }));
45         },
46         now: py.classmethod.fromJSON(function () {
47             var d = new Date();
48             return py.PY_call(datetime.datetime,
49                 [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate(),
50                  d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(),
51                  d.getUTCMilliseconds() * 1000]);
52         }),
53         today: py.classmethod.fromJSON(function () {
54             var d = new Date();
55             return py.PY_call(datetime.datetime,
56                 [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()]);
57         }),
58         combine: py.classmethod.fromJSON(function () {
59             var args = py.PY_parseArgs(arguments, 'date time');
60             return py.PY_call(datetime.datetime, [
61                 // FIXME: should use getattr
62                 args.date.year,
63                 args.date.month,
64                 args.date.day,
65                 args.time.hour,
66                 args.time.minute,
67                 args.time.second
68             ]);
69         })
70     });
71     datetime.date = py.type('date', null, {
72         __init__: function () {
73             var args = py.PY_parseArgs(arguments, 'year month day');
74             this.year = asJS(args.year);
75             this.month = asJS(args.month);
76             this.day = asJS(args.day);
77         },
78         strftime: function () {
79             var self = this;
80             var args = py.PY_parseArgs(arguments, 'format');
81             return py.str.fromJSON(args.format.toJSON()
82                 .replace(/%([A-Za-z])/g, function (m, c) {
83                     switch (c) {
84                     case 'Y': return self.year;
85                     case 'm': return _.str.sprintf('%02d', self.month);
86                     case 'd': return _.str.sprintf('%02d', self.day);
87                     }
88                     throw new Error('ValueError: No known conversion for ' + m);
89                 }));
90         },
91         today: py.classmethod.fromJSON(function () {
92             var d = new Date();
93             return py.PY_call(
94                 datetime.date, [d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate()]);
95         })
96     });
97     datetime.time = py.type('time', null, {
98         __init__: function () {
99             var zero = py.float.fromJSON(0);
100             var args = py.PY_parseArgs(arguments, [
101                 ['hour', zero], ['minute', zero], ['second', zero], ['microsecond', zero],
102                 ['tzinfo', py.None]
103             ]);
104
105             for(var k in args) {
106                 if (!args.hasOwnProperty(k)) { continue; }
107                 this[k] = asJS(args[k]);
108             }
109         }
110     });
111
112     var time = py.PY_call(py.object);
113     time.strftime = py.PY_def.fromJSON(function () {
114         // FIXME: needs PY_getattr
115         var d = py.PY_call(datetime.__getattribute__('datetime')
116                                    .__getattribute__('now'));
117         var args = [].slice.call(arguments);
118         return py.PY_call.apply(
119             null, [d.__getattribute__('strftime')].concat(args));
120     });
121
122     var relativedelta = py.type('relativedelta', null, {
123         __init__: function () {
124             this.ops = py.PY_parseArgs(arguments,
125                 '* year month day hour minute second microsecond '
126                 + 'years months weeks days hours minutes secondes microseconds '
127                 + 'weekday leakdays yearday nlyearday');
128         },
129         __add__: function (other) {
130             if (py.PY_call(py.isinstance, [datetime.date]) !== py.True) {
131                 return py.NotImplemented;
132             }
133             // TODO: test this whole mess
134             var year = asJS(this.ops.year) || asJS(other.year);
135             if (asJS(this.ops.years)) {
136                 year += asJS(this.ops.years);
137             }
138
139             var month = asJS(this.ops.month) || asJS(other.month);
140             if (asJS(this.ops.months)) {
141                 month += asJS(this.ops.months);
142                 // FIXME: no divmod in JS?
143                 while (month < 1) {
144                     year -= 1;
145                     month += 12;
146                 }
147                 while (month > 12) {
148                     year += 1;
149                     month -= 12;
150                 }
151             }
152
153             var lastMonthDay = new Date(year, month, 0).getDate();
154             var day = asJS(this.ops.day) || asJS(other.day);
155             if (day > lastMonthDay) { day = lastMonthDay; }
156             var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
157             if (days_offset) {
158                 day = new Date(year, month-1, day + days_offset).getDate();
159             }
160             // TODO: leapdays?
161             // TODO: hours, minutes, seconds? Not used in XML domains
162             // TODO: weekday?
163             // FIXME: use date.replace
164             return py.PY_call(datetime.date, [
165                 py.float.fromJSON(year),
166                 py.float.fromJSON(month),
167                 py.float.fromJSON(day)
168             ]);
169         },
170         __radd__: function (other) {
171             return this.__add__(other);
172         },
173
174         __sub__: function (other) {
175             if (py.PY_call(py.isinstance, [datetime.date]) !== py.True) {
176                 return py.NotImplemented;
177             }
178             // TODO: test this whole mess
179             var year = asJS(this.ops.year) || asJS(other.year);
180             if (asJS(this.ops.years)) {
181                 year -= asJS(this.ops.years);
182             }
183
184             var month = asJS(this.ops.month) || asJS(other.month);
185             if (asJS(this.ops.months)) {
186                 month -= asJS(this.ops.months);
187                 // FIXME: no divmod in JS?
188                 while (month < 1) {
189                     year -= 1;
190                     month += 12;
191                 }
192                 while (month > 12) {
193                     year += 1;
194                     month -= 12;
195                 }
196             }
197
198             var lastMonthDay = new Date(year, month, 0).getDate();
199             var day = asJS(this.ops.day) || asJS(other.day);
200             if (day > lastMonthDay) { day = lastMonthDay; }
201             var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
202             if (days_offset) {
203                 day = new Date(year, month-1, day - days_offset).getDate();
204             }
205             // TODO: leapdays?
206             // TODO: hours, minutes, seconds? Not used in XML domains
207             // TODO: weekday?
208             return py.PY_call(datetime.date, [
209                 py.float.fromJSON(year),
210                 py.float.fromJSON(month),
211                 py.float.fromJSON(day)
212             ]);
213         },
214         __rsub__: function (other) {
215             return this.__sub__(other);
216         }
217     });
218
219     var eval_contexts = function (contexts, evaluation_context) {
220         evaluation_context = evaluation_context || {};
221         return _(contexts).reduce(function (result_context, ctx) {
222             // __eval_context evaluations can lead to some of `contexts`'s
223             // values being null, skip them as well as empty contexts
224             if (_.isEmpty(ctx)) { return result_context; }
225             var evaluated = ctx;
226             switch(ctx.__ref) {
227             case 'context':
228                 evaluated = py.eval(ctx.__debug, evaluation_context);
229                 break;
230             case 'compound_context':
231                 var eval_context = eval_contexts([ctx.__eval_context]);
232                 evaluated = eval_contexts(
233                     ctx.__contexts, _.extend({}, evaluation_context, eval_context));
234                 break;
235             }
236             // add newly evaluated context to evaluation context for following
237             // siblings
238             _.extend(evaluation_context, evaluated);
239             return _.extend(result_context, evaluated);
240         }, _.extend({}, instance.session.user_context));
241     };
242     var eval_domains = function (domains, evaluation_context) {
243         var result_domain = [];
244         _(domains).each(function (domain) {
245             switch(domain.__ref) {
246             case 'domain':
247                 result_domain.push.apply(
248                     result_domain, py.eval(domain.__debug, evaluation_context));
249                 break;
250             case 'compound_domain':
251                 var eval_context = eval_contexts([domain.__eval_context]);
252                 result_domain.push.apply(
253                     result_domain, eval_domains(
254                         domain.__domains, _.extend(
255                             {}, evaluation_context, eval_context)));
256                 break;
257             default:
258                 result_domain.push.apply(result_domain, domain);
259             }
260         });
261         return result_domain;
262     };
263     var eval_groupbys = function (contexts, evaluation_context) {
264         var result_group = [];
265         _(contexts).each(function (ctx) {
266             var group;
267             var evaluated = ctx;
268             switch(ctx.__ref) {
269             case 'context':
270                 evaluated = py.eval(ctx.__debug, evaluation_context);
271                 break;
272             case 'compound_context':
273                 var eval_context = eval_contexts([ctx.__eval_context]);
274                 evaluated = eval_contexts(
275                     ctx.__contexts, _.extend({}, evaluation_context, eval_context));
276                 break;
277             }
278             group = evaluated.group_by;
279             if (!group) { return; }
280             if (typeof group === 'string') {
281                 result_group.push(group);
282             } else if (group instanceof Array) {
283                 result_group.push.apply(result_group, group);
284             } else {
285                 throw new Error('Got invalid groupby {{'
286                         + JSON.stringify(group) + '}}');
287             }
288             _.extend(evaluation_context, evaluated);
289         });
290         return result_group;
291     };
292
293     instance.web.pyeval.context = function () {
294         return {
295             uid: py.float.fromJSON(instance.session.uid),
296             datetime: datetime,
297             time: time,
298             relativedelta: relativedelta,
299             current_date: py.PY_call(
300                 time.strftime, [py.str.fromJSON('%Y-%m-%d')]),
301         };
302     };
303
304     /**
305      * @param {String} type "domains", "contexts" or "groupbys"
306      * @param {Array} object domains or contexts to evaluate
307      * @param {Object} [context] evaluation context
308      */
309     instance.web.pyeval.eval = function (type, object, context) {
310         context = _.extend(instance.web.pyeval.context(), context || {});
311         context['context'] = py.dict.fromJSON(context);
312
313         switch(type) {
314         case 'contexts': return eval_contexts(object, context);
315         case 'domains': return eval_domains(object, context);
316         case 'groupbys': return eval_groupbys(object, context);
317         }
318         throw new Error("Unknow evaluation type " + type)
319     };
320 };