diff --git a/addons/web/common/http.py b/addons/web/common/http.py index 96af5afe65e..4bdd87a6764 100644 --- a/addons/web/common/http.py +++ b/addons/web/common/http.py @@ -6,11 +6,12 @@ import ast import contextlib import functools import logging -import urllib import os import pprint import sys +import threading import traceback +import urllib import uuid import xmlrpclib @@ -128,17 +129,37 @@ class JsonRequest(WebRequest): "id": null} """ - - def dispatch(self, controller, method, requestf=None, request=None): - """ Calls the method asked for by the JSON-RPC2 request + def dispatch(self, controller, method): + """ Calls the method asked for by the JSON-RPC2 or JSONP request :param controller: the instance of the controller which received the request :param method: the method which received the request - :param requestf: a file-like object containing an encoded JSON-RPC2 request - :param request: a JSON-RPC2 request - :returns: an utf8 encoded JSON-RPC2 reply + :returns: an utf8 encoded JSON-RPC2 or JSONP reply """ + args = self.httprequest.args + jsonp = args.get('jsonp', False) + requestf = None + request = None + + if jsonp and self.httprequest.method == 'POST': + # jsonp 2 steps step1 POST: save call + self.init(args) + req.session.jsonp_requests[args.get('id')] = self.httprequest.form['r'] + headers=[('Content-Type', 'text/plain; charset=utf-8')] + r = werkzeug.wrappers.Response(request_id, headers=headers) + return r + elif jsonp and args.get('id'): + # jsonp 2 steps step2 GET: run and return result + self.init(args) + request = self.session.jsonp_requests.pop(args.get(id), "") + elif jsonp and args.get('r'): + # jsonp method GET + request = args.get('r') + else: + # regular jsonrpc2 + requestf = self.httprequest.stream + response = {"jsonrpc": "2.0" } error = None try: @@ -188,10 +209,16 @@ class JsonRequest(WebRequest): if _logger.isEnabledFor(logging.DEBUG): _logger.debug("<--\n%s", pprint.pformat(response)) - content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder) - return werkzeug.wrappers.Response( - content, headers=[('Content-Type', 'application/json'), - ('Content-Length', len(content))]) + + if jsonp: + mime = 'application/javascript' + body = "%s(%s);" % (jsonp, simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder),) + else: + mime = 'application/json' + body = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder) + + r = werkzeug.wrappers.Response(body, headers=[('Content-Type', mime), ('Content-Length', len(body))]) + return r def jsonrequest(f): """ Decorator marking the decorated method as being a handler for a @@ -205,8 +232,7 @@ def jsonrequest(f): """ @functools.wraps(f) def json_handler(controller, request, config): - return JsonRequest(request, config).dispatch( - controller, f, requestf=request.stream) + return JsonRequest(request, config).dispatch(controller, f) json_handler.exposed = True return json_handler @@ -281,17 +307,19 @@ STORES = {} @contextlib.contextmanager def session_context(request, storage_path, session_cookie='sessionid'): - session_store = STORES.get(storage_path) + session_store, session_lock = STORES.get(storage_path, (None, None)) if not session_store: session_store = werkzeug.contrib.sessions.FilesystemSessionStore( storage_path) - STORES[storage_path] = session_store + session_lock = threading.Lock() + STORES[storage_path] = session_store, session_lock sid = request.cookies.get(session_cookie) - if sid: - request.session = session_store.get(sid) - else: - request.session = session_store.new() + with session_lock: + if sid: + request.session = session_store.get(sid) + else: + request.session = session_store.new() try: yield request.session @@ -300,32 +328,42 @@ def session_context(request, storage_path, session_cookie='sessionid'): # either by login process or by HTTP requests without an OpenERP # session id, and are generally noise for key, value in request.session.items(): - if isinstance(value, session.OpenERPSession) and not value._uid: + if (isinstance(value, session.OpenERPSession) + and not value._uid + and not value.jsonp_requests + ): + _logger.info('remove session %s: %r', key, value.jsonp_requests) del request.session[key] - # FIXME: remove this when non-literals disappear - if sid: - # Re-load sessions from storage and merge non-literal - # contexts and domains (they're indexed by hash of the - # content so conflicts should auto-resolve), otherwise if - # two requests alter those concurrently the last to finish - # will overwrite the previous one, leading to loss of data - # (a non-literal is lost even though it was sent to the - # client and client errors) - # - # note that domains_store and contexts_store are append-only (we - # only ever add items to them), so we can just update one with the - # other to get the right result, if we want to merge the - # ``context`` dict we'll need something smarter - in_store = session_store.get(sid) - for k, v in request.session.iteritems(): - stored = in_store.get(k) - if stored and isinstance(v, session.OpenERPSession)\ - and v != stored: - v.contexts_store.update(stored.contexts_store) - v.domains_store.update(stored.domains_store) + with session_lock: + if sid: + # Re-load sessions from storage and merge non-literal + # contexts and domains (they're indexed by hash of the + # content so conflicts should auto-resolve), otherwise if + # two requests alter those concurrently the last to finish + # will overwrite the previous one, leading to loss of data + # (a non-literal is lost even though it was sent to the + # client and client errors) + # + # note that domains_store and contexts_store are append-only (we + # only ever add items to them), so we can just update one with the + # other to get the right result, if we want to merge the + # ``context`` dict we'll need something smarter + in_store = session_store.get(sid) + for k, v in request.session.iteritems(): + stored = in_store.get(k) + if stored and isinstance(v, session.OpenERPSession)\ + and v != stored: + v.contexts_store.update(stored.contexts_store) + v.domains_store.update(stored.domains_store) + v.jsonp_requests.update(stored.jsonp_requests) - session_store.save(request.session) + # add missing keys + for k, v in in_store.iteritems(): + if k not in request.session: + request.session[k] = v + + session_store.save(request.session) #---------------------------------------------------------- # OpenERP Web Module/Controller Loading and URL Routing diff --git a/addons/web/common/session.py b/addons/web/common/session.py index 1fa76e30b1d..be1a2b749da 100644 --- a/addons/web/common/session.py +++ b/addons/web/common/session.py @@ -37,6 +37,7 @@ class OpenERPSession(object): self.context = {} self.contexts_store = {} self.domains_store = {} + self.jsonp_requests = {} # FIXME use a LRU def __getstate__(self): state = dict(self.__dict__) diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py index 09b5299a555..fc9ae1dec16 100644 --- a/addons/web/controllers/main.py +++ b/addons/web/controllers/main.py @@ -10,6 +10,7 @@ import os import re import simplejson import time +import urllib2 import xmlrpclib import zlib from xml.etree import ElementTree @@ -242,6 +243,21 @@ class WebClient(openerpweb.Controller): "version": web.common.release.version } +class Proxy(openerpweb.Controller): + _cp_path = '/web/proxy' + + @openerpweb.jsonrequest + def load(self, req, path): + #req.config.socket_port + #if not re.match('^/[^/]+/static/.*', path): + # return werkzeug.exceptions.BadRequest() + + env = req.httprequest.environ + port = env['SERVER_PORT'] + + o = urllib2.urlopen('http://127.0.0.1:%s%s' % (port, path)) + return o.read() + class Database(openerpweb.Controller): _cp_path = "/web/database" @@ -359,7 +375,6 @@ class Session(openerpweb.Controller): @openerpweb.jsonrequest def get_session_info(self, req): - req.session.assert_valid(force=True) return { "uid": req.session._uid, "context": req.session.get_context() if req.session._uid else False, diff --git a/addons/web/static/src/js/chrome.js b/addons/web/static/src/js/chrome.js index e27dc343961..50fb8018918 100644 --- a/addons/web/static/src/js/chrome.js +++ b/addons/web/static/src/js/chrome.js @@ -629,9 +629,6 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{ callback: continuation || function() {} }); }, - on_logout: function() { - this.session.logout(); - } }); openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# */{ @@ -995,47 +992,52 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie this._super(null, element_id); openerp.webclient = this; - var params = {}; - if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { - this.$element.addClass("kitten-mode-activated"); - this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) { - self.$element.toggleClass('clark-gable'); - }); - } - this.$element.html(QWeb.render("Interface", params)); - this.notification = new openerp.web.Notification(this); this.loading = new openerp.web.Loading(this); this.crashmanager = new openerp.web.CrashManager(); this.header = new openerp.web.Header(this); this.login = new openerp.web.Login(this); - this.header.on_logout.add(this.login.on_logout); + this.header.on_logout.add(this.on_logout); this.header.on_action.add(this.on_menu_action); - this.session.on_session_invalid.add(this.login.do_ask_login); - this.session.on_session_valid.add_last(this.header.do_update); - this.session.on_session_invalid.add_last(this.header.do_update); - this.session.on_session_valid.add_last(this.on_logged); - this.session.on_session_invalid.add_last(this.on_logged_out); - - this.menu = new openerp.web.Menu(this, "oe_menu", "oe_secondary_menu"); - this.menu.on_action.add(this.on_menu_action); - this._current_state = null; - }, start: function() { this._super.apply(this, arguments); - this.notification.prependTo(this.$element); - this.loading.appendTo($('#oe_loading')); - this.header.appendTo($("#oe_header")); - this.session.start(); - this.login.appendTo($('#oe_login')); - this.menu.start(); + var self = this; + this.session.bind().then(function() { + var params = {}; + if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { + this.$element.addClass("kitten-mode-activated"); + this.$element.delegate('img.oe-record-edit-link-img', 'hover', function(e) { + self.$element.toggleClass('clark-gable'); + }); + } + self.$element.html(QWeb.render("Interface", params)); + self.menu = new openerp.web.Menu(self, "oe_menu", "oe_secondary_menu"); + self.menu.on_action.add(self.on_menu_action); + + self.notification.prependTo(self.$element); + self.loading.appendTo($('#oe_loading')); + self.header.appendTo($("#oe_header")); + self.login.appendTo($('#oe_login')); + self.menu.start(); + self.login.on_login_invalid(); + }); + this.session.ready.then(function() { + self.login.on_login_valid(); + self.header.do_update(); + self.menu.do_reload(); + if(self.action_manager) + self.action_manager.stop(); + self.action_manager = new openerp.web.ActionManager(this); + self.action_manager.appendTo($("#oe_app")); + self.bind_hashchange(); + }); }, do_reload: function() { - return $.when(this.session.session_restore(),this.menu.do_reload()); + return $.when(this.session.session_init(),this.menu.do_reload()); }, do_notify: function() { var n = this.notification; @@ -1045,24 +1047,10 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie var n = this.notification; n.warn.apply(n, arguments); }, - on_logged: function() { - this.menu.do_reload(); - if(this.action_manager) - this.action_manager.stop(); - this.action_manager = new openerp.web.ActionManager(this); - this.action_manager.appendTo($("#oe_app")); - - if (openerp._modules_loaded) { // TODO: find better option than this - this.bind_hashchange(); - } else { - this.session.on_modules_loaded.add({ // XXX what about a $.Deferred ? - callback: $.proxy(this, 'bind_hashchange'), - unique: true, - position: 'last' - }) - } - }, - on_logged_out: function() { + on_logout: function() { + this.session.session_logout(); + this.login.on_login_invalid(); + this.header.do_update(); $(window).unbind('hashchange', this.on_hashchange); this.do_push_state({}); if(this.action_manager) @@ -1105,6 +1093,47 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie }, }); +openerp.currentScript = function() { + var currentScript = document.currentScript; + if (!currentScript) { + var sc = document.getElementsByTagName('script'); + currentScript = sc[sc.length-1]; + } + return currentScript; +}; + +openerp.web.EmbeddedClient = openerp.web.Widget.extend({ + template: 'EmptyComponent', + init: function(action_id, options) { + this._super(); + // TODO take the xmlid of a action instead of its id + this.action_id = action_id; + this.options = options || {}; + this.am = new openerp.web.ActionManager(this); + }, + + start: function() { + var self = this; + + this.am.appendTo(this.$element.addClass('openerp')); + + return this.rpc("/web/action/load", { action_id: this.action_id }, function(result) { + var action = result.result; + action.flags = _.extend({ + //views_switcher : false, + search_view : false, + action_buttons : false, + sidebar : false + //pager : false + }, self.options, action.flags || {}); + + self.am.do_action(action); + }); + }, + +}); + + }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/web/static/src/js/core.js b/addons/web/static/src/js/core.js index d3734d29278..3d66c4f4ccf 100644 --- a/addons/web/static/src/js/core.js +++ b/addons/web/static/src/js/core.js @@ -350,11 +350,19 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. * @param {String} [server] JSON-RPC endpoint hostname * @param {String} [port] JSON-RPC endpoint port */ - init: function(server, port) { + init: function() { this._super(); - this.server = (server == undefined) ? location.hostname : server; - this.port = (port == undefined) ? location.port : port; - this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; + // TODO: session store in cookie should be optional + this.name = openerp._session_id; + }, + bind: function(host, protocol) { + var self = this; + this.host = (host == undefined) ? location.host : host; + this.protocol = (protocol == undefined) ? location.protocol : protocol; + this.prefix = this.protocol + '//' + this.host; + openerp.web.qweb.default_dict['_s'] = this.prefix + this.rpc_mode = (this.host == location.host) ? "json" : "jsonp"; + this.rpc_function = (this.host == location.host) ? this.rpc_json : this.rpc_jsonp; this.debug = (window.location.search.indexOf('?debug') !== -1); this.session_id = false; this.uid = false; @@ -366,14 +374,8 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. this.context = {}; this.shortcuts = []; this.active_id = null; - // TODO: session should have an optional name indicating that they'll - // be saved to (and revived from) cookies - this.name = 'session'; - this.do_load_qweb(['/web/webclient/qweb']); - }, - - start: function() { - this.session_restore(); + this.ready = $.Deferred(); + return this.session_init(); }, /** * Executes an RPC call, registering the provided callbacks. @@ -390,82 +392,133 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. */ rpc: function(url, params, success_callback, error_callback) { var self = this; + // url can be an $.ajax option object + if (_.isString(url)) { + url = { url: url }; + } // Construct a JSON-RPC2 request, method is currently unused params.session_id = this.session_id; if (this.debug) params.debug = 1; - - // Call using the rpc_mode - var deferred = $.Deferred(); - this.rpc_ajax(url, { - jsonrpc: "2.0", - method: "call", + var payload = { + jsonrpc: '2.0', + method: 'call', params: params, - id: _.uniqueId('browser-client-') - }).then(function () {deferred.resolve.apply(deferred, arguments);}, - function(error) {deferred.reject(error, $.Event());}); - return deferred.fail(function() { + id: _.uniqueId('r') + }; + var deferred = $.Deferred(); + this.on_rpc_request(); + this.rpc_function(url, payload).then( + function (response, textStatus, jqXHR) { + self.on_rpc_response(); + if (!response.error) { + deferred.resolve(response["result"], textStatus, jqXHR); + } else if (response.error.data.type === "session_invalid") { + self.uid = false; + // TODO deprecate or use a deferred on login.do_ask_login() + self.on_session_invalid(function() { + self.rpc(url, payload.params, + function() { deferred.resolve.apply(deferred, arguments); }, + function() { deferred.reject.apply(deferred, arguments); }); + }); + } else { + deferred.reject(response.error, $.Event()); + } + }, + function(jqXHR, textStatus, errorThrown) { + self.on_rpc_response(); + var error = { + code: -32098, + message: "XmlHttpRequestError " + errorThrown, + data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } + }; + deferred.reject(error, $.Event()); + }); + // Allow deferred user to disable on_rpc_error in fail + deferred.fail(function() { deferred.fail(function(error, event) { if (!event.isDefaultPrevented()) { self.on_rpc_error(error, event); } }); }).then(success_callback, error_callback).promise(); + return deferred; }, /** * Raw JSON-RPC call * * @returns {jQuery.Deferred} ajax-webd deferred object */ - rpc_ajax: function(url, payload) { + rpc_json: function(url, payload) { var self = this; - this.on_rpc_request(); - // url can be an $.ajax option object - if (_.isString(url)) { - url = { - url: url - } - } var ajax = _.extend({ type: "POST", - url: url, dataType: 'json', contentType: 'application/json', data: JSON.stringify(payload), - processData: false + processData: false, }, url); - var deferred = $.Deferred(); - $.ajax(ajax).done(function(response, textStatus, jqXHR) { - self.on_rpc_response(); - if (!response.error) { - deferred.resolve(response["result"], textStatus, jqXHR); - return; - } - if (response.error.data.type !== "session_invalid") { - deferred.reject(response.error); - return; - } - self.uid = false; - self.on_session_invalid(function() { - self.rpc(url, payload.params, - function() { - deferred.resolve.apply(deferred, arguments); - }, - function(error, event) { - event.preventDefault(); - deferred.reject.apply(deferred, arguments); - }); - }); - }).fail(function(jqXHR, textStatus, errorThrown) { - self.on_rpc_response(); - var error = { - code: -32098, - message: "XmlHttpRequestError " + errorThrown, - data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } + return $.ajax(ajax); + }, + rpc_jsonp: function(url, payload) { + var self = this; + // extracted from payload to set on the url + var data = { + session_id: this.session_id, + id: payload.id, + }; + url.url = this.get_url(url.url); + var ajax = _.extend({ + type: "GET", + dataType: 'jsonp', + jsonp: 'jsonp', + cache: false, + data: data + }, url); + var payload_str = JSON.stringify(payload); + var payload_url = $.param({r:payload_str}); + if(playload_url.length < 2000) { + // Direct jsonp request + ajax.data.r = payload_str; + return $.ajax(ajax); + } else { + // Indirect jsonp request + var ifid = _.uniqueId('oe_rpc_iframe'); + var display = options.openerp.debug ? 'block' : 'none'; + var $iframe = $(_.str.sprintf("", ifid, ifid, display)); + var $form = $('
') + .attr('method', 'POST') + .attr('target', ifid) + .attr('enctype', "multipart/form-data") + .attr('action', ajax.url + '?' + $.param(data)) + .append($('').attr('value', payload_str)) + .hide() + .appendTo($('body')); + var cleanUp = function() { + if ($iframe) { + $iframe.unbind("load").attr("src", "javascript:false;").remove(); + } + $form.remove(); }; - deferred.reject(error); - }); - return deferred.promise(); + var deferred = $.Deferred(); + // the first bind is fired up when the iframe is added to the DOM + $iframe.bind('load', function() { + // the second bind is fired up when the result of the form submission is received + $iframe.unbind('load').bind('load', function() { + $.ajax(ajax).always(function() { + cleanUp(); + }).then( + function() { deferred.resolve.apply(deferred, arguments); }, + function() { deferred.reject.apply(deferred, arguments); } + ); + }); + // now that the iframe can receive data, we fill and submit the form + $form.submit(); + }); + // append the iframe to the DOM (will trigger the first load) + $form.after($iframe); + return deferred; + } }, on_rpc_request: function() { }, @@ -474,75 +527,51 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. on_rpc_error: function(error) { }, /** - * The session is validated either by login or by restoration of a previous session + * Init a session, reloads from cookie, if it exists */ - on_session_valid: function() { - if(!openerp._modules_loaded) - this.load_modules(); - }, - on_session_invalid: function(contination) { - }, - session_is_valid: function() { - return this.uid; - }, - session_authenticate: function(db, login, password, success_callback) { - var self = this; - var base_location = document.location.protocol + '//' + document.location.host; - var params = { db: db, login: login, password: password, base_location: base_location }; - return this.rpc("/web/session/authenticate", params, function(result) { - _.extend(self, { - session_id: result.session_id, - uid: result.uid, - user_context: result.context, - db: result.db, - username: result.login - }); - self.session_save(); - self.on_session_valid(); - return true; - }).then(success_callback); - }, - login: function() { this.session_authenticate.apply(this, arguments); }, - /** - * Reloads uid and session_id from local storage, if they exist - */ - session_restore: function () { + session_init: function () { var self = this; + // TODO: session store in cookie should be optional this.session_id = this.get_cookie('session_id'); - return this.rpc("/web/session/get_session_info", {}).then(function(result) { + return this.rpc("/web/session/get_session_info", {}).pipe(function(result) { // If immediately follows a login (triggered by trying to restore // an invalid session or no session at all), refresh session data - // (should not change, but just in case...) but should not call - // on_session_valid again as it triggers reloading the menu - var already_logged = self.uid; + // (should not change, but just in case...) _.extend(self, { - uid: result.uid, - user_context: result.context, db: result.db, - username: result.login + username: result.login, + uid: result.uid, + user_context: result.context }); - if (!already_logged) { - if (self.uid) { - self.on_session_valid(); - } else { - self.on_session_invalid(); - } + var deferred = self.do_load_qweb(['/web/webclient/qweb']); + if(self.uid) { + return deferred.then(self.load_modules()); } - }, function() { - self.on_session_invalid(); + return deferred; }); }, /** - * Saves the session id and uid locally + * The session is validated either by login or by restoration of a previous session */ - session_save: function () { - this.set_cookie('session_id', this.session_id); + session_authenticate: function(db, login, password) { + var self = this; + var base_location = document.location.protocol + '//' + document.location.host; + var params = { db: db, login: login, password: password, base_location: base_location }; + return this.rpc("/web/session/authenticate", params).pipe(function(result) { + _.extend(self, { + session_id: result.session_id, + db: result.db, + username: result.login, + uid: result.uid, + user_context: result.context + }); + // TODO: session store in cookie should be optional + self.set_cookie('session_id', self.session_id); + return self.load_modules(); + }); }, - logout: function() { + session_logout: function() { this.set_cookie('session_id', ''); - this.reload_client(); - }, - reload_client: function() { window.location.reload(); }, /** @@ -586,23 +615,28 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. */ load_modules: function() { var self = this; + if(openerp._modules_loaded) { + return $.when(); + } this.rpc('/web/session/modules', {}, function(result) { self.module_list = result; var lang = self.user_context.lang; var params = { mods: ["web"].concat(result), lang: lang}; - self.rpc('/web/webclient/translations',params).then(function(transs) { + self.rpc('/web/webclient/translations',params).pipe(function(transs) { openerp.web._t.database.set_bundle(transs); var modules = self.module_list.join(','); var file_list = ["/web/static/lib/datejs/globalization/" + self.user_context.lang.replace("_", "-") + ".js" ]; - - self.rpc('/web/webclient/csslist', {"mods": modules}, self.do_load_css); - self.rpc('/web/webclient/jslist', {"mods": modules}, function(files) { - self.do_load_js(file_list.concat(files)); + return $.when( + self.rpc('/web/webclient/csslist', {mods: modules}, self.do_load_css), + self.rpc('/web/webclient/qweblist', {mods: modules}).pipe(self.do_load_qweb), + self.rpc('/web/webclient/jslist', {mods: modules}).pipe(function(files) { + return self.do_load_js(file_list.concat(files)); + }) + ).then(function() { + self.ready.resolve(); }); - self.rpc('/web/webclient/qweblist', {"mods": modules}, self.do_load_qweb); - openerp._modules_loaded = true; }); }); }, @@ -610,7 +644,7 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. var self = this; _.each(files, function (file) { $('head').append($('', { - 'href': file, + 'href': self.get_url(file), 'rel': 'stylesheet', 'type': 'text/css' })); @@ -618,28 +652,39 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp. }, do_load_js: function(files) { var self = this; + var d = $.Deferred(); if(files.length != 0) { var file = files.shift(); var tag = document.createElement('script'); tag.type = 'text/javascript'; - tag.src = file; + tag.src = self.get_url(file); tag.onload = tag.onreadystatechange = function() { if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done ) return; tag.onload_done = true; - self.do_load_js(files); + self.do_load_js(files).then(function () { + d.resolve(); + }); }; var head = document.head || document.getElementsByTagName('head')[0]; head.appendChild(tag); } else { - this.on_modules_loaded(); + self.on_modules_loaded(); + d.resolve(); } + return d; }, do_load_qweb: function(files) { var self = this; - _.each(files, function(file) { - openerp.web.qweb.add_template(file); - }); + if (files.length != 0) { + var file = files.shift(); + return self.rpc('/web/proxy/load', {path: file}).pipe(function(xml) { + openerp.web.qweb.add_template(_.str.trim(xml)); + return self.do_load_qweb(files); + }); + } else { + return $.when(); + } }, on_modules_loaded: function() { for(var j=0; j', - '', column.string || '', '', '' ].join('') diff --git a/addons/web/static/src/xml/base.xml b/addons/web/static/src/xml/base.xml index 600d1c0b78b..ff21856aeff 100644 --- a/addons/web/static/src/xml/base.xml +++ b/addons/web/static/src/xml/base.xml @@ -266,7 +266,7 @@
- +
@@ -306,7 +306,7 @@ @@ -928,7 +928,7 @@ @@ -940,7 +940,7 @@ t-att-id="widget.element_id" t-attf-class="field_#{widget.type}" style="width: 100%" - > + >
@@ -948,7 +948,7 @@
@@ -968,9 +968,9 @@ - + - + @@ -1021,7 +1021,7 @@
- + Full featured
@@ -315,7 +315,7 @@
- + Open Source
@@ -324,7 +324,7 @@
- + User Friendly
@@ -352,13 +352,13 @@
  • - +
  • - +
  • - +
@@ -724,7 +724,7 @@ @@ -895,7 +895,7 @@ t-att-id="widget.element_id" t-attf-class="field_#{widget.type} #{_(['integer', 'float', 'float_time']).contains(widget.type) ? 'oe-number' : ''}" style="width: 100%" - /> + />
- @@ -1051,14 +1051,14 @@
@@ -1086,7 +1086,7 @@ @@ -1095,13 +1095,13 @@ @@ -1109,7 +1109,7 @@ - + Uploading ... @@ -1118,7 +1118,7 @@ @@ -1211,7 +1211,7 @@ t-att-title="attrs.help" t-att-class="classes.join(' ')" t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined"> - +
@@ -1358,7 +1358,7 @@ - + @@ -1702,7 +1702,7 @@ diff --git a/addons/web_kanban/static/src/js/kanban.js b/addons/web_kanban/static/src/js/kanban.js index 0b67fdb3f66..053e1cdfd00 100644 --- a/addons/web_kanban/static/src/js/kanban.js +++ b/addons/web_kanban/static/src/js/kanban.js @@ -106,7 +106,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({ node.children = [{ tag: 'img', attrs: { - src: '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png', + src: openerp.connection.prefix + '/web/static/src/img/icons/' + node.attrs['data-icon'] + '.png', width: '16', height: '16' } @@ -524,7 +524,7 @@ openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({ }, kanban_image: function(model, field, id) { id = id || ''; - return '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id; + return openerp.connection.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id; }, kanban_text_ellipsis: function(s, size) { size = size || 160;

@@ -1586,7 +1586,7 @@

- +
- +