[MERGE] jsonp branch by chs,

deferrization and cleanup of Connection, 
simple jsonp (not fully tested),
url (push_state) is broken
(+395/-262) 8 files modified

bzr revid: al@openerp.com-20111216015358-e8p5bi8pl86onlbz
This commit is contained in:
Antony Lesuisse 2011-12-16 02:53:58 +01:00
commit fc4faa599b
8 changed files with 388 additions and 255 deletions

View File

@ -6,11 +6,12 @@ import ast
import contextlib import contextlib
import functools import functools
import logging import logging
import urllib
import os import os
import pprint import pprint
import sys import sys
import threading
import traceback import traceback
import urllib
import uuid import uuid
import xmlrpclib import xmlrpclib
@ -128,17 +129,37 @@ class JsonRequest(WebRequest):
"id": null} "id": null}
""" """
def dispatch(self, controller, method):
def dispatch(self, controller, method, requestf=None, request=None): """ Calls the method asked for by the JSON-RPC2 or JSONP request
""" Calls the method asked for by the JSON-RPC2 request
:param controller: the instance of the controller which received the request :param controller: the instance of the controller which received the request
:param method: the method 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" } response = {"jsonrpc": "2.0" }
error = None error = None
try: try:
@ -188,10 +209,16 @@ class JsonRequest(WebRequest):
if _logger.isEnabledFor(logging.DEBUG): if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("<--\n%s", pprint.pformat(response)) _logger.debug("<--\n%s", pprint.pformat(response))
content = simplejson.dumps(response, cls=nonliterals.NonLiteralEncoder)
return werkzeug.wrappers.Response( if jsonp:
content, headers=[('Content-Type', 'application/json'), mime = 'application/javascript'
('Content-Length', len(content))]) 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): def jsonrequest(f):
""" Decorator marking the decorated method as being a handler for a """ Decorator marking the decorated method as being a handler for a
@ -205,8 +232,7 @@ def jsonrequest(f):
""" """
@functools.wraps(f) @functools.wraps(f)
def json_handler(controller, request, config): def json_handler(controller, request, config):
return JsonRequest(request, config).dispatch( return JsonRequest(request, config).dispatch(controller, f)
controller, f, requestf=request.stream)
json_handler.exposed = True json_handler.exposed = True
return json_handler return json_handler
@ -281,13 +307,15 @@ STORES = {}
@contextlib.contextmanager @contextlib.contextmanager
def session_context(request, storage_path, session_cookie='sessionid'): 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: if not session_store:
session_store = werkzeug.contrib.sessions.FilesystemSessionStore( session_store = werkzeug.contrib.sessions.FilesystemSessionStore(
storage_path) storage_path)
STORES[storage_path] = session_store session_lock = threading.Lock()
STORES[storage_path] = session_store, session_lock
sid = request.cookies.get(session_cookie) sid = request.cookies.get(session_cookie)
with session_lock:
if sid: if sid:
request.session = session_store.get(sid) request.session = session_store.get(sid)
else: else:
@ -300,10 +328,14 @@ def session_context(request, storage_path, session_cookie='sessionid'):
# either by login process or by HTTP requests without an OpenERP # either by login process or by HTTP requests without an OpenERP
# session id, and are generally noise # session id, and are generally noise
for key, value in request.session.items(): 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] del request.session[key]
# FIXME: remove this when non-literals disappear with session_lock:
if sid: if sid:
# Re-load sessions from storage and merge non-literal # Re-load sessions from storage and merge non-literal
# contexts and domains (they're indexed by hash of the # contexts and domains (they're indexed by hash of the
@ -324,6 +356,12 @@ def session_context(request, storage_path, session_cookie='sessionid'):
and v != stored: and v != stored:
v.contexts_store.update(stored.contexts_store) v.contexts_store.update(stored.contexts_store)
v.domains_store.update(stored.domains_store) v.domains_store.update(stored.domains_store)
v.jsonp_requests.update(stored.jsonp_requests)
# 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) session_store.save(request.session)

View File

@ -37,6 +37,7 @@ class OpenERPSession(object):
self.context = {} self.context = {}
self.contexts_store = {} self.contexts_store = {}
self.domains_store = {} self.domains_store = {}
self.jsonp_requests = {} # FIXME use a LRU
def __getstate__(self): def __getstate__(self):
state = dict(self.__dict__) state = dict(self.__dict__)

View File

@ -10,6 +10,7 @@ import os
import re import re
import simplejson import simplejson
import time import time
import urllib2
import xmlrpclib import xmlrpclib
import zlib import zlib
from xml.etree import ElementTree from xml.etree import ElementTree
@ -242,6 +243,21 @@ class WebClient(openerpweb.Controller):
"version": web.common.release.version "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): class Database(openerpweb.Controller):
_cp_path = "/web/database" _cp_path = "/web/database"
@ -359,7 +375,6 @@ class Session(openerpweb.Controller):
@openerpweb.jsonrequest @openerpweb.jsonrequest
def get_session_info(self, req): def get_session_info(self, req):
req.session.assert_valid(force=True)
return { return {
"uid": req.session._uid, "uid": req.session._uid,
"context": req.session.get_context() if req.session._uid else False, "context": req.session.get_context() if req.session._uid else False,

View File

@ -629,9 +629,6 @@ openerp.web.Login = openerp.web.Widget.extend(/** @lends openerp.web.Login# */{
callback: continuation || function() {} callback: continuation || function() {}
}); });
}, },
on_logout: function() {
this.session.logout();
}
}); });
openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# */{ openerp.web.Header = openerp.web.Widget.extend(/** @lends openerp.web.Header# */{
@ -995,6 +992,21 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
this._super(null, element_id); this._super(null, element_id);
openerp.webclient = this; openerp.webclient = this;
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.on_logout);
this.header.on_action.add(this.on_menu_action);
this._current_state = null;
},
start: function() {
this._super.apply(this, arguments);
var self = this;
this.session.bind().then(function() {
var params = {}; var params = {};
if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) { if (jQuery.param != undefined && jQuery.deparam(jQuery.param.querystring()).kitten != undefined) {
this.$element.addClass("kitten-mode-activated"); this.$element.addClass("kitten-mode-activated");
@ -1002,40 +1014,30 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
self.$element.toggleClass('clark-gable'); self.$element.toggleClass('clark-gable');
}); });
} }
this.$element.html(QWeb.render("Interface", params)); 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);
this.notification = new openerp.web.Notification(this); self.notification.prependTo(self.$element);
this.loading = new openerp.web.Loading(this); self.loading.appendTo($('#oe_loading'));
this.crashmanager = new openerp.web.CrashManager(); self.header.appendTo($("#oe_header"));
self.login.appendTo($('#oe_login'));
this.header = new openerp.web.Header(this); self.menu.start();
this.login = new openerp.web.Login(this); self.login.on_login_invalid();
this.header.on_logout.add(this.login.on_logout); });
this.header.on_action.add(this.on_menu_action); this.session.ready.then(function() {
self.login.on_login_valid();
this.session.on_session_invalid.add(this.login.do_ask_login); self.header.do_update();
this.session.on_session_valid.add_last(this.header.do_update); self.menu.do_reload();
this.session.on_session_invalid.add_last(this.header.do_update); if(self.action_manager)
this.session.on_session_valid.add_last(this.on_logged); self.action_manager.stop();
this.session.on_session_invalid.add_last(this.on_logged_out); self.action_manager = new openerp.web.ActionManager(this);
self.action_manager.appendTo($("#oe_app"));
this.menu = new openerp.web.Menu(this, "oe_menu", "oe_secondary_menu"); self.bind_hashchange();
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();
}, },
do_reload: function() { 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() { do_notify: function() {
var n = this.notification; var n = this.notification;
@ -1045,24 +1047,10 @@ openerp.web.WebClient = openerp.web.Widget.extend(/** @lends openerp.web.WebClie
var n = this.notification; var n = this.notification;
n.warn.apply(n, arguments); n.warn.apply(n, arguments);
}, },
on_logged: function() { on_logout: function() {
this.menu.do_reload(); this.session.session_logout();
if(this.action_manager) this.login.on_login_invalid();
this.action_manager.stop(); this.header.do_update();
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() {
$(window).unbind('hashchange', this.on_hashchange); $(window).unbind('hashchange', this.on_hashchange);
this.do_push_state({}); this.do_push_state({});
if(this.action_manager) 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: // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -350,11 +350,19 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
* @param {String} [server] JSON-RPC endpoint hostname * @param {String} [server] JSON-RPC endpoint hostname
* @param {String} [port] JSON-RPC endpoint port * @param {String} [port] JSON-RPC endpoint port
*/ */
init: function(server, port) { init: function() {
this._super(); this._super();
this.server = (server == undefined) ? location.hostname : server; // TODO: session store in cookie should be optional
this.port = (port == undefined) ? location.port : port; this.name = openerp._session_id;
this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; },
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.debug = (window.location.search.indexOf('?debug') !== -1);
this.session_id = false; this.session_id = false;
this.uid = false; this.uid = false;
@ -366,14 +374,8 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
this.context = {}; this.context = {};
this.shortcuts = []; this.shortcuts = [];
this.active_id = null; this.active_id = null;
// TODO: session should have an optional name indicating that they'll this.ready = $.Deferred();
// be saved to (and revived from) cookies return this.session_init();
this.name = 'session';
this.do_load_qweb(['/web/webclient/qweb']);
},
start: function() {
this.session_restore();
}, },
/** /**
* Executes an RPC call, registering the provided callbacks. * 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) { rpc: function(url, params, success_callback, error_callback) {
var self = this; 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 // Construct a JSON-RPC2 request, method is currently unused
params.session_id = this.session_id; params.session_id = this.session_id;
if (this.debug) if (this.debug)
params.debug = 1; params.debug = 1;
var payload = {
// Call using the rpc_mode jsonrpc: '2.0',
var deferred = $.Deferred(); method: 'call',
this.rpc_ajax(url, {
jsonrpc: "2.0",
method: "call",
params: params, params: params,
id: _.uniqueId('browser-client-') id: _.uniqueId('r')
}).then(function () {deferred.resolve.apply(deferred, arguments);}, };
function(error) {deferred.reject(error, $.Event());});
return deferred.fail(function() {
deferred.fail(function(error, event) {
if (!event.isDefaultPrevented()) {
self.on_rpc_error(error, event);
}
});
}).then(success_callback, error_callback).promise();
},
/**
* Raw JSON-RPC call
*
* @returns {jQuery.Deferred} ajax-webd deferred object
*/
rpc_ajax: 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
}, url);
var deferred = $.Deferred(); var deferred = $.Deferred();
$.ajax(ajax).done(function(response, textStatus, jqXHR) { this.on_rpc_request();
this.rpc_function(url, payload).then(
function (response, textStatus, jqXHR) {
self.on_rpc_response(); self.on_rpc_response();
if (!response.error) { if (!response.error) {
deferred.resolve(response["result"], textStatus, jqXHR); deferred.resolve(response["result"], textStatus, jqXHR);
return; } else if (response.error.data.type === "session_invalid") {
}
if (response.error.data.type !== "session_invalid") {
deferred.reject(response.error);
return;
}
self.uid = false; self.uid = false;
// TODO deprecate or use a deferred on login.do_ask_login()
self.on_session_invalid(function() { self.on_session_invalid(function() {
self.rpc(url, payload.params, self.rpc(url, payload.params,
function() { function() { deferred.resolve.apply(deferred, arguments); },
deferred.resolve.apply(deferred, arguments); function() { deferred.reject.apply(deferred, arguments); });
});
} else {
deferred.reject(response.error, $.Event());
}
}, },
function(error, event) { function(jqXHR, textStatus, errorThrown) {
event.preventDefault();
deferred.reject.apply(deferred, arguments);
});
});
}).fail(function(jqXHR, textStatus, errorThrown) {
self.on_rpc_response(); self.on_rpc_response();
var error = { var error = {
code: -32098, code: -32098,
message: "XmlHttpRequestError " + errorThrown, message: "XmlHttpRequestError " + errorThrown,
data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] } data: {type: "xhr"+textStatus, debug: jqXHR.responseText, objects: [jqXHR, errorThrown] }
}; };
deferred.reject(error); deferred.reject(error, $.Event());
}); });
return deferred.promise(); // 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_json: function(url, payload) {
var self = this;
var ajax = _.extend({
type: "POST",
dataType: 'json',
contentType: 'application/json',
data: JSON.stringify(payload),
processData: false,
}, url);
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("<iframe src='javascript:false;' name='%s' id='%s' style='display:%s'></iframe>", ifid, ifid, display));
var $form = $('<form>')
.attr('method', 'POST')
.attr('target', ifid)
.attr('enctype', "multipart/form-data")
.attr('action', ajax.url + '?' + $.param(data))
.append($('<input type="hidden" name="r" />').attr('value', payload_str))
.hide()
.appendTo($('body'));
var cleanUp = function() {
if ($iframe) {
$iframe.unbind("load").attr("src", "javascript:false;").remove();
}
$form.remove();
};
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() { on_rpc_request: function() {
}, },
@ -473,76 +526,52 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
}, },
on_rpc_error: function(error) { on_rpc_error: function(error) {
}, },
/**
* Init a session, reloads from cookie, if it exists
*/
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", {}).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...)
_.extend(self, {
db: result.db,
username: result.login,
uid: result.uid,
user_context: result.context
});
var deferred = self.do_load_qweb(['/web/webclient/qweb']);
if(self.uid) {
return deferred.then(self.load_modules());
}
return deferred;
});
},
/** /**
* The session is validated either by login or by restoration of a previous session * The session is validated either by login or by restoration of a previous session
*/ */
on_session_valid: function() { session_authenticate: function(db, login, password) {
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 self = this;
var base_location = document.location.protocol + '//' + document.location.host; var base_location = document.location.protocol + '//' + document.location.host;
var params = { db: db, login: login, password: password, base_location: base_location }; var params = { db: db, login: login, password: password, base_location: base_location };
return this.rpc("/web/session/authenticate", params, function(result) { return this.rpc("/web/session/authenticate", params).pipe(function(result) {
_.extend(self, { _.extend(self, {
session_id: result.session_id, session_id: result.session_id,
uid: result.uid,
user_context: result.context,
db: result.db, db: result.db,
username: result.login 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 () {
var self = this;
this.session_id = this.get_cookie('session_id');
return this.rpc("/web/session/get_session_info", {}).then(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;
_.extend(self, {
uid: result.uid, uid: result.uid,
user_context: result.context, user_context: result.context
db: result.db,
username: result.login
}); });
if (!already_logged) { // TODO: session store in cookie should be optional
if (self.uid) { self.set_cookie('session_id', self.session_id);
self.on_session_valid(); return self.load_modules();
} else {
self.on_session_invalid();
}
}
}, function() {
self.on_session_invalid();
}); });
}, },
/** session_logout: function() {
* Saves the session id and uid locally
*/
session_save: function () {
this.set_cookie('session_id', this.session_id);
},
logout: function() {
this.set_cookie('session_id', ''); this.set_cookie('session_id', '');
this.reload_client();
},
reload_client: function() {
window.location.reload(); window.location.reload();
}, },
/** /**
@ -586,23 +615,28 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
*/ */
load_modules: function() { load_modules: function() {
var self = this; var self = this;
if(openerp._modules_loaded) {
return $.when();
}
this.rpc('/web/session/modules', {}, function(result) { this.rpc('/web/session/modules', {}, function(result) {
self.module_list = result; self.module_list = result;
var lang = self.user_context.lang; var lang = self.user_context.lang;
var params = { mods: ["web"].concat(result), lang: 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); openerp.web._t.database.set_bundle(transs);
var modules = self.module_list.join(','); var modules = self.module_list.join(',');
var file_list = ["/web/static/lib/datejs/globalization/" + var file_list = ["/web/static/lib/datejs/globalization/" +
self.user_context.lang.replace("_", "-") + ".js" self.user_context.lang.replace("_", "-") + ".js"
]; ];
return $.when(
self.rpc('/web/webclient/csslist', {"mods": modules}, self.do_load_css); self.rpc('/web/webclient/csslist', {mods: modules}, self.do_load_css),
self.rpc('/web/webclient/jslist', {"mods": modules}, function(files) { self.rpc('/web/webclient/qweblist', {mods: modules}).pipe(self.do_load_qweb),
self.do_load_js(file_list.concat(files)); 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; var self = this;
_.each(files, function (file) { _.each(files, function (file) {
$('head').append($('<link>', { $('head').append($('<link>', {
'href': file, 'href': self.get_url(file),
'rel': 'stylesheet', 'rel': 'stylesheet',
'type': 'text/css' 'type': 'text/css'
})); }));
@ -618,28 +652,39 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
}, },
do_load_js: function(files) { do_load_js: function(files) {
var self = this; var self = this;
var d = $.Deferred();
if(files.length != 0) { if(files.length != 0) {
var file = files.shift(); var file = files.shift();
var tag = document.createElement('script'); var tag = document.createElement('script');
tag.type = 'text/javascript'; tag.type = 'text/javascript';
tag.src = file; tag.src = self.get_url(file);
tag.onload = tag.onreadystatechange = function() { tag.onload = tag.onreadystatechange = function() {
if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done ) if ( (tag.readyState && tag.readyState != "loaded" && tag.readyState != "complete") || tag.onload_done )
return; return;
tag.onload_done = true; 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]; var head = document.head || document.getElementsByTagName('head')[0];
head.appendChild(tag); head.appendChild(tag);
} else { } else {
this.on_modules_loaded(); self.on_modules_loaded();
d.resolve();
} }
return d;
}, },
do_load_qweb: function(files) { do_load_qweb: function(files) {
var self = this; var self = this;
_.each(files, function(file) { if (files.length != 0) {
openerp.web.qweb.add_template(file); 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() { on_modules_loaded: function() {
for(var j=0; j<this.module_list.length; j++) { for(var j=0; j<this.module_list.length; j++) {
@ -654,6 +699,9 @@ openerp.web.Connection = openerp.web.CallbackEnabled.extend( /** @lends openerp.
} }
} }
}, },
get_url: function (file) {
return this.prefix + file;
},
/** /**
* Cooperative file download implementation, for ajaxy APIs. * Cooperative file download implementation, for ajaxy APIs.
* *
@ -1069,6 +1117,7 @@ openerp.web.TranslationDataBase = openerp.web.Class.extend(/** @lends openerp.we
} }
}); });
/** Configure blockui */
if ($.blockUI) { if ($.blockUI) {
$.blockUI.defaults.baseZ = 1100; $.blockUI.defaults.baseZ = 1100;
$.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">'; $.blockUI.defaults.message = '<img src="/web/static/src/img/throbber2.gif">';
@ -1100,6 +1149,7 @@ openerp.web.qweb.format_text_node = function(s) {
openerp.connection = new openerp.web.Connection(); openerp.connection = new openerp.web.Connection();
openerp.web.qweb.default_dict['__debug__'] = openerp.connection.debug; openerp.web.qweb.default_dict['__debug__'] = openerp.connection.debug;
/** Jquery extentions */
$.Mutex = (function() { $.Mutex = (function() {
function Mutex() { function Mutex() {
this.def = $.Deferred().resolve(); this.def = $.Deferred().resolve();

View File

@ -254,7 +254,7 @@ openerp.web.format_cell = function (row_data, column, value_if_empty, process_mo
if (column.tag === 'button') { if (column.tag === 'button') {
return [ return [
'<button type="button" title="', column.string || '', '">', '<button type="button" title="', column.string || '', '">',
'<img src="/web/static/src/img/icons/', column.icon, '.png"', '<img src="', openerp.connection.prefix, '/web/static/src/img/icons/', column.icon, '.png"',
' alt="', column.string || '', '"/>', ' alt="', column.string || '', '"/>',
'</button>' '</button>'
].join('') ].join('')

View File

@ -266,7 +266,7 @@
<form class="oe_forms"> <form class="oe_forms">
<fieldset> <fieldset>
<legend style=""> <legend style="">
<img src="/web/static/src/img/stock_person.png" alt="" /> <img t-att-src='_s + "/web/static/src/img/stock_person.png"' alt="" />
</legend> </legend>
<div class="oe_box2"> <div class="oe_box2">
<table align="center" cellspacing="2px" cellpadding="0"> <table align="center" cellspacing="2px" cellpadding="0">
@ -306,7 +306,7 @@
<tbody> <tbody>
<tr> <tr>
<td> <td>
<img src="/web/static/src/img/product.png"/> <img t-att-src='_s + "/web/static/src/img/product.png"'/>
</td> </td>
<td> <td>
<strong>Full featured</strong><br /> <strong>Full featured</strong><br />
@ -315,7 +315,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<img src="/web/static/src/img/accessories-archiver.png"/> <img t-att-src='_s + "/web/static/src/img/accessories-archiver.png"'/>
</td> </td>
<td> <td>
<strong>Open Source</strong><br /> <strong>Open Source</strong><br />
@ -324,7 +324,7 @@
</tr> </tr>
<tr> <tr>
<td> <td>
<img src="/web/static/src/img/partner.png" /> <img t-att-src='_s + "/web/static/src/img/partner.png"' />
</td> </td>
<td> <td>
<strong>User Friendly</strong><br /> <strong>User Friendly</strong><br />
@ -352,13 +352,13 @@
<div class="header_corner"> <div class="header_corner">
<ul class="block"> <ul class="block">
<li> <li>
<a t-att-href="'/' + widget.qs" title="Home" class="home"><img src="/web/static/src/img/header-home.png" width="16" height="16" border="0"/></a> <a t-att-href="'/' + widget.qs" title="Home" class="home"><img t-att-src='_s + "/web/static/src/img/header-home.png"' width="16" height="16" border="0"/></a>
</li> </li>
<li class="preferences"> <li class="preferences">
<a href="javascript:void(0)" title="Preferences" class="preferences"><img src="/web/static/src/img/header-preferences.png" width="16" height="16" border="0"/></a> <a href="javascript:void(0)" title="Preferences" class="preferences"><img t-att-src='_s + "/web/static/src/img/header-preferences.png"' width="16" height="16" border="0"/></a>
</li> </li>
<li> <li>
<a href="javascript:void(0)" title="About" class="about"><img src="/web/static/src/img/header-about.png" width="16" height="16" border="0"/></a> <a href="javascript:void(0)" title="About" class="about"><img t-att-src='_s + "/web/static/src/img/header-about.png"' width="16" height="16" border="0"/></a>
</li> </li>
</ul> </ul>
<div class="block"> <div class="block">
@ -724,7 +724,7 @@
<input type="hidden" name="model" t-att-value="view.dataset.model"/> <input type="hidden" name="model" t-att-value="view.dataset.model"/>
<input type="hidden" name="id" t-att-value="view.datarecord.id"/> <input type="hidden" name="id" t-att-value="view.datarecord.id"/>
<button class="button" type="button"> <button class="button" type="button">
<img src="/web/static/src/img/throbber.gif" width="16" height="16" style="display: none"/> <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16" style="display: none"/>
<span>Add</span> <span>Add</span>
</button> </button>
<input type="file" class="oe-binary-file" name="ufile" title="Add attachment" <input type="file" class="oe-binary-file" name="ufile" title="Add attachment"
@ -736,14 +736,14 @@
<br style="clear: both"/> <br style="clear: both"/>
<ul class="oe-sidebar-attachments-items"> <ul class="oe-sidebar-attachments-items">
<li t-foreach="attachments" t-as="attachment"> <li t-foreach="attachments" t-as="attachment">
<t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="'/web/binary/saveas?session_id=' <t t-if="attachment.type == 'binary'" t-set="attachment.url" t-value="_s + '/web/binary/saveas?session_id='
+ session.session_id + '&amp;model=ir.attachment&amp;id=' + attachment.id + session.session_id + '&amp;model=ir.attachment&amp;id=' + attachment.id
+ '&amp;field=datas&amp;fieldname=name&amp;t=' + (new Date().getTime())"/> + '&amp;field=datas&amp;fieldname=name&amp;t=' + (new Date().getTime())"/>
<a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank"> <a class="oe-sidebar-attachments-link" t-att-href="attachment.url" target="_blank">
<t t-esc="attachment.name"/> <t t-esc="attachment.name"/>
</a> </a>
<a href="#" class="oe-sidebar-attachment-delete" t-att-data-id="attachment.id" t-attf-title="Delete the attachment #{attachment.name}"> <a href="#" class="oe-sidebar-attachment-delete" t-att-data-id="attachment.id" t-attf-title="Delete the attachment #{attachment.name}">
<img src="/web/static/src/img/attachments-close.png" width="15" height="15" border="0"/> <img t-att-src='_s + "/web/static/src/img/attachments-close.png"' width="15" height="15" border="0"/>
</a> </a>
</li> </li>
</ul> </ul>
@ -895,7 +895,7 @@
t-att-id="widget.element_id" t-att-id="widget.element_id"
t-attf-class="field_#{widget.type} #{_(['integer', 'float', 'float_time']).contains(widget.type) ? 'oe-number' : ''}" t-attf-class="field_#{widget.type} #{_(['integer', 'float', 'float_time']).contains(widget.type) ? 'oe-number' : ''}"
style="width: 100%" style="width: 100%"
/><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/> /><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/>
</t> </t>
<t t-name="FieldChar.readonly"> <t t-name="FieldChar.readonly">
<div <div
@ -914,7 +914,7 @@
</td> </td>
<td width="16"> <td width="16">
<button type="button" class="button" title="Send an e-mail with your default e-mail client"> <button type="button" class="button" title="Send an e-mail with your default e-mail client">
<img src="/web/static/src/img/icons/terp-mail-message-new.png"/> <img t-att-src='_s + "/web/static/src/img/icons/terp-mail-message-new.png"'/>
</button> </button>
</td> </td>
</tr> </tr>
@ -928,7 +928,7 @@
</td> </td>
<td width="16"> <td width="16">
<button type="button" class="button" title="Open this resource"> <button type="button" class="button" title="Open this resource">
<img src="/web/static/src/img/icons/gtk-ok.png"/> <img t-att-src='_s + "/web/static/src/img/icons/gtk-ok.png"'/>
</button> </button>
</td> </td>
</tr> </tr>
@ -940,7 +940,7 @@
t-att-id="widget.element_id" t-att-id="widget.element_id"
t-attf-class="field_#{widget.type}" t-attf-class="field_#{widget.type}"
style="width: 100%" style="width: 100%"
></textarea><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" src="/web/static/src/img/icons/terp-translate.png" width="16" height="16" border="0"/> ></textarea><img class="oe_field_translate oe_input_icon" t-if="widget.field.translate" t-att-src='_s + "/web/static/src/img/icons/terp-translate.png"' width="16" height="16" border="0"/>
</t> </t>
<t t-name="web.datetimepicker"> <t t-name="web.datetimepicker">
<div class="oe_datepicker_root"> <div class="oe_datepicker_root">
@ -948,7 +948,7 @@
<input type="text" size="1" style="width: 100%" <input type="text" size="1" style="width: 100%"
t-att-name="widget.name" t-att-name="widget.name"
t-attf-class="oe_datepicker_master field_#{widget.type_of_date}" t-attf-class="oe_datepicker_master field_#{widget.type_of_date}"
/><img class="oe_input_icon oe_datepicker_trigger" src="/web/static/src/img/ui/field_calendar.png" /><img class="oe_input_icon oe_datepicker_trigger" t-att-src='_s + "/web/static/src/img/ui/field_calendar.png"'
title="Select date" width="16" height="16" border="0"/> title="Select date" width="16" height="16" border="0"/>
</div> </div>
</t> </t>
@ -968,9 +968,9 @@
<input type="text" size="1" style="width: 100%;" <input type="text" size="1" style="width: 100%;"
t-att-id="widget.element_id"/> t-att-id="widget.element_id"/>
<span class="oe-m2o-drop-down-button"> <span class="oe-m2o-drop-down-button">
<img src="/web/static/src/img/down-arrow.png" /></span> <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span>
<span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'"> <span class="oe-m2o-cm-button" t-att-id="widget.name + '_open'">
<img src="/web/static/src/img/icons/gtk-index.png"/></span> <img t-att-src='_s + "/web/static/src/img/icons/gtk-index.png"'/></span>
<div t-att-id="widget.cm_id" class="contextMenu" style="display:none"> <div t-att-id="widget.cm_id" class="contextMenu" style="display:none">
</div> </div>
</div> </div>
@ -1021,7 +1021,7 @@
<table cellpadding="0" cellspacing="0" border="0"> <table cellpadding="0" cellspacing="0" border="0">
<tr> <tr>
<td align="center"> <td align="center">
<img src="/web/static/src/img/placeholder.png" class="oe-binary-image" <img t-att-src='_s + "/web/static/src/img/placeholder.png"' class="oe-binary-image"
t-att-border="widget.readonly ? 0 : 1" t-att-border="widget.readonly ? 0 : 1"
t-att-id="widget.element_id + '_field'" t-att-id="widget.element_id + '_field'"
t-att-name="widget.name" t-att-name="widget.name"
@ -1043,7 +1043,7 @@
<input type="hidden" name="session_id" value=""/> <input type="hidden" name="session_id" value=""/>
<input type="hidden" name="callback" t-att-value="widget.iframe"/> <input type="hidden" name="callback" t-att-value="widget.iframe"/>
<button class="button" type="button" title="Set Image"> <button class="button" type="button" title="Set Image">
<img src="/web/static/src/img/icons/STOCK_DIRECTORY.png"/> <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/>
</button> </button>
<input type="file" class="oe-binary-file" name="ufile"/> <input type="file" class="oe-binary-file" name="ufile"/>
</form> </form>
@ -1051,14 +1051,14 @@
</td> </td>
<td> <td>
<button class="button oe-binary-file-clear" type="button" title="Clear"> <button class="button oe-binary-file-clear" type="button" title="Clear">
<img src="/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"/> <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/>
</button> </button>
</td> </td>
</tr> </tr>
</table> </table>
</div> </div>
<div class="oe-binary-progress" style="display: none"> <div class="oe-binary-progress" style="display: none">
<img src="/web/static/src/img/throbber.gif" width="16" height="16"/> <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/>
<b>Uploading ...</b> <b>Uploading ...</b>
</div> </div>
<iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"> </iframe> <iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"> </iframe>
@ -1086,7 +1086,7 @@
<input type="hidden" name="session_id" value=""/> <input type="hidden" name="session_id" value=""/>
<input type="hidden" name="callback" t-att-value="widget.iframe"/> <input type="hidden" name="callback" t-att-value="widget.iframe"/>
<button class="button" type="button" title="Set Image"> <button class="button" type="button" title="Set Image">
<img src="/web/static/src/img/icons/STOCK_DIRECTORY.png"/> <img t-att-src='_s + "/web/static/src/img/icons/STOCK_DIRECTORY.png"'/>
<span>Select</span> <span>Select</span>
</button> </button>
<input type="file" class="oe-binary-file" name="ufile"/> <input type="file" class="oe-binary-file" name="ufile"/>
@ -1095,13 +1095,13 @@
</td> </td>
<td> <td>
<button class="button oe-binary-file-save" type="button" title="Save As"> <button class="button oe-binary-file-save" type="button" title="Save As">
<img src="/web/static/src/img/icons/gtk-save.png"/> <img t-att-src='_s + "/web/static/src/img/icons/gtk-save.png"'/>
<span>Save As</span> <span>Save As</span>
</button> </button>
</td> </td>
<td> <td>
<button class="button oe-binary-file-clear" type="button" title="Clear"> <button class="button oe-binary-file-clear" type="button" title="Clear">
<img src="/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"/> <img t-att-src='_s + "/web/static/src/img/icons/STOCK_MISSING_IMAGE.png"'/>
<span>Clear</span> <span>Clear</span>
</button> </button>
</td> </td>
@ -1109,7 +1109,7 @@
</table> </table>
</td> </td>
<td class="oe-binary-progress" style="display: none" nowrap="true"> <td class="oe-binary-progress" style="display: none" nowrap="true">
<img src="/web/static/src/img/throbber.gif" width="16" height="16"/> <img t-att-src='_s + "/web/static/src/img/throbber.gif"' width="16" height="16"/>
<b>Uploading ...</b> <b>Uploading ...</b>
<iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"> </iframe> <iframe t-att-id="widget.iframe" t-att-name="widget.iframe" style="display: none"> </iframe>
</td> </td>
@ -1118,7 +1118,7 @@
</t> </t>
<t t-name="WidgetButton"> <t t-name="WidgetButton">
<button type="button" class="oe_button"> <button type="button" class="oe_button">
<img t-if="widget.node.attrs.icon" t-att-src="'/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/> <img t-if="widget.node.attrs.icon" t-att-src="_s + '/web/static/src/img/icons/' + widget.node.attrs.icon + '.png'" width="16" height="16"/>
<span t-if="widget.string"><t t-esc="widget.string"/></span> <span t-if="widget.string"><t t-esc="widget.string"/></span>
</button> </button>
</t> </t>
@ -1211,7 +1211,7 @@
t-att-title="attrs.help" t-att-title="attrs.help"
t-att-class="classes.join(' ')" t-att-class="classes.join(' ')"
t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined"> t-att-autofocus="attrs.default_focus === '1' ? 'autofocus' : undefined">
<img t-att-src="'/web/static/src/img/icons/' + (attrs.icon || 'gtk-home') + '.png'" width="16" height="16"/> <img t-att-src="_s + '/web/static/src/img/icons/' + (attrs.icon || 'gtk-home') + '.png'" width="16" height="16"/>
<br t-if="attrs.string"/> <br t-if="attrs.string"/>
<t t-esc="attrs.string"/> <t t-esc="attrs.string"/>
</button> </button>
@ -1358,7 +1358,7 @@
<t t-name="DialogWarning"> <t t-name="DialogWarning">
<table cellspacing="0" cellpadding="0" border="0" class="oe-dialog-warning"> <table cellspacing="0" cellpadding="0" border="0" class="oe-dialog-warning">
<tr> <tr>
<td><img src="/web/static/src/img/warning.png" class="oe-dialog-icon"/></td> <td><img t-att-src='_s + "/web/static/src/img/warning.png"' class="oe-dialog-icon"/></td>
<td> <td>
<p> <p>
<t t-js="d"> <t t-js="d">
@ -1586,7 +1586,7 @@
<td valign="top" align="left" style="cursor: pointer;" width="18"> <td valign="top" align="left" style="cursor: pointer;" width="18">
<t t-if="field.children"> <t t-if="field.children">
<t t-if="(field.id).split('/').length != 3"> <t t-if="(field.id).split('/').length != 3">
<img t-att-id="'parentimg-' + field.id" src="/web/static/src/img/expand.gif" width="16" height="16" border="0"/> <img t-att-id="'parentimg-' + field.id" t-att-src='_s + "/web/static/src/img/expand.gif"' width="16" height="16" border="0"/>
</t> </t>
</t> </t>
</td> </td>
@ -1702,7 +1702,7 @@
<tr> <tr>
<td t-foreach="records[0]" t-as="column"> <td t-foreach="records[0]" t-as="column">
<input class="sel_fields" placeholder="--- Don't Import ---"/><span class="oe-m2o-drop-down-button"> <input class="sel_fields" placeholder="--- Don't Import ---"/><span class="oe-m2o-drop-down-button">
<img src="/web/static/src/img/down-arrow.png" /></span> <img t-att-src='_s + "/web/static/src/img/down-arrow.png"' /></span>
</td> </td>
</tr> </tr>
<tr t-foreach="records" t-as="record" class="oe_import_grid-row"> <tr t-foreach="records" t-as="record" class="oe_import_grid-row">

View File

@ -106,7 +106,7 @@ openerp.web_kanban.KanbanView = openerp.web.View.extend({
node.children = [{ node.children = [{
tag: 'img', tag: 'img',
attrs: { 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', width: '16',
height: '16' height: '16'
} }
@ -524,7 +524,7 @@ openerp.web_kanban.KanbanRecord = openerp.web.Widget.extend({
}, },
kanban_image: function(model, field, id) { kanban_image: function(model, field, id) {
id = 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) { kanban_text_ellipsis: function(s, size) {
size = size || 160; size = size || 160;