Merge lp:~therp-nl/openerp-web/trunk-relativedelta-momentjs into lp:openerp-web

Proposed by Holger Brunn (Therp)
Status: Needs review
Proposed branch: lp:~therp-nl/openerp-web/trunk-relativedelta-momentjs
Merge into: lp:openerp-web
Prerequisite: lp:~therp-nl/openerp-web/trunk-momentjs
Diff against target: 312 lines (+121/-158)
3 files modified
addons/web/__openerp__.py (+0/-63)
addons/web/static/src/js/pyeval.js (+118/-95)
addons/web/views/webclient_templates.xml (+3/-0)
To merge this branch: bzr merge lp:~therp-nl/openerp-web/trunk-relativedelta-momentjs
Reviewer Review Type Date Requested Status
Mathieu Benoit (community) Needs Information
OpenERP R&D Web Team Pending
Review via email: mp+217734@code.launchpad.net

Description of the change

This is a proposal to include Moment.js (http://momentjs.com, hidden in prerequisite branch for simpler review) into core and a reimplementation of relativedelta for pyjs that makes use of it.

This version of relativedelta supports the weekday parameter as well as hour, minute and second, which is why it also handles datetime objects.

Further, it is meant to mimic python's relativedelta more closely than the current implementation does (for example, relativedelta(months=1, days=-1) does what you want here)

Moment.js also seems to be a good replacement for the unmaintained datejs, so the latter could be deprecated in favor of moment.js

To post a comment you must log in.
Revision history for this message
Mathieu Benoit (mathben) wrote :
Download full text (90.2 KiB)

Code has change. I suggest this patch.

=== modified file 'addons/web/__openerp__.py'
--- addons/web/__openerp__.py 2013-04-16 09:23:44 +0000
+++ addons/web/__openerp__.py 2014-06-25 22:13:32 +0000
@@ -42,6 +42,7 @@
         "static/lib/backbone/backbone.js",
         "static/lib/cleditor/jquery.cleditor.js",
         "static/lib/py.js/lib/py.js",
+ "static/lib/moment.js",
         "static/src/js/boot.js",
         "static/src/js/testing.js",
         "static/src/js/pyeval.js",

=== added file 'addons/web/static/lib/moment.js'
--- addons/web/static/lib/moment.js 1970-01-01 00:00:00 +0000
+++ addons/web/static/lib/moment.js 2014-04-30 09:24:00 +0000
@@ -0,0 +1,2489 @@
+//! moment.js
+//! version : 2.6.0
+//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
+//! license : MIT
+//! momentjs.com
+
+(function (undefined) {
+
+ /************************************
+ Constants
+ ************************************/
+
+ var moment,
+ VERSION = "2.6.0",
+ // the global-scope this is NOT the global object in Node.js
+ globalScope = typeof global !== 'undefined' ? global : this,
+ oldGlobalMoment,
+ round = Math.round,
+ i,
+
+ YEAR = 0,
+ MONTH = 1,
+ DATE = 2,
+ HOUR = 3,
+ MINUTE = 4,
+ SECOND = 5,
+ MILLISECOND = 6,
+
+ // internal storage for language config files
+ languages = {},
+
+ // moment internal properties
+ momentProperties = {
+ _isAMomentObject: null,
+ _i : null,
+ _f : null,
+ _l : null,
+ _strict : null,
+ _isUTC : null,
+ _offset : null, // optional. Combine with _isUTC
+ _pf : null,
+ _lang : null // optional
+ },
+
+ // check for nodeJS
+ hasModule = (typeof module !== 'undefined' && module.exports),
+
+ // ASP.NET json date format regex
+ aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
+ aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
+
+ // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
+ // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
+ isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
+
+ // format tokens
+ formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
+ localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
+
+ // parsing token regexes
+ parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
+ parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
+ parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
+ parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
+ parseTokenDigits = /\d+/, // nonzero number of digits
+ parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06F...

review: Needs Fixing
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

could you please provide an MP on this branch? I can't really see what the differences are.

Revision history for this message
Mathieu Benoit (mathben) wrote :

Sorry, your code works. I just update from trunk (merge).

This is the MP : https://code.launchpad.net/~savoirfairelinux-openerp/openerp-web/7.0-relativedelta-momentjs/+merge/224691

I will change my last review if you update from the trunk.
Thanks for your contributions.

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

ah, now I understand what you meant. Thanks for the pointer!

3998. By Holger Brunn (Therp)

[MRG] trunk
[ADD] add moment.js to backend template

3999. By Holger Brunn (Therp)

[FIX] cleanup __openerp__.py

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

PS: Why do we do this here and not on github?

Revision history for this message
Mathieu Benoit (mathben) :
review: Approve
Revision history for this message
Mathieu Benoit (mathben) wrote :

> PS: Why do we do this here and not on github?
Because everybody is in transition?
You have right, I am sure the feature has more chance to be accepted on github ;-)
https://github.com/odoo/odoo/tree/master/addons/web

Create an issue and associate it with a pull request, I will support it!

Revision history for this message
Mathieu Benoit (mathben) wrote :

I noticed something about "weeks" in relativedelta. I am trying to obtain a domain of all dates for last week.

Python code with bpython2 interpreter :
>>> from datetime import *
>>> from dateutil.relativedelta import *
>>> datetime.now()
datetime.datetime(2014, 6, 26, 14, 7, 20, 82037)
>>> datetime.now()+ relativedelta(weeks=-2, weekday=0)
datetime.datetime(2014, 6, 16, 14, 7, 26, 585995)
>>> datetime.now() + relativedelta(weeks=-1, weekday=6)
datetime.datetime(2014, 6, 22, 14, 7, 45, 118444)

But, with "momentjs", for the first day of last week (monday), I only need to remove 1 weeks, like this :
domain="[('date', '>=', (context_today() + relativedelta(weeks=-1, weekday=0)).strftime('%%Y-%%m-%%d')), ('date', '<=', (context_today() + relativedelta(weeks=-1, weekday=6)).strftime('%%Y-%%m-%%d'))]"

I received this on my request :
"domain":[["date",">=","2014-06-16"],["date","<=","2014-06-22"]]

It's not the same comportement of python relative day. I tried with last month and last day, and it works.

review: Needs Information
Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

Your python expression gives you the wrong start date on Mondays. That's what relativedelta(weekday=MO(-2)) is for.

I wrongly implemented weekday the way that it sets the day of week in the current week (as opposed to dateutil.relativedelta which from the current date picks the next/previous weekday that was asked for).

Now there are several options: Accept differences between python and the js-implementation (bad option) or implement the WEEKDAY(n) syntax (i think that's going to be quite a PITA)

The best option I think would be to get it right for weekday=n, then Monday of current week would be
now() + relativedelta(weeks=-1, days=1, weekday=0) and Sunday relativedelta(weekday=6). That's the simplest option to implement, but I'm not sure if it makes users happy.

Opinions?

4000. By Holger Brunn (Therp)

[FIX] wrong weekday evaluation

Revision history for this message
Holger Brunn (Therp) (hbrunn) wrote :

I added a fix for this, and in https://code.launchpad.net/~therp-nl/web-addons/7.0-web_relativedelta/+merge/217653 also tests.

I think it makes sense to concentrate on the addon version, get everything right there (by adding more tests) and then come back on this MP.

Unmerged revisions

4000. By Holger Brunn (Therp)

[FIX] wrong weekday evaluation

3999. By Holger Brunn (Therp)

[FIX] cleanup __openerp__.py

3998. By Holger Brunn (Therp)

[MRG] trunk
[ADD] add moment.js to backend template

3997. By Holger Brunn (Therp)

[IMP] rewrite relativedelta using moment.js

3996. By Holger Brunn (Therp)

[ADD] moment.js (http://momentjs.com)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'addons/web/__openerp__.py'
2--- addons/web/__openerp__.py 2014-06-30 14:00:02 +0000
3+++ addons/web/__openerp__.py 2014-04-28 18:24:51 +0000
4@@ -14,69 +14,6 @@
5 'data': [
6 'views/webclient_templates.xml',
7 ],
8-<<<<<<< TREE
9-=======
10- 'js': [
11- "static/lib/es5-shim/es5-shim.min.js",
12- "static/lib/datejs/globalization/en-US.js",
13- "static/lib/datejs/core.js",
14- "static/lib/datejs/parser.js",
15- "static/lib/datejs/sugarpak.js",
16- "static/lib/datejs/extras.js",
17- "static/lib/jquery/jquery.js",
18- "static/lib/jquery.form/jquery.form.js",
19- "static/lib/jquery.validate/jquery.validate.js",
20- "static/lib/jquery.ba-bbq/jquery.ba-bbq.js",
21- "static/lib/spinjs/spin.js",
22- "static/lib/jquery.autosize/jquery.autosize.js",
23- "static/lib/jquery.blockUI/jquery.blockUI.js",
24- "static/lib/jquery.hotkeys/jquery.hotkeys.js",
25- "static/lib/jquery.placeholder/jquery.placeholder.js",
26- "static/lib/jquery.ui/js/jquery-ui-1.9.1.custom.js",
27- "static/lib/jquery.ui.timepicker/js/jquery-ui-timepicker-addon.js",
28- "static/lib/jquery.ui.notify/js/jquery.notify.js",
29- "static/lib/jquery.deferred-queue/jquery.deferred-queue.js",
30- "static/lib/jquery.scrollTo/jquery.scrollTo-min.js",
31- "static/lib/jquery.textext/jquery.textext.js",
32- "static/lib/jquery.timeago/jquery.timeago.js",
33- "static/lib/bootstrap/js/bootstrap.js",
34- "static/lib/qweb/qweb2.js",
35- "static/lib/underscore/underscore.js",
36- "static/lib/underscore.string/lib/underscore.string.js",
37- "static/lib/backbone/backbone.js",
38- "static/lib/cleditor/jquery.cleditor.js",
39- "static/lib/py.js/lib/py.js",
40- "static/lib/select2/select2.js",
41- "static/lib/moment.js",
42- "static/src/js/openerpframework.js",
43- "static/src/js/boot.js",
44- "static/src/js/testing.js",
45- "static/src/js/pyeval.js",
46- "static/src/js/core.js",
47- "static/src/js/formats.js",
48- "static/src/js/chrome.js",
49- "static/src/js/views.js",
50- "static/src/js/data.js",
51- "static/src/js/data_export.js",
52- "static/src/js/search.js",
53- "static/src/js/view_list.js",
54- "static/src/js/view_form.js",
55- "static/src/js/view_list_editable.js",
56- "static/src/js/view_tree.js",
57- ],
58- 'css' : [
59- "static/lib/jquery.ui.bootstrap/css/custom-theme/jquery-ui-1.9.0.custom.css",
60- "static/lib/jquery.ui.timepicker/css/jquery-ui-timepicker-addon.css",
61- "static/lib/jquery.ui.notify/css/ui.notify.css",
62- "static/lib/jquery.textext/jquery.textext.css",
63- "static/lib/fontawesome/css/font-awesome.css",
64- "static/lib/bootstrap/css/bootstrap.css",
65- "static/lib/select2/select2.css",
66- "static/src/css/base.css",
67- "static/src/css/data_export.css",
68- "static/lib/cleditor/jquery.cleditor.css",
69- ],
70->>>>>>> MERGE-SOURCE
71 'qweb' : [
72 "static/src/xml/*.xml",
73 ],
74
75=== modified file 'addons/web/static/src/js/pyeval.js'
76--- addons/web/static/src/js/pyeval.js 2014-01-28 13:37:06 +0000
77+++ addons/web/static/src/js/pyeval.js 2014-06-30 14:00:02 +0000
78@@ -546,102 +546,125 @@
79 return py.PY_call(py.PY_getAttr(d, 'strftime'), [args.format]);
80 });
81
82- var args = _.map(('year month day hour minute second microsecond '
83- + 'years months weeks days hours minutes secondes microseconds '
84- + 'weekday leakdays yearday nlyearday').split(' '), function (arg) {
85- return [arg, null];
86- });
87- args.unshift('*');
88 var relativedelta = py.type('relativedelta', null, {
89- __init__: function () {
90- this.ops = py.PY_parseArgs(arguments, args);
91- },
92- __add__: function (other) {
93- if (!py.PY_isInstance(other, datetime.date)) {
94- return py.NotImplemented;
95- }
96- // TODO: test this whole mess
97- var year = asJS(this.ops.year) || asJS(other.year);
98- if (asJS(this.ops.years)) {
99- year += asJS(this.ops.years);
100- }
101-
102- var month = asJS(this.ops.month) || asJS(other.month);
103- if (asJS(this.ops.months)) {
104- month += asJS(this.ops.months);
105- // FIXME: no divmod in JS?
106- while (month < 1) {
107- year -= 1;
108- month += 12;
109- }
110- while (month > 12) {
111- year += 1;
112- month -= 12;
113- }
114- }
115-
116- var lastMonthDay = new Date(year, month, 0).getDate();
117- var day = asJS(this.ops.day) || asJS(other.day);
118- if (day > lastMonthDay) { day = lastMonthDay; }
119- var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
120- if (days_offset) {
121- day = new Date(year, month-1, day + days_offset).getDate();
122- }
123- // TODO: leapdays?
124- // TODO: hours, minutes, seconds? Not used in XML domains
125- // TODO: weekday?
126- // FIXME: use date.replace
127- return py.PY_call(datetime.date, [
128- py.float.fromJSON(year),
129- py.float.fromJSON(month),
130- py.float.fromJSON(day)
131- ]);
132- },
133- __radd__: function (other) {
134- return this.__add__(other);
135- },
136-
137- __sub__: function (other) {
138- if (!py.PY_isInstance(other, datetime.date)) {
139- return py.NotImplemented;
140- }
141- // TODO: test this whole mess
142- var year = asJS(this.ops.year) || asJS(other.year);
143- if (asJS(this.ops.years)) {
144- year -= asJS(this.ops.years);
145- }
146-
147- var month = asJS(this.ops.month) || asJS(other.month);
148- if (asJS(this.ops.months)) {
149- month -= asJS(this.ops.months);
150- // FIXME: no divmod in JS?
151- while (month < 1) {
152- year -= 1;
153- month += 12;
154- }
155- while (month > 12) {
156- year += 1;
157- month -= 12;
158- }
159- }
160-
161- var lastMonthDay = new Date(year, month, 0).getDate();
162- var day = asJS(this.ops.day) || asJS(other.day);
163- if (day > lastMonthDay) { day = lastMonthDay; }
164- var days_offset = ((asJS(this.ops.weeks) || 0) * 7) + (asJS(this.ops.days) || 0);
165- if (days_offset) {
166- day = new Date(year, month-1, day - days_offset).getDate();
167- }
168- // TODO: leapdays?
169- // TODO: hours, minutes, seconds? Not used in XML domains
170- // TODO: weekday?
171- return py.PY_call(datetime.date, [
172- py.float.fromJSON(year),
173- py.float.fromJSON(month),
174- py.float.fromJSON(day)
175- ]);
176- },
177- __rsub__: function (other) {
178+ __init__: function() {
179+ this.ops = py.PY_parseArgs(
180+ arguments,
181+ [
182+ ['year', null],
183+ ['years', null],
184+ ['month', null],
185+ ['months', null],
186+ ['day', null],
187+ ['days', null],
188+ ['hour', null],
189+ ['hours', null],
190+ ['minute', null],
191+ ['minutes', null],
192+ ['second', null],
193+ ['seconds', null],
194+ ['weeks', null],
195+ ['weekday', null],
196+ ]);
197+ },
198+ __add__: function(other) {
199+ if(!py.PY_isInstance(other, datetime.date) &&
200+ !py.PY_isInstance(other, datetime.datetime)) {
201+ return py.NotImplemented;
202+ }
203+
204+ var result = moment({
205+ year: other.year,
206+ //january==0 in moment.js
207+ month: other.month - 1,
208+ day: other.day,
209+ hour: other.hour,
210+ minute: other.minute,
211+ second: other.second});
212+
213+ if(this.ops.year) {
214+ result.year(Math.abs(this.ops.year._value));
215+ }
216+ if(this.ops.years) {
217+ result.add('years', this.ops.years._value);
218+ }
219+ if(this.ops.month) {
220+ //january==0 in moment.js
221+ result.month(Math.abs(this.ops.month._value % 13) - 1);
222+ }
223+ if(this.ops.months) {
224+ result.add('months', this.ops.months._value);
225+ }
226+ if(this.ops.day) {
227+ result = result.clone()
228+ .endOf('month')
229+ .hours(result.hours())
230+ .minutes(result.minutes())
231+ .seconds(result.seconds())
232+ .max(result.clone()
233+ .date(Math.abs(this.ops.day._value)));
234+ }
235+ if(this.ops.days) {
236+ result.add('days', this.ops.days._value)
237+ }
238+ if(this.ops.weeks) {
239+ result.add('days', this.ops.weeks._value * 7);
240+ }
241+ if(this.ops.hour) {
242+ result.hour(Math.abs(this.ops.hour._value % 24));
243+ }
244+ if(this.ops.hours) {
245+ result.add('hours', this.ops.hours._value);
246+ }
247+ if(this.ops.minute) {
248+ result.minute(Math.abs(this.ops.minute._value % 60));
249+ }
250+ if(this.ops.minutes) {
251+ result.add('minutes', this.ops.minutes._value);
252+ }
253+ if(this.ops.second) {
254+ result.second(Math.abs(this.ops.second._value % 60));
255+ }
256+ if(this.ops.seconds) {
257+ result.add('seconds', this.ops.seconds._value);
258+ }
259+ if(this.ops.weekday) {
260+ //in relativedelta, 0=MO, but in iso, 1=MO
261+ var isoWeekday = Math.abs(this.ops.weekday._value || 1) /
262+ (this.ops.weekday._value || 1) *
263+ (Math.abs(this.ops.weekday._value) + 1),
264+ originalIsoWeekday = result.isoWeekday();
265+ result.isoWeekday(isoWeekday).add(
266+ 'weeks', isoWeekday < originalIsoWeekday ? 1 : 0);
267+ }
268+
269+ var args = [
270+ result.year(),
271+ //january==0 in moment.js
272+ result.month() + 1,
273+ result.date(),
274+ ];
275+ if(py.PY_isInstance(other, datetime.datetime)) {
276+ args.push(result.hour());
277+ args.push(result.minute());
278+ args.push(result.second());
279+ }
280+
281+ return py.PY_call(Object.getPrototypeOf(other), args);
282+ },
283+ __radd__: function(other) {
284+ return this.__add__(other);
285+ },
286+ __sub__: function(other) {
287+ _.each(this.ops, function(op, name) {
288+ if(!op || name == 'weekday') {
289+ return;
290+ }
291+ op._value = -op._value;
292+ });
293+ return this.__add__(other);
294+ },
295+ __rsub__: function(other) {
296 return this.__sub__(other);
297 }
298 });
299
300=== modified file 'addons/web/views/webclient_templates.xml'
301--- addons/web/views/webclient_templates.xml 2014-05-12 15:18:12 +0000
302+++ addons/web/views/webclient_templates.xml 2014-06-30 14:00:02 +0000
303@@ -106,6 +106,9 @@
304 <script type="text/javascript" src="/web/static/src/js/view_tree.js"></script>
305 <script type="text/javascript" src="/base/static/src/js/apps.js"></script>
306
307+ <!-- Momentjs -->
308+ <script type="text/javascript" src="/web/static/lib/moment.js"></script>
309+
310 </template>
311
312 <template id="web.assets_webclient_manifest">