diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index bdedb85e63d..085e48d89e0 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -346,12 +346,17 @@ class DataSet(openerpweb.Controller): :rtype: list """ Model = request.session.model(model) + ids = Model.search(domain or [], offset or 0, limit or False, sort or False, request.context) + if fields and fields == ['id']: # shortcut read if we only want the ids return map(lambda id: {'id': id}, ids) - return Model.read(ids, fields or False, request.context) + + reads = Model.read(ids, fields or False, request.context) + reads.sort(key=lambda obj: ids.index(obj['id'])) + return reads @openerpweb.jsonrequest def get(self, request, model, ids, fields=False): @@ -409,6 +414,11 @@ class DataSet(openerpweb.Controller): r = getattr(m, method)(ids, *args) return {'result': r} + @openerpweb.jsonrequest + def exec_workflow(self, req, model, id, signal): + r = req.session.exec_workflow(model, id, signal) + return {'result': r} + @openerpweb.jsonrequest def default_get(self, req, model, fields, context={}): m = req.session.model(model) @@ -551,11 +561,11 @@ class ListView(View): @openerpweb.jsonrequest def fill(self, request, model, id, domain, - offset=0, limit=False): - return self.do_fill(request, model, id, domain, offset, limit) + offset=0, limit=False, sort=None): + return self.do_fill(request, model, id, domain, offset, limit, sort) def do_fill(self, request, model, id, domain, - offset=0, limit=False): + offset=0, limit=False, sort=None): """ Returns all information needed to fill a table: * view with processed ``editable`` flag @@ -573,19 +583,33 @@ class ListView(View): :param int limit: search limit, for pagination :returns: hell if I have any idea yet """ - view = self.fields_view_get(request, model, id) + view = self.fields_view_get(request, model, id, toolbar=True) + print sort rows = DataSet().do_search_read(request, model, offset=offset, limit=limit, - domain=domain) + domain=domain, sort=sort) eval_context = request.session.evaluation_context( request.context) - return [ - {'data': dict((key, {'value': value}) - for key, value in row.iteritems()), - 'color': self.process_colors(view, row, eval_context)} - for row in rows - ] + + if sort: + sort_criteria = sort.split(',')[0].split(' ') + print sort, sort_criteria + view['sorted'] = { + 'field': sort_criteria[0], + 'reversed': sort_criteria[1] == 'DESC' + } + else: + view['sorted'] = {} + return { + 'view': view, + 'records': [ + {'data': dict((key, {'value': value}) + for key, value in row.iteritems()), + 'color': self.process_colors(view, row, eval_context)} + for row in rows + ] + } def process_colors(self, view, row, context): colors = view['arch']['attrs'].get('colors') @@ -618,5 +642,12 @@ class Action(openerpweb.Controller): _cp_path = "/base/action" @openerpweb.jsonrequest - def load(self, req, action_id): - return {} + def load(self, req, action_id, context={}): + Actions = req.session.model('ir.actions.actions') + value = False + action_type = Actions.read([action_id], ['type'], context) + if action_type: + action = req.session.model(action_type[0]['type']).read([action_id], False, context) + if action: + value = action[0] + return {'result': value} diff --git a/addons/base/static/lib/datejs/date-en-US.js b/addons/base/static/lib/datejs/date-en-US.js new file mode 100644 index 00000000000..59005e2097a --- /dev/null +++ b/addons/base/static/lib/datejs/date-en-US.js @@ -0,0 +1,145 @@ +/** + * @version: 1.0 Alpha-1 + * @author: Coolite Inc. http://www.coolite.com/ + * @date: 2008-05-13 + * @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved. + * @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/. + * @website: http://www.datejs.com/ + */ +Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]}; +(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,p=function(s,l){if(!l){l=2;} +return("000"+s).slice(l*-1);};$P.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};$P.setTimeToNow=function(){var n=new Date();this.setHours(n.getHours());this.setMinutes(n.getMinutes());this.setSeconds(n.getSeconds());this.setMilliseconds(n.getMilliseconds());return this;};$D.today=function(){return new Date().clearTime();};$D.compare=function(date1,date2){if(isNaN(date1)||isNaN(date2)){throw new Error(date1+" - "+date2);}else if(date1 instanceof Date&&date2 instanceof Date){return(date1date2)?1:0;}else{throw new TypeError(date1+" - "+date2);}};$D.equals=function(date1,date2){return(date1.compareTo(date2)===0);};$D.getDayNumberFromName=function(name){var n=$C.dayNames,m=$C.abbreviatedDayNames,o=$C.shortestDayNames,s=name.toLowerCase();for(var i=0;i=start.getTime()&&this.getTime()<=end.getTime();};$P.isAfter=function(date){return this.compareTo(date||new Date())===1;};$P.isBefore=function(date){return(this.compareTo(date||new Date())===-1);};$P.isToday=function(){return this.isSameDay(new Date());};$P.isSameDay=function(date){return this.clone().clearTime().equals(date.clone().clearTime());};$P.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};$P.addSeconds=function(value){return this.addMilliseconds(value*1000);};$P.addMinutes=function(value){return this.addMilliseconds(value*60000);};$P.addHours=function(value){return this.addMilliseconds(value*3600000);};$P.addDays=function(value){this.setDate(this.getDate()+value);return this;};$P.addWeeks=function(value){return this.addDays(value*7);};$P.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,$D.getDaysInMonth(this.getFullYear(),this.getMonth())));return this;};$P.addYears=function(value){return this.addMonths(value*12);};$P.add=function(config){if(typeof config=="number"){this._orient=config;return this;} +var x=config;if(x.milliseconds){this.addMilliseconds(x.milliseconds);} +if(x.seconds){this.addSeconds(x.seconds);} +if(x.minutes){this.addMinutes(x.minutes);} +if(x.hours){this.addHours(x.hours);} +if(x.weeks){this.addWeeks(x.weeks);} +if(x.months){this.addMonths(x.months);} +if(x.years){this.addYears(x.years);} +if(x.days){this.addDays(x.days);} +return this;};var $y,$m,$d;$P.getWeek=function(){var a,b,c,d,e,f,g,n,s,w;$y=(!$y)?this.getFullYear():$y;$m=(!$m)?this.getMonth()+1:$m;$d=(!$d)?this.getDate():$d;if($m<=2){a=$y-1;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=0;f=$d-1+(31*($m-1));}else{a=$y;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=s+1;f=$d+((153*($m-3)+2)/5)+58+s;} +g=(a+b)%7;d=(f+g-e)%7;n=(f+3-d)|0;if(n<0){w=53-((g-s)/5|0);}else if(n>364+s){w=1;}else{w=(n/7|0)+1;} +$y=$m=$d=null;return w;};$P.getISOWeek=function(){$y=this.getUTCFullYear();$m=this.getUTCMonth()+1;$d=this.getUTCDate();return p(this.getWeek());};$P.setWeek=function(n){return this.moveToDayOfWeek(1).addWeeks(n-this.getWeek());};$D._validate=function(n,min,max,name){if(typeof n=="undefined"){return false;}else if(typeof n!="number"){throw new TypeError(n+" is not a Number.");}else if(nmax){throw new RangeError(n+" is not a valid value for "+name+".");} +return true;};$D.validateMillisecond=function(value){return $D._validate(value,0,999,"millisecond");};$D.validateSecond=function(value){return $D._validate(value,0,59,"second");};$D.validateMinute=function(value){return $D._validate(value,0,59,"minute");};$D.validateHour=function(value){return $D._validate(value,0,23,"hour");};$D.validateDay=function(value,year,month){return $D._validate(value,1,$D.getDaysInMonth(year,month),"day");};$D.validateMonth=function(value){return $D._validate(value,0,11,"month");};$D.validateYear=function(value){return $D._validate(value,0,9999,"year");};$P.set=function(config){if($D.validateMillisecond(config.millisecond)){this.addMilliseconds(config.millisecond-this.getMilliseconds());} +if($D.validateSecond(config.second)){this.addSeconds(config.second-this.getSeconds());} +if($D.validateMinute(config.minute)){this.addMinutes(config.minute-this.getMinutes());} +if($D.validateHour(config.hour)){this.addHours(config.hour-this.getHours());} +if($D.validateMonth(config.month)){this.addMonths(config.month-this.getMonth());} +if($D.validateYear(config.year)){this.addYears(config.year-this.getFullYear());} +if($D.validateDay(config.day,this.getFullYear(),this.getMonth())){this.addDays(config.day-this.getDate());} +if(config.timezone){this.setTimezone(config.timezone);} +if(config.timezoneOffset){this.setTimezoneOffset(config.timezoneOffset);} +if(config.week&&$D._validate(config.week,0,53,"week")){this.setWeek(config.week);} +return this;};$P.moveToFirstDayOfMonth=function(){return this.set({day:1});};$P.moveToLastDayOfMonth=function(){return this.set({day:$D.getDaysInMonth(this.getFullYear(),this.getMonth())});};$P.moveToNthOccurrence=function(dayOfWeek,occurrence){var shift=0;if(occurrence>0){shift=occurrence-1;} +else if(occurrence===-1){this.moveToLastDayOfMonth();if(this.getDay()!==dayOfWeek){this.moveToDayOfWeek(dayOfWeek,-1);} +return this;} +return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek,+1).addWeeks(shift);};$P.moveToDayOfWeek=function(dayOfWeek,orient){var diff=(dayOfWeek-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};$P.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};$P.getOrdinalNumber=function(){return Math.ceil((this.clone().clearTime()-new Date(this.getFullYear(),0,1))/86400000)+1;};$P.getTimezone=function(){return $D.getTimezoneAbbreviation(this.getUTCOffset());};$P.setTimezoneOffset=function(offset){var here=this.getTimezoneOffset(),there=Number(offset)*-6/10;return this.addMinutes(there-here);};$P.setTimezone=function(offset){return this.setTimezoneOffset($D.getTimezoneOffset(offset));};$P.hasDaylightSavingTime=function(){return(Date.today().set({month:0,day:1}).getTimezoneOffset()!==Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.isDaylightSavingTime=function(){return(this.hasDaylightSavingTime()&&new Date().getTimezoneOffset()===Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r.charAt(0)+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};$P.getElapsed=function(date){return(date||new Date())-this;};if(!$P.toISOString){$P.toISOString=function(){function f(n){return n<10?'0'+n:n;} +return'"'+this.getUTCFullYear()+'-'+ +f(this.getUTCMonth()+1)+'-'+ +f(this.getUTCDate())+'T'+ +f(this.getUTCHours())+':'+ +f(this.getUTCMinutes())+':'+ +f(this.getUTCSeconds())+'Z"';};} +$P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}} +var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");} +x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}()); +(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());} +return false;};$P.at=function(time){return(typeof time==="string")?$D.parse(this.toString("d")+" "+time):this.set(time);};$N.fromNow=$N.after=function(date){var c={};c[this._dateElement]=this;return((!date)?new Date():date.clone()).add(c);};$N.ago=$N.before=function(date){var c={};c[this._dateElement]=this*-1;return((!date)?new Date():date.clone()).add(c);};var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),pxf=("Milliseconds Seconds Minutes Hours Date Week Month FullYear").split(/\s/),nth=("final first second third fourth fifth").split(/\s/),de;$P.toObject=function(){var o={};for(var i=0;itemp){throw new RangeError($D.getDayName(n)+" does not occur "+ntemp+" times in the month of "+$D.getMonthName(temp.getMonth())+" "+temp.getFullYear()+".");} +return this;} +return this.moveToDayOfWeek(n,this._orient);};};var sdf=function(n){return function(){var t=$D.today(),shift=n-t.getDay();if(n===0&&$C.firstDayOfWeek===1&&t.getDay()!==0){shift=shift+7;} +return t.addDays(shift);};};for(var i=0;i-1;m--){v=px[m].toLowerCase();if(o1[v]!=o2[v]){return false;} +if(k==v){break;}} +return true;} +if(j.substring(j.length-1)!="s"){j+="s";} +return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;} +if(!last&&q[1].length===0){last=true;} +if(!last){var qx=[];for(var j=0;j0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}} +if(rx[1].length1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];} +if(args){for(var i=0,px=args.shift();i2)?n:(n+(((n+2000)<$C.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];for(var i=0;i$D.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");} +var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});} +return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;} +for(var i=0;i + + + + + + + + + +Please, check your console for results + + + + diff --git a/addons/base/static/lib/qweb/qweb-benchmark.xml b/addons/base/static/lib/qweb/qweb-benchmark.xml new file mode 100644 index 00000000000..71452572e51 --- /dev/null +++ b/addons/base/static/lib/qweb/qweb-benchmark.xml @@ -0,0 +1,60 @@ + + + +
+
+ x +

title

+

text

+
+
+ x + +

title

+

text

+
+
+ + d.iter = 'one,two,three,four,five'.split(',') + + + + + + + + 1 + true +
+
+ + height: 200px; border: 1px solid red; +
+ + * : + + Ok this is good ! + Hi there ! + [] + + + () + +
+
+
+ x +

title

+

text

+
+
+ + +
+ x + +

Here's your value : () !!

+
+
+ + diff --git a/addons/base/static/lib/qweb/qweb-test-foreach.xml b/addons/base/static/lib/qweb/qweb-test-foreach.xml index fa037d72fc2..80749882aaf 100644 --- a/addons/base/static/lib/qweb/qweb-test-foreach.xml +++ b/addons/base/static/lib/qweb/qweb-test-foreach.xml @@ -1,40 +1,17 @@ - - * - - - - - - - + * + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - - + + diff --git a/addons/base/static/lib/qweb/qweb-test-trim.xml b/addons/base/static/lib/qweb/qweb-test-trim.xml deleted file mode 100644 index 964aa2733c5..00000000000 --- a/addons/base/static/lib/qweb/qweb-test-trim.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - ok - - ok - ok - ok - - o k - - - - - - [ ] - - - - diff --git a/addons/base/static/lib/qweb/qweb-test.js.html b/addons/base/static/lib/qweb/qweb-test.js.html index a7742cc5142..b39527f0c13 100644 --- a/addons/base/static/lib/qweb/qweb-test.js.html +++ b/addons/base/static/lib/qweb/qweb-test.js.html @@ -5,9 +5,10 @@ - + + + @@ -16,7 +19,9 @@ + + @@ -32,6 +37,7 @@ + diff --git a/addons/base_dashboard/__init__.py b/addons/base_dashboard/__init__.py new file mode 100644 index 00000000000..355939a4922 --- /dev/null +++ b/addons/base_dashboard/__init__.py @@ -0,0 +1 @@ +import controllers \ No newline at end of file diff --git a/addons/base_dashboard/__openerp__.py b/addons/base_dashboard/__openerp__.py new file mode 100644 index 00000000000..a5230cda40b --- /dev/null +++ b/addons/base_dashboard/__openerp__.py @@ -0,0 +1,11 @@ +{ + "name": "Base Dashboard", + "version": "2.0", + "depends": ['base'], + "js": [ + 'static/lib/jquery.dashboard/js/jquery.dashboard.js', + 'static/src/js/dashboard.js' + ], + "css": ['static/lib/jquery.dashboard/css/dashboardui.css'], + 'active': True +} diff --git a/addons/base_dashboard/controllers/__init__.py b/addons/base_dashboard/controllers/__init__.py new file mode 100644 index 00000000000..039d9715fab --- /dev/null +++ b/addons/base_dashboard/controllers/__init__.py @@ -0,0 +1 @@ +import main \ No newline at end of file diff --git a/addons/base_dashboard/controllers/main.py b/addons/base_dashboard/controllers/main.py new file mode 100644 index 00000000000..a223deda6be --- /dev/null +++ b/addons/base_dashboard/controllers/main.py @@ -0,0 +1,17 @@ +from base.controllers.main import View, clean_action +import openerpweb + +class Dashboard(View): + _cp_path = "/base_dashboard/dashboard" + + @openerpweb.jsonrequest + def load(self, req, node_attrs): + + action_id = int(node_attrs['name']) + actions = req.session.model('ir.actions.actions') + result = actions.read([action_id],['type'], req.session.context) + if not result: + raise _('Action not found!') + action = req.session.model(result[0]['type']).read([action_id], False, req.session.context)[0] + clean_action(action, req.session) + return {'action': action} \ No newline at end of file diff --git a/addons/base_dashboard/static/data.html b/addons/base_dashboard/static/data.html new file mode 100644 index 00000000000..d533476cc83 --- /dev/null +++ b/addons/base_dashboard/static/data.html @@ -0,0 +1 @@ +content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 content 4 \ No newline at end of file diff --git a/addons/base_dashboard/static/lib/jquery.dashboard/css/dashboardui.css b/addons/base_dashboard/static/lib/jquery.dashboard/css/dashboardui.css new file mode 100644 index 00000000000..0bdc5ec9153 --- /dev/null +++ b/addons/base_dashboard/static/lib/jquery.dashboard/css/dashboardui.css @@ -0,0 +1,261 @@ +.ui-icon { + cursor:default; +} +.widgetheader { + cursor:move; +} + +#layout-dialog .selected { + background: no-repeat scroll 0px -51px transparent; +} +#layout-dialog .layoutchoice { + width: 82px; + height: 51px; + float:left; + list-style-type:none; + margin:5px; + padding:0; +} +.selectedcolumn { + border: 3px dashed #aaaaaa; +} +.emptycolumn { + font-size:20px; + font-weight:bold; + color: #aaaaaa; + padding: 5px 5px 5px 5px; +} + +.dashboard { + margin-top: 5px; +} +.right { + float:right; +} +.widgetheader { + padding:2px 2px 5px 5px; +} +.ui-icon { + float:left; +} +.widgetcontent { + padding:2px 2px 5px 5px; +} +.widget { + margin-bottom:10px; +} +.column { + float:left; + margin:0 1% -1.5em; + padding:0; + width:47.5%; +} + +.layout-a .column { +width:98%; +} +.layout-a .column.second, .layout-a .column.third { +display:none; +} +.layout-aa .column { +width:47.5%; +} +.layout-aa .third { +display:none; +} +.layout-ba .column { +width:68%; +} +.layout-ba .first { +width:27%; +} +.layout-ba .third { +display:none; +} +.layout-ab .column { +width:27%; +} +.layout-ab .first { +width:68%; +} +.layout-ab .third { +display:none; +} +.layout-aaa .column { + width:30.9%; +} + +/*body { + margin:0; + color:#333333; + font:12px/1.4 arial,FreeSans,Helvetica,sans-serif; +}*/ + +.headerlink { + color:#ffffff; +} + +.headerlinks { + text-align:right; + margin-right:20px; + line-height:24px; + text-decoration:underline; + font-weight:bold; +} + +.headerbox { + height:148px; +} + +.ui-widget-overlay { + opacity:0.5; +} + +/*.loading { + padding: 50px; + text-align: center; + background-image:loading.gif; +}*/ + + +#layout-dialog ul { + margin:0; + padding:0; +} + +#layout-dialog ul li a, #layout-dialog ul li a:link, #layout-dialog ul li a:visited { + border:1px solid #BBBBBB; + display:block; + float:left; + margin:0 1em 1em 0; + outline:medium none; + padding:0.35em; + width:auto; +} + +.hidden { + display:none; +} + +.controls { + border:1px solid #BBBBBB; + float:none; + margin:0; + padding:4px 0; +} + +.controls { + width:100px; + background:none repeat scroll 0 0 #dddddd; + border:1px solid #6A8EB3; + color:#000000; + margin-top:-1px; + padding:4px 0; + position:absolute; + right:0; + z-index:2003; +} + +.controls li { + float:none; + margin:0; + padding:0; + list-style-type:none; + margin:0 0 0 0.2em; + width:auto; + position:static; +} + +.controls li a { + color:#000000; + font-weight:normal; + float:none; + margin:0; + text-decoration:none; + width:auto; + padding-left:5px; +} + +.hiddenmenu { + position:relative; +} + +.dialog .categories { + list-style:none outside none; + height: 414px; +} + +.dialog .categories li.selected button { + color:#FFFFFF; + font-weight:800; +} +.dialog .categories li button { + background:none repeat scroll 0 0 transparent; + border:medium none; + color:#666666; + font-family:"segoe ui",helvetica,arial,sans-serif; + font-size:0.8em; + padding:0.4em 1.2em; + text-align:left; + width:100%; +} +ul.categories button { + cursor:pointer; +} + +.widgetitem { + border:2px none white; + float:left; + font-size:0.77em; + height:142px; + margin:0; + overflow:hidden; + padding:0 20px 0 142px; + width:152px; +} + +ol.widgets { + float:left; + list-style:none outside none; + margin:-10px; + padding:0; + width:auto; +} + +.dialog .panel-body { + overflow:auto; + padding:10px; +} + +.dialog .categories { + background:none repeat scroll 0 0 #FFFFFF; + border-right:1px solid #F0F0F0; + float:left; + height:100%; + list-style:none outside none; + margin:0 1.17em 0 0; + padding:10px 0 0; + width:25%; +} + +.dialog .categories li.selected { + background:none repeat scroll 0 0 #6699CC; + color:#FFFFFF; +} + +.widgetitem .add-button { + float:left; + margin:81px 0 0 -131px; + width:auto; +} + +.widgetitem h3 { + margin:11px 0 0; + padding:0; +} + +.widgetitem img { + border:1px solid #999999; + float:left; + margin:10px 0 0 -132px; +} diff --git a/addons/base_dashboard/static/lib/jquery.dashboard/js/jquery.dashboard.js b/addons/base_dashboard/static/lib/jquery.dashboard/js/jquery.dashboard.js new file mode 100644 index 00000000000..5f6afc7ce7d --- /dev/null +++ b/addons/base_dashboard/static/lib/jquery.dashboard/js/jquery.dashboard.js @@ -0,0 +1,936 @@ +/* + * dashboard 1.0 + * http://www.gxdeveloperweb.com/dashboard/ + * + * Copyright (c) 2010 Mark Machielsen + * + * Dual licensed under the MIT and GPL licenses (same as jQuery): + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +(function($) { // Create closure. + + // Constructor for dashboard object. + $.fn.dashboard = function(options) { + // Public properties of dashboard. + var dashboard = {}; + var loading; + var widgetDirectoryUrl; + dashboard.layout; + dashboard.element = this; + dashboard.id = this.attr("id"); + dashboard.widgets = {}; + dashboard.widgetsToAdd = {}; + dashboard.widgetCategories = {}; + dashboard.initialized = false; + + // Public methods + dashboard.serialize = function() { + dashboard.log('entering serialize function',1); + var r = '{"layout": "' + dashboard.layout.id + '", "data" : ['; + // add al widgets in the right order + var i=0; + if ($('.' + opts.columnClass).length == 0) dashboard.log(opts.columnClass + ' class not found',5); + $('.' + opts.columnClass).each(function() { + $(this).children().each(function() { + if ($(this).hasClass(opts.widgetClass)) { + if (i > 0) { r+= ','; } + r+= (dashboard.getWidget($(this).attr("id"))).serialize(); + i++; + } + }); + }); + r+= ']}'; + return r; + } + + dashboard.log = function(msg, level) { + if (level >= opts.debuglevel && typeof console != 'undefined') { + var l = ''; + if (level == 1) l = 'INFO'; + if (level == 2) l = 'EVENT'; + if (level == 3) l = 'WARNING'; + if (level == 5) l = 'ERROR'; + console.log(l + ' - ' + msg); + } + } + + dashboard.setLayout = function(layout) { + if (layout != null) { + dashboard.log('entering setLayout function with layout ' + layout.id,1); + } else { + dashboard.log('entering setLayout function with layout null',1); + } + dashboard.layout = layout; + + loading.remove(); + if (dashboard.layout != null) { + if (typeof opts.layoutClass != 'undefined') { + this.element.find('.' + opts.layoutClass).addClass(dashboard.layout.classname); + } else { + this.element.html(dashboard.layout.html); + } + } + + // make the columns sortable, see http://jqueryui.com/demos/sortable/ for explaination + $('.' + opts.columnClass).sortable({ + connectWith: $('.' + opts.columnClass), + opacity: opts.opacity, + handle: '.' + opts.widgetHeaderClass, + over: function(event, ui) { + $(this).addClass("selectedcolumn"); + }, + out: function(event, ui) { + $(this).removeClass("selectedcolumn"); + }, + receive: function(event, ui) { + // update the column attribute for the widget + var w = dashboard.getWidget(ui.item.attr("id")); + w.column = getColumnIdentifier($(this).attr("class")); + + dashboard.log('dashboardStateChange event thrown for widget ' + w.id,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetMoved","widget":w}); + + dashboard.log('widgetDropped event thrown for widget ' + w.id,2); + w.element.trigger("widgetDropped",{"widget":w}); + }, + deactivate: function(event, ui) { + // This event is called for each column + dashboard.log('Widget is dropped: check if the column is now empty.',1); + var childLength = $(this).children().length; + if (childLength == 0) { + dashboard.log('adding the empty text to the column',1); + $(this).html('
' + opts.emptyColumnHtml + '
'); + } else { + if (childLength == 2) { + // remove the empty column HTML + $(this).find('.emptycolumn').remove(); + } + } + }, + start: function(event, ui) { + ui.item.find('.' + opts.widgetTitleClass).addClass('noclick'); + }, + stop: function(event, ui) { + //sorting changed (within one list) + setTimeout(function(){ + ui.item.find('.' + opts.widgetTitleClass).removeClass('noclick'); + }, 300); + } + + }); + + fixSortableColumns(); + + // trigger the dashboardLayoutLoaded event + dashboard.log('dashboardLayoutLoaded event thrown',2); + dashboard.element.trigger("dashboardLayoutLoaded"); + } + + // This is a workaround for the following problem: when I drag a widget from column2 to column1, sometimes the widget is + // moved to column3, which is not visible + function fixSortableColumns() { + dashboard.log('entering fixSortableColumns function',1); + $('.nonsortablecolumn').removeClass('nonsortablecolumn').addClass(opts.columnClass); + $('.' + opts.columnClass).filter(function() {return $(this).css("display") == 'none'}).addClass('nonsortablecolumn').removeClass(opts.columnClass); + } + + function getColumnIdentifier(classes) { + dashboard.log('entering getColumnIdentifier function',1); + var r; + var s = classes.split(" "); + for (var i = 0;i < s.length;i++) { + if (s[i].indexOf(opts.columnPrefix) === 0) { r = s[i] }; + }; + return r.replace(opts.columnPrefix,''); + } + + dashboard.loadLayout = function() { + dashboard.log('entering loadLayout function',1); + if (typeof opts.json_data.url != 'undefined' && opts.json_data.url.length > 0) { + // ajax option + dashboard.log('Getting JSON feed : ' + opts.json_data.url,1); + $.getJSON(opts.json_data.url, function(json) { + if (json == null) { + alert('Unable to get json. If you are using chrome: there is an issue with loading json with local files. It works on a server :-)',5); + return; + } + // set the layout + var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(json.layout); + dashboard.setLayout(currentLayout); + dashboard.loadWidgets(json.data); + }); + } else { + // set the layout + var currentLayout = (typeof dashboard.layout != 'undefined') ? dashboard.layout : getLayout(opts.json_data.layout); + dashboard.setLayout(currentLayout); + dashboard.loadWidgets(opts.json_data.data); + } + }; + + dashboard.addWidget = function(obj, column) { + dashboard.log('entering addWidget function',1); + // add the widget to the column + var wid = obj.id; + + // check if the widget is already registered and available in the dom + if (typeof dashboard.widgets[wid] != 'undefined' && $('#' + wid).length > 0) { + var wi = $('#' + wid); + column = dashboard.widgets[wid].column; + + // add it to the column + wi.appendTo(column); + + } else { + // build the widget + dashboard.log('Applying template : ' + opts.widgetTemplate,1); + if ($('#' + opts.widgetTemplate).length == 0) dashboard.log('Template "' + opts.widgetTemplate + ' not found',5); + var widgetStr = tmpl($('#' + opts.widgetTemplate).html(), obj); + var wi = $(widgetStr); + + // add it to the column + wi.appendTo(column); + + if(column.find('.emptycolumn').length) + column.find('.emptycolumn').remove(); + + dashboard.widgets[wid] = widget({ + id: wid, + element: wi, + column: obj.column, + url: (typeof obj.url != 'undefined' ? obj.url : null), + editurl: obj.editurl, + title: obj.title, + open: obj.open, + metadata: obj.metadata + }); + } + + dashboard.log('widgetAdded event thrown for widget ' + wid,2); + dashboard.widgets[wid].element.trigger("widgetAdded", {"widget":dashboard.widgets[wid]}); + + if (dashboard.initialized) { + dashboard.log('dashboardStateChange event thrown for widget ' + wid,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetAdded","widget":wi}); + } + } + + dashboard.loadWidgets = function(data) { + dashboard.log('entering loadWidgets function',1); + dashboard.element.find('.' + opts.columnClass).empty(); + + + // This is for the manual feed + $(data).each(function() { + var column = this.column; + dashboard.addWidget(this, dashboard.element.find('.' + opts.columnPrefix + column)); + }); // end loop for widgets + + // check if there are widgets in the temp dashboard which needs to be moved + // this is not the correct place, but otherwise we are too late + + // check if there are still widgets in the temp + $('#tempdashboard').find('.' + opts.widgetClass).each(function() { + // append it to the first column + var firstCol = dashboard.element.find('.' + opts.columnClass + ':first'); + $(this).appendTo(firstCol); + + // set the new column + dashboard.getWidget($(this).attr("id")).column = firstCol.attr("id"); + }); + $('#tempdashboard').remove(); + + // add the text to the empty columns + $('.' + opts.columnClass).each(function() { + if ($(this).children().length == 0) { + $(this).html('
' + opts.emptyColumnHtml + '
'); + } + }); + + dashboard.initialized = true; + }; + + dashboard.init = function() { + dashboard.log('entering init function',1); + // load the widgets as fast as we can. After that add the binding + dashboard.loadLayout(); + } + + dashboard.getWidget = function(id) { + dashboard.log('entering getWidget function',1); + var wi = dashboard.widgets[id]; + if (typeof wi != 'undefined') { + return wi; + } else { + return null; + } + } + + + // Merge in the caller's options with the defaults. + var opts = $.extend({}, $.fn.dashboard.defaults, options); + var addOpts = $.extend({}, $.fn.dashboard.defaults.addWidgetSettings, options.addWidgetSettings); + var layoutOpts = $.extend({}, $.fn.dashboard.defaults.editLayoutSettings, options.editLayoutSettings); + + // Execution 'forks' here and restarts in init(). Tell the user we're busy with a loading. + var loading = $(opts.loadingHtml).appendTo(dashboard.element); + + /** + * widget object + * Private sub-class of dashboard + * Constructor starts + */ + function widget(widget) { + + dashboard.log('entering widget constructor',1); + // Merge default options with the options defined for this widget. + widget = $.extend({}, $.fn.dashboard.widget.defaults, widget); + + // public functions + widget.openContent = function() { + // hide the open link, show the close link + widget.element.find('.widgetOpen').hide(); + widget.element.find('.widgetClose').show(); + + dashboard.log('entering openContent function',1); + widget.open = true; + if (!widget.loaded) { + // load the content in the widget if the state == open + if (this.url != '' && this.url != null && typeof this.url != 'undefined') { + // add the loading + $(opts.loadingHtml).appendTo(widget.element.find('.' + opts.widgetContentClass)); + + dashboard.log('widgetShow event thrown for widget ' + widget.id,2); + widget.element.trigger("widgetShow", {"widget":widget}); + + widget.element.find('.' + opts.widgetContentClass).load(this.url, function(response, status, xhr) { + if (status == "error") { + widget.element.find('.' + opts.widgetContentClass).html(opts.widgetNotFoundHtml); + } + widget.loaded = true; + dashboard.log('widgetLoaded event thrown for widget ' + widget.id,2); + widget.element.trigger("widgetLoaded", {"widget":widget}); + }); + } else { + dashboard.log('widgetShow event thrown for widget ' + widget.id,2); + widget.element.trigger("widgetShow", {"widget":widget}); + + dashboard.log('widgetLoaded event thrown',2); + widget.element.trigger("widgetLoaded", {"widget":widget}); + } + } else { + dashboard.log('widgetShow event thrown for widget ' + widget.id,2); + widget.element.trigger("widgetShow", {"widget":widget}); + } + if (dashboard.initialized) { + dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetOpened","widget":widget}); + } + }; + widget.refreshContent = function() { + dashboard.log('entering refreshContent function',1); + widget.loaded = false; + if (widget.open) { + widget.openContent(); + } + } + widget.setTitle = function(newTitle){ + dashboard.log('entering setTitle function',1); + widget.title=newTitle; + widget.element.find('.' + opts.widgetTitleClass).html(newTitle); + if (dashboard.initialized) { + dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"titleChanged","widget":widget}); + } + } + widget.closeContent = function() { + dashboard.log('entering closeContent function',1); + widget.open = false; + + dashboard.log('widgetHide event thrown for widget ' + widget.id,2); + widget.element.trigger("widgetHide", {"widget":widget}); + + // show the open link, hide the close link + widget.element.find('.widgetOpen').show(); + widget.element.find('.widgetClose').hide(); + + dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetClosed","widget":widget}); + }; + widget.addMetadataValue = function(name, value) { + dashboard.log('entering addMetadataValue function',1); + if (typeof widget.metadata == 'undefined') { + widget.metadata = {}; + } + widget.metadata[name] = value; + dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"metadataChanged","widget":widget}); + }; + widget.openMenu = function() { + dashboard.log('entering openMenu function',1); + widget.element.find('.' + opts.menuClass).show(); + }; + widget.closeMenu = function() { + dashboard.log('entering closeMenu function',1); + widget.element.find('.' + opts.menuClass).hide(); + }; + widget.remove = function() { + dashboard.log('entering remove function',1); + widget.element.remove(); + dashboard.log('widgetDeleted event thrown for widget ' + widget.id,2); + widget.element.trigger('widgetDeleted', {"widget":widget}); + + dashboard.log('dashboardStateChange event thrown for widget ' + widget.id,2); + dashboard.element.trigger("dashboardStateChange",{"stateChange":"widgetRemoved","widget":widget}); + }; + widget.serialize = function() { + dashboard.log('entering serialize function',1); + var r = '{"title" : "' + widget.title + '", "id" : "' + widget.id + '", "column" : "' + widget.column + '","editurl" : "' + widget.editurl + '","open" : ' + widget.open + ',"url" : "' + widget.url + '"'; + + if (typeof widget.metadata != 'undefined') { + r+= ',"metadata":{' + var obj = widget.metadata; + var i=0; + for(var item in obj) { + if (i > 0) { r+= ',' }; + + // FIXME: support for more than string, eg numbers subobjects + r+= '"' + item + '":"' + obj[item] + '"'; + i++; + } + r+= '}' + + } + + r += '}'; + return r; + }; + widget.openFullscreen = function() { + dashboard.log('entering openFullscreen function',1); + widget.fullscreen = true; + + // create a clone + var clone = widget.element.clone(); + + // move the dashboard + var temp = $(''); + temp.appendTo($("body")); + dashboard.element.children().appendTo(temp); + + // add the clone to the dashboard + var fs = $('
    '); + fs.appendTo(dashboard.element); + clone.appendTo(fs); + + }; + widget.closeFullscreen = function() { + dashboard.log('entering closeFullscreen function',1); + widget.fullscreen = false; + + // remove the fullscreen + $('#fullscreen_' + dashboard.id).remove(); + + // move the content from the tempdashboard back + $('#tempdashboard_' + dashboard.id).children().appendTo(dashboard.element); + $('#tempdashboard_' + dashboard.id).remove(); + + }; + widget.openSettings = function() { + dashboard.log('entering openSettings function',1); + widget.element.find('.' + opts.widgetContentClass).load(widget.editurl); + }; + + // called when widget is initialized + if (widget.open) { + widget.openContent(); + } + + widget.initialized = true; + + dashboard.log('widgetInitialized event thrown',2); + widget.element.trigger("widgetInitialized", {"widget":widget}); + + return widget; + }; + + + // FIXME: can this be done easier?? + function getLayout(id) { + dashboard.log('entering getLayout function',1); + var r = null; + var first = null; + if (typeof opts.layouts != 'undefined') { + + $.each(opts.layouts,function(i, item) { + if (i == 0) { first = item; } + if (item.id == id) { + r = item; + } + }); + } + if (r == null) { r = first } + return r; + } + + + $('#' + dashboard.id + ' .menutrigger').live('click', function() { + dashboard.log('widgetOpenMenu event thrown for widget ' + widget.id,2); + var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id")); + + wi.element.trigger('widgetOpenMenu', {"widget":wi}); + return false; + }); + + // add event handlers to the menu + $('#' + dashboard.id + ' .' + opts.widgetFullScreenClass).live('click',function(e) { + // close the menu + dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2); + var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id")); + wi.element.trigger('widgetCloseMenu', {"widget":wi}); + + if (wi.fullscreen) { + dashboard.log('widgetCloseFullScreen event thrown for widget ' + wi.id,2); + wi.element.trigger('widgetCloseFullScreen', {"widget":wi}); + } else { + dashboard.log('widgetOpenFullScreen event thrown for widget ' + wi.id,2); + wi.element.trigger('widgetOpenFullScreen', {"widget":wi}); + } + return false; + }); + + $('#' + dashboard.id + ' .controls li').live('click',function(e) { + // close the menu + dashboard.log('widgetCloseMenu event thrown for widget ' + widget.id,2); + + var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id")); + wi.element.trigger('widgetCloseMenu', {"widget":wi}); + + // use the class on the li to determine what action to trigger + dashboard.log($(this).attr('class') + ' event thrown for widget ' + widget.id,2); + + var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id")); + wi.element.trigger($(this).attr('class'), {"widget":wi}); + return false; + }); + + // add the menu events (by default triggers are connected in dashboard_jsonfeed) + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetCloseMenu',function(e,o) { + dashboard.log("Closing menu " + $(this).attr("id"),1); + o.widget.closeMenu(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenMenu',function(e,o) { + dashboard.log("Opening menu " + $(this).attr("id"),1); + o.widget.openMenu(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetDelete',function(e,o) { + if (confirm(opts.deleteConfirmMessage)) { + dashboard.log("Removing widget " + $(this).attr("id"),1); + o.widget.remove(); + } + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetRefresh',function(e,o) { + o.widget.refreshContent(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetSetTitle',function(event, o) { + o.widget.setTitle(o.title); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetClose',function(e,o) { + dashboard.log("Closing widget " + $(this).attr("id"),1); + o.widget.closeContent(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpen',function(e,o) { + dashboard.log("Opening widget " + $(this).attr("id"),1); + o.widget.openContent(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetShow',function() { + $(this).find('.' + opts.widgetContentClass).show(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetHide',function() { + $(this).find('.' + opts.widgetContentClass).hide(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetAddMetadataValue',function(e,o) { + dashboard.log("Changing metadata for widget " + $(this).attr("id") + ", metadata name: " + o.name + ", value: " + o.value, 1); + o.widget.addMetadataValue(o.name, o.value); + }); + + // Define a toggle event when clicking at the header + $('#' + dashboard.id + ' .' + opts.widgetTitleClass).live('click',function(e) { + dashboard.log("Click on the header detected for widget " + $(this).attr("id"),1); + if (!$(this).hasClass('noclick')) { + var wi = dashboard.getWidget($(this).closest('.' + opts.widgetClass).attr("id")); + if (wi.open) { + dashboard.log('widgetClose event thrown for widget ' + wi,2); + wi.element.trigger('widgetClose', {"widget":wi}); + } else { + dashboard.log('widgetOpen event thrown for widget ' + wi,2); + wi.element.trigger('widgetOpen', {"widget":wi}); + } + } + return false; + }); + + $('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseover',function () { + $(this).find('.' + opts.iconsClass).removeClass("hidden"); + }); + + $('#' + dashboard.id + ' .' + opts.widgetHeaderClass).live('mouseout', function () { + $(this).find('.' + opts.iconsClass).addClass("hidden"); + }); + + $('body').click(function() { + $('.' + opts.menuClass).hide(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetOpenFullScreen',function(e,o) { + o.widget.openFullscreen(); + }); + + $('.' + opts.widgetClass).live('widgetCloseFullScreen',function(e,o) { + o.widget.closeFullscreen(); + }); + + $('#' + dashboard.id + ' .' + opts.widgetClass).live('widgetEdit',function(e,o) { + o.widget.openSettings(); + }); + + if ($('#' + addOpts.dialogId).length == 0) dashboard.log('Unable to find ' + addOpts.dialogId,5); + $('#' + addOpts.dialogId).dialog({ + autoOpen: false, + height: 414, + width: 550, + modal: true, + buttons: { + Cancel: function() { + $(this).dialog('close'); + } + }, + close: function() { + //close + } + }); + + if ($('#' + layoutOpts.dialogId).length == 0) dashboard.log('Unable to find ' + layoutOpts.dialogId,5); + $('#' + layoutOpts.dialogId).dialog({ + autoOpen: false, + height: 300, + width: 600, + modal: true + }); + + $('.' + layoutOpts.openDialogClass).live('click', function(){ + dashboard.log('dashboardOpenLayoutDialog event thrown',2); + dashboard.element.trigger("dashboardOpenLayoutDialog"); + return false; + }); + + dashboard.element.live('dashboardOpenLayoutDialog', function(){ + dashboard.log('Opening dialog ' + layoutOpts.dialogId,1); + $('#' + layoutOpts.dialogId).dialog('open'); + + // add the layout images + var h = $('#' + layoutOpts.dialogId).find('.' + layoutOpts.layoutClass); + h.empty(); + if (h.children().length == 0) { + dashboard.log('Number of layouts : ' + opts.layouts.length,1); + $.each(opts.layouts,function(i, item) { + dashboard.log('Applying template : ' + layoutOpts.layoutTemplate,1); + if ($('#' + layoutOpts.layoutTemplate).length == 0) dashboard.log('Template "' + layoutOpts.layoutTemplate + ' not found',5); + h.append(tmpl($('#' + layoutOpts.layoutTemplate).html(), item)); + }); + } + + // set the selected class for the selected layout + $('.' + layoutOpts.selectLayoutClass).removeClass(layoutOpts.selectedLayoutClass); + $('#' + dashboard.layout.id).addClass(layoutOpts.selectedLayoutClass); + + bindSelectLayout(); + }); + + + dashboard.element.live('dashboardStateChange', function(){ + if (typeof opts.stateChangeUrl != 'undefined' && opts.stateChangeUrl != null && opts.stateChangeUrl != '') { + $.ajax({type: 'POST', + url: opts.stateChangeUrl, + data: { dashboard: dashboard.element.attr("id"), settings: dashboard.serialize() }, + success: function(data){ + if (data == "NOK" || data.indexOf('NOK') != -1){ + dashboard.log('dashboardSaveFailed event thrown',2); + dashboard.element.trigger("dashboardSaveFailed"); + } else { + dashboard.log('dashboardSuccessfulSaved event thrown',2); + dashboard.element.trigger("dashboardSuccessfulSaved"); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown){ + dashboard.log('dashboardSaveFailed event thrown',2); + dashboard.element.trigger("dashboardSaveFailed"); + }, + dataType: "text" + }); + } + }); + + + dashboard.element.live('dashboardCloseLayoutDialog', function(){ + // close the dialog + $('#' + layoutOpts.dialogId).dialog('close'); + }); + + // FIXME: why doesn't the live construct work in this case + function bindSelectLayout() { + if ($('.' + layoutOpts.selectLayoutClass).length == 0) dashboard.log('Unable to find ' + layoutOpts.selectLayoutClass,5); + $('.' + layoutOpts.selectLayoutClass).bind('click', function(e){ + var currentLayout = dashboard.layout; + + dashboard.log('dashboardCloseLayoutDialog event thrown',2); + dashboard.element.trigger('dashboardCloseLayoutDialog'); + + // Now set the new layout + var newLayout = getLayout($(this).attr("id")); + dashboard.layout = newLayout; + + // remove the class of the old layout + if (typeof opts.layoutClass != 'undefined') { + dashboard.element.find('.' + opts.layoutClass).removeClass(currentLayout.classname).addClass(newLayout.classname); + + fixSortableColumns(); + + // check if there are widgets in hidden columns, move them to the first column + if ($('.' + opts.columnClass).length == 0) dashboard.log('Unable to find ' + opts.columnClass,5); + dashboard.element.find('.' + opts.columnClass).each(function() { + if ($(this).css("display") == "none") { + // move the widgets to the first column + $(this).children().appendTo(dashboard.element.find('.' + opts.columnClass + ':first')); + } + + $('.emptycolumn').remove(); + // add the text to the empty columns + $('.' + opts.columnClass).each(function() { + if ($(this).children().length == 0) { + $(this).html('
    ' + opts.emptyColumnHtml + '
    '); + } + }); + + + }); + + } else { + // set the new layout, but first move the dashboard to a temp + var temp = $(''); + temp.appendTo($("body")); + + dashboard.element.children().appendTo(temp); + + // reload the dashboard + dashboard.init(); + } + + // throw an event upon changing the layout. + dashboard.log('dashboardChangeLayout event thrown',2); + dashboard.element.trigger('dashboardLayoutChanged'); + + }); + return false; + } + + $('.' + addOpts.selectCategoryClass).live('click', function(){ + dashboard.log('addWidgetDialogSelectCategory event thrown',2); + dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$(this)}); + return false; + }); + + dashboard.element.live('addWidgetDialogSelectCategory', function(e, obj){ + // remove the category selection + $('.' + addOpts.selectCategoryClass).removeClass(addOpts.selectedCategoryClass); + + // empty the widgets div + $('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty(); + + // select the category + $(obj.category).addClass(addOpts.selectedCategoryClass); + + // get the widgets + url = dashboard.widgetCategories[$(obj.category).attr("id")]; + + dashboard.log('Getting JSON feed : ' + url,1); + $.getJSON(url, {"cache":true}, function(json) { + // load the widgets from the category + if (json.data == 0) dashboard.log('Empty data returned',3); + $.each(json.data, function(i,item){ + dashboard.widgetsToAdd[item.id] = item; + + dashboard.log('Applying template : ' + addOpts.widgetTemplate,1); + if ($('#' + addOpts.widgetTemplate).length == 0) dashboard.log('Template "' + addOpts.widgetTemplate + ' not found',5); + var html = tmpl($('#' + addOpts.widgetTemplate).html(), item); + $('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).append(html); + }); + }); + + dashboard.log('addWidgetDialogWidgetsLoaded event thrown',2); + dashboard.element.trigger('addWidgetDialogWidgetsLoaded'); + }); + + + $('.' + addOpts.addWidgetClass).live('click', function(){ + var widget = dashboard.widgetsToAdd[$(this).attr("id").replace('addwidget','')]; + dashboard.log('dashboardAddWidget event thrown',2); + dashboard.element.trigger('dashboardAddWidget', {"widget":widget}); + + dashboard.log('dashboardCloseWidgetDialog event thrown',2); + dashboard.element.trigger('dashboardCloseWidgetDialog'); + return false; + }); + + $('.' + addOpts.openDialogClass).live('click', function(){ + dashboard.log('dashboardOpenWidgetDialog event thrown',2); + dashboard.element.trigger('dashboardOpenWidgetDialog'); + return false; + }); + + dashboard.element.live('dashboardCloseWidgetDialog', function(){ + // close the dialog + $('#' + addOpts.dialogId).dialog('close'); + }); + + dashboard.element.live('dashboardOpenWidgetDialog', function(){ + + //remove existing categories/widgets from the DOM, to prevent duplications + $('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).empty(); + $('#' + addOpts.dialogId).find('.' + addOpts.widgetClass).empty(); + + dashboard.log('Opening dialog ' + addOpts.dialogId,1); + $('#' + addOpts.dialogId).dialog('open'); + + dashboard.log('Getting JSON feed : ' + addOpts.widgetDirectoryUrl,1); + $.getJSON(addOpts.widgetDirectoryUrl, function(json) { + if (json.category == 0) dashboard.log('Empty data returned',3); + $.each(json.category, function(i,item){ + // Add the categories to the dashboard + dashboard.widgetCategories[item.id] = item.url; + + dashboard.log('Applying template : ' + addOpts.categoryTemplate,1); + if ($('#' + addOpts.categoryTemplate).length == 0) dashboard.log('Template "' + addOpts.categoryTemplate + ' not found',5); + var html = tmpl($('#' + addOpts.categoryTemplate).html(),item); + $('#' + addOpts.dialogId).find('.' + addOpts.categoryClass).append(html); + }); + dashboard.log('addWidgetDialogCategoriesLoaded event thrown',2); + dashboard.element.trigger('addWidgetDialogCategoriesLoaded'); + + dashboard.log('addWidgetDialogSelectCategory event thrown',2); + dashboard.element.trigger('addWidgetDialogSelectCategory', {"category":$('#' + addOpts.dialogId).find('.' + addOpts.categoryClass + '>li:first')}); + + }); + + }); + + return dashboard; + }; + + + // Public static properties of dashboard. Default settings. + $.fn.dashboard.defaults = { + debuglevel:3, + json_data: {}, + loadingHtml: '
    Loading, please wait

    Loading...

    ', + emptyColumnHtml: 'Drag your widgets here', + widgetTemplate: 'widgettemplate', + columnPrefix: 'column-', + opacity:"0.2", + deleteConfirmMessage: "Are you sure you want to delete this widget?", + widgetNotFoundHtml: "The content of this widget is not available anymore. You may remove this widget.", + columnClass: 'column', + widgetClass: 'widget', + menuClass: 'controls', + widgetContentClass: 'widgetcontent', + widgetTitleClass: 'widgettitle', + widgetHeaderClass: 'widgetheader', + widgetFullScreenClass: 'widgetopenfullscreen', + iconsClass: 'icons', + stateChangeUrl: '', + + addWidgetSettings: { + openDialogClass: 'openaddwidgetdialog', + addWidgetClass: 'addwidget', + selectCategoryClass: 'selectcategory', + selectedCategoryClass: 'selected', + categoryClass: 'categories', + widgetClass: 'widgets', + + dialogId: 'addwidgetdialog', + + categoryTemplate: 'categorytemplate', + widgetTemplate: 'addwidgettemplate' + }, + editLayoutSettings: { + dialogId: 'editLayout', + layoutClass: 'layoutselection', + selectLayoutClass: 'layoutchoice', + selectedLayoutClass: 'selected', + openDialogClass: 'editlayout', + layoutTemplate: 'selectlayouttemplate' + } + + + }; + + // Default widget settings. + $.fn.dashboard.widget = { + defaults: { + open: true, + fullscreen: false, + loaded: false, + url: '', + metadata: {} + } + }; + +})(jQuery); // end of closure + + +// Simple JavaScript Templating +// John Resig - http://ejohn.org/ - MIT Licensed +(function(){ + var cache = {}; + + this.tmpl = function tmpl(str, data){ + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + + var fn = !/\W/.test(str) ? + cache[str] = cache[str] || + tmpl(document.getElementById(str).innerHTML) : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');"); + + // Provide some basic currying to the user + return data ? fn( data ) : fn; + }; +})(); \ No newline at end of file diff --git a/addons/base_dashboard/static/src/dashboard_template.html b/addons/base_dashboard/static/src/dashboard_template.html new file mode 100644 index 00000000000..f78f8b861a8 --- /dev/null +++ b/addons/base_dashboard/static/src/dashboard_template.html @@ -0,0 +1,85 @@ + + + + + + + + + +
    +
      +
    + +
    +
      +
    +
    +
    + + +
    +
    +

    Choose dashboard layout

    +
      +
    +
    +
    \ No newline at end of file diff --git a/addons/base_dashboard/static/src/js/dashboard.js b/addons/base_dashboard/static/src/js/dashboard.js new file mode 100644 index 00000000000..34d09f80e62 --- /dev/null +++ b/addons/base_dashboard/static/src/js/dashboard.js @@ -0,0 +1,104 @@ +openerp.base_dashboard = function(openerp){ + +QWeb.add_template('/base_dashboard/static/src/xml/base_dashboard.xml'); + +openerp.base.form.Board = openerp.base.form.Widget.extend({ + init: function(view, node) { + + this._super(view, node); + this.template = "Board"; + }, + start: function() { + this._super.apply(this, arguments); + this.$element.html(QWeb.render(this.template)); + }, + + render: function() { + var self = this; + jQuery('body').append( + jQuery('
    ', {'id': 'dashboard_template'}).load('/base_dashboard/static/src/dashboard_template.html',self.on_loaded).hide() + ) + }, + + on_loaded: function() { + var children = this.node.children; + var board = jQuery('#dashboard').dashboard({ + layoutClass:'layout' + }); + board.init(); + for(var ch = 0; ch < children.length; ch++) { + var ch_widgets = children[ch].children; + for(var chld = 0; chld < ch_widgets.length; chld++) { + var widget_type = ch_widgets[chld].tag; + var child_index = widget_type == 'action' ? chld : ch; + var widget = new (openerp.base.form.widgets.get_object(widget_type)) (this.view, ch_widgets[chld], board, child_index); + widget.start(); + } + } + } +}); + +openerp.base.form.Action = openerp.base.form.Widget.extend({ + init: function(view, node, board, child_index) { + this._super(view, node, board, child_index); + this.board = board; + this.child_index = child_index; + }, + start: function() { + this._super.apply(this, arguments); + this.rpc('/base_dashboard/dashboard/load',{ + node_attrs: this.node.attrs + }, + this.on_load_action); + }, + + on_load_action: function(result) { + var action = result.action; + + action.search_view = false; + action.no_sidebar = true; + action.search_view_id = false; + + var node_attrs = this.node.attrs; + var get_column = ['first', 'second', 'third']; + var board_element = this.board.element.find('[id=column-'+get_column[this.child_index]+']'); + + this.board.addWidget({ + 'id': node_attrs.name, + 'title': node_attrs.string, + }, board_element); + + var content_id = node_attrs.name+'-widgetcontent'; + this.board.getWidget(node_attrs.name).element.find('.widgetcontent').attr('id',content_id) + + action_manager = new openerp.base.ActionManager(this.session, content_id); + action_manager.start(); + this.board.getWidget(node_attrs.name).url = action_manager.do_action(action); + } +}) + +openerp.base.form.Vpaned = openerp.base.form.Widget.extend({ + init: function(view, node, board, child_index) { + + this._super(view, node, board, child_index); + this.board = board; + this.child_index = child_index; + }, + start: function() { + this._super.apply(this, arguments); + var children = this.node.children; + for(var chld=0; chld + +
    + +
    +
    +
    +
    +
    +
    +
    + \ No newline at end of file diff --git a/doc/source/development.rst b/doc/source/development.rst index a1d989b46ac..514e094b689 100644 --- a/doc/source/development.rst +++ b/doc/source/development.rst @@ -55,7 +55,7 @@ Events Event triggered after a user asked for a search. The search view fires this event after collecting all input data (contexts, domains and group_by contexts). Note that the search view does *not* merge - those (or therwise evaluate them), they are returned as provided by + those (or otherwise evaluate them), they are returned as provided by the various inputs within the view. ``on_clear`` @@ -71,7 +71,7 @@ An important concept in the search view is that of input. It is both an informal protocol and an abstract type that can be inherited from. Inputs are widgets which can contain user data (a char widget for -instance, or a selection box). They are able of action and of +instance, or a selection box). They are capable of action and of reaction: .. _views-search-registration: @@ -93,9 +93,9 @@ reaction: * Return a context (an object), this is the "normal" response if the input holds a value. - * Return a falsy value (generally ``null``). This value indicates - the input does not contain any value and will not take part in the - research. + * Return a value that evaluates as false (generally ``null``). This + value indicates the input does not contain any value and will not + affect the results of the search. * Raise :js:class:`openerp.base.search.Invalid` to indicate that it holds a value but this value can not be used in the search @@ -104,7 +104,7 @@ reaction: the search process. :js:class:`~openerp.base.search.Invalid` takes three mandatory - arguments: an indentifier (a name for instance), the invalid value + arguments: an identifier (a name for instance), the invalid value, and a validation message indicating the issue. ``get_domain`` @@ -114,11 +114,11 @@ reaction: The :js:class:`openerp.base.search.Input` type implements registration on its own, but its implementations of ``get_context`` and -``get_domain`` simply raise errors and *have* to be overridden. +``get_domain`` simply raise errors and *must* be overridden. One last action is for filters, as an activation order has to be kept -on them for some controls (establish the correct grouping sequence for -instance). +on them for some controls (to establish the correct grouping sequence, +for instance). To that end, filters can call :js:func:`openerp.base.Search.do_toggle_filter`, providing themselves @@ -137,7 +137,7 @@ Life cycle The search view has a pretty simple and linear life cycle, in three main steps: -:js:class:`init ` +:js:class:`~openerp.base.SearchView.init` Nothing interesting happens here @@ -211,7 +211,7 @@ called in this order: Gives the widget the opportunity to unbind its events, remove itself from the DOM and perform any other cleanup task it may have. - Event if the widget does not do anything itself, it is responsible + Even if the widget does not do anything itself, it is responsible for shutting down its children. An abstract type is available and can be inherited from, to simplify @@ -232,7 +232,8 @@ abstract types, used to implement input widgets: `. If inherited from, descendant classes should not call its - implementations of ``get_context`` and ``get_domain``. + implementations of :js:func:`~openerp.base.search.Input.get_context` + and :js:func:`~openerp.base.search.Input.get_domain`. * :js:class:`openerp.base.search.Field` is used to implement more "field" widgets (which allow the user to input potentially complex @@ -251,7 +252,7 @@ abstract types, used to implement input widgets: :js:func:`~openerp.base.search.Widget.make_id`. * It sets up a basic (overridable) - :js:attr:`~opererp.base.search.Field.template` attribute, combined + :js:attr:`~openerp.base.search.Field.template` attribute, combined with the previous tasks, this makes subclasses of :js:class:`~openerp.base.search.Field` render themselves "for free". diff --git a/doc/source/project.rst b/doc/source/project.rst index 6f9a06b2f0d..faa15da2163 100644 --- a/doc/source/project.rst +++ b/doc/source/project.rst @@ -31,7 +31,7 @@ Guides and main documentation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The meat and most important part of all documentation. Should be -written in plain english, using reStructuredText_ and taking advantage +written in plain English, using reStructuredText_ and taking advantage of `Sphinx's extensions`_, especially `cross-references`_. Python API Documentation @@ -65,7 +65,7 @@ RST, using Sphinx's `Python domain`_ [#]_: and ``__new__``), using the `info fields`_ as well. * Attributes (class and instance) should be documented in their - class's docstrin via the ``.. attribute::`` directiveg, following + class's docstring via the ``.. attribute::`` directive, following the class's own documentation. * The relation between modules and module-level attributes is similar: @@ -220,7 +220,7 @@ Roadmap Release notes +++++++++++++ -.. [#] because Python is the default domain, the ``py:`` markup prefix +.. [#] Because Python is the default domain, the ``py:`` markup prefix is optional and should be left out. .. [#] Resig's Class still uses prototypes under the hood, it doesn't diff --git a/openerpweb/dates.py b/openerpweb/dates.py new file mode 100644 index 00000000000..14313fd4a46 --- /dev/null +++ b/openerpweb/dates.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2009 Tiny SPRL (). +# Copyright (C) 2010 OpenERP s.a. (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +""" We assume that, just like the OpenERP server, the python part of the client + doesn't need to handle timezone-aware datetimes, this could be changed in the future. """ + +import datetime + +DEFAULT_SERVER_DATE_FORMAT = "%Y-%m-%d" +DEFAULT_SERVER_TIME_FORMAT = "%H:%M:%S" +DEFAULT_SERVER_DATETIME_FORMAT = "%s %s" % ( + DEFAULT_SERVER_DATE_FORMAT, + DEFAULT_SERVER_TIME_FORMAT) + +def parse_datetime(str): + if not str: + return str + return datetime.datetime.strptime(str, DEFAULT_SERVER_DATETIME_FORMAT) + +def parse_date(str): + if not str: + return str + return datetime.datetime.strptime(str, DEFAULT_SERVER_DATE_FORMAT).date() + +def parse_time(str): + if not str: + return str + return datetime.datetime.strptime(str, DEFAULT_SERVER_TIME_FORMAT).time() + +def format_datetime(obj): + if not obj: + return False + return obj.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + +def format_date(obj): + if not obj: + return False + return obj.strftime(DEFAULT_SERVER_DATE_FORMAT) + +def format_time(obj): + if not obj: + return False + return obj.strftime(DEFAULT_SERVER_TIME_FORMAT) diff --git a/openerpweb/nonliterals.py b/openerpweb/nonliterals.py index a0805c9c0c2..1584628f777 100644 --- a/openerpweb/nonliterals.py +++ b/openerpweb/nonliterals.py @@ -80,10 +80,6 @@ class Domain(object): def get_domain_string(self): """ Retrieves the domain string linked to this non-literal domain in the provided session. - - :param session: the OpenERP Session used to store the domain string in - the first place. - :type session: openerpweb.openerpweb.OpenERPSession """ return self.session.domains_store[self.key] @@ -129,10 +125,6 @@ class Context(object): def get_context_string(self): """ Retrieves the context string linked to this non-literal context in the provided session. - - :param session: the OpenERP Session used to store the context string in - the first place. - :type session: openerpweb.openerpweb.OpenERPSession """ return self.session.contexts_store[self.key] diff --git a/openerpweb/openerpweb.py b/openerpweb/openerpweb.py index 8423dc878ab..19d6ec1eeb4 100644 --- a/openerpweb/openerpweb.py +++ b/openerpweb/openerpweb.py @@ -10,14 +10,12 @@ import time import traceback import uuid import xmlrpclib -import pytz import cherrypy import cherrypy.lib.static import simplejson import nonliterals -import xmlrpctimeout import logging #----------------------------------------------------------- @@ -94,7 +92,7 @@ class OpenERPSession(object): self.client_timezone = False def proxy(self, service): - s = xmlrpctimeout.TimeoutServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service), timeout=5) + s = xmlrpclib.ServerProxy('http://%s:%s/xmlrpc/%s' % (self._server, self._port, service)) return s def bind(self, db, uid, password): @@ -116,6 +114,12 @@ class OpenERPSession(object): r = self.proxy('object').execute(self._db, self._uid, self._password, model, func, *l, **d) return r + def exec_workflow(self, model, id, signal): + if not (self._db and self._uid and self._password): + raise OpenERPUnboundException() + r = self.proxy('object').exec_workflow(self._db, self._uid, self._password, model, signal, id) + return r + def model(self, model): """ Get an RPC proxy for the object ``model``, bound to this session. @@ -136,8 +140,9 @@ class OpenERPSession(object): self.context = self.model('res.users').context_get(self.context) self.client_timezone = self.context.get("tz", False) - if self.client_timezone: - self.remote_timezone = self.execute('common', 'timezone_get') + # invalid code, anyway we decided the server will be in UTC + #if self.client_timezone: + # self.remote_timezone = self.execute('common', 'timezone_get') self._locale = self.context.get('lang','en_US') lang_ids = self.execute('res.lang','search', [('code', '=', self._locale)]) @@ -289,13 +294,34 @@ class JsonRequest(object): * the json string is passed as a form parameter named "request" * method is currently ignored - Sucessful request: - --> {"jsonrpc": "2.0", "method": "call", "params": {"session_id": "SID", "context": {}, "arg1": "val1" }, "id": null} - <-- {"jsonrpc": "2.0", "result": { "res1": "val1" }, "id": null} + Sucessful request:: - Request producing a error: - --> {"jsonrpc": "2.0", "method": "call", "params": {"session_id": "SID", "context": {}, "arg1": "val1" }, "id": null} - <-- {"jsonrpc": "2.0", "error": {"code": 1, "message": "End user error message.", "data": {"code": "codestring", "debug": "traceback" } }, "id": null} + --> {"jsonrpc": "2.0", + "method": "call", + "params": {"session_id": "SID", + "context": {}, + "arg1": "val1" }, + "id": null} + + <-- {"jsonrpc": "2.0", + "result": { "res1": "val1" }, + "id": null} + + Request producing a error:: + + --> {"jsonrpc": "2.0", + "method": "call", + "params": {"session_id": "SID", + "context": {}, + "arg1": "val1" }, + "id": null} + + <-- {"jsonrpc": "2.0", + "error": {"code": 1, + "message": "End user error message.", + "data": {"code": "codestring", + "debug": "traceback" } }, + "id": null} """ @@ -471,6 +497,9 @@ class Root(object): default.exposed = True def main(argv): + # change the timezone of the program to the OpenERP server's assumed timezone + os.environ["TZ"] = "UTC" + # Parse config op = optparse.OptionParser() op.add_option("-p", "--port", dest="socket_port", help="listening port", metavar="NUMBER", default=8002) diff --git a/openerpweb/xmlrpctimeout.py b/openerpweb/xmlrpctimeout.py deleted file mode 100644 index 28f9bcd41bd..00000000000 --- a/openerpweb/xmlrpctimeout.py +++ /dev/null @@ -1,30 +0,0 @@ -import xmlrpclib -import httplib - -class TimeoutHTTPConnection(httplib.HTTPConnection): - def connect(self): - httplib.HTTPConnection.connect(self) - self.sock.settimeout(self.timeout) - -class TimeoutHTTP(httplib.HTTP): - _connection_class = TimeoutHTTPConnection - def set_timeout(self, timeout): - self._conn.timeout = timeout - -class TimeoutTransport(xmlrpclib.Transport): - def __init__(self, timeout=10, *l, **kw): - xmlrpclib.Transport.__init__(self,*l,**kw) - self.timeout=timeout - def make_connection(self, host): - conn = TimeoutHTTP(host) - conn.set_timeout(self.timeout) - return conn - -class TimeoutServerProxy(xmlrpclib.ServerProxy): - def __init__(self,uri,timeout=10,*l,**kw): - kw['transport']=TimeoutTransport(timeout=timeout, use_datetime=kw.get('use_datetime',0)) - xmlrpclib.ServerProxy.__init__(self,uri,*l,**kw) - -if __name__ == "__main__": - s=TimeoutServerProxy('http://127.0.0.1:9090',timeout=2) - s.dummy()