diff --git a/addons/web/common/http.py b/addons/web/common/http.py
index 71a280f27eb..9294808fda4 100644
--- a/addons/web/common/http.py
+++ b/addons/web/common/http.py
@@ -405,14 +405,14 @@ def session_context(request, storage_path, session_cookie='sessionid'):
#----------------------------------------------------------
addons_module = {}
addons_manifest = {}
-controllers_class = {}
+controllers_class = []
controllers_object = {}
controllers_path = {}
class ControllerType(type):
def __init__(cls, name, bases, attrs):
super(ControllerType, cls).__init__(name, bases, attrs)
- controllers_class["%s.%s" % (cls.__module__, cls.__name__)] = cls
+ controllers_class.append(("%s.%s" % (cls.__module__, cls.__name__), cls))
class Controller(object):
__metaclass__ = ControllerType
@@ -440,12 +440,12 @@ class Root(object):
self.root = '/web/webclient/home'
self.config = options
- if self.config.backend == 'local':
- conn = LocalConnector()
- else:
- conn = openerplib.get_connector(hostname=self.config.server_host,
- port=self.config.server_port)
- self.config.connector = conn
+ if not hasattr(self.config, 'connector'):
+ if self.config.backend == 'local':
+ self.config.connector = LocalConnector()
+ else:
+ self.config.connector = openerplib.get_connector(
+ hostname=self.config.server_host, port=self.config.server_port)
self.session_cookie = 'sessionid'
self.addons = {}
@@ -526,7 +526,7 @@ class Root(object):
addons_module[module] = m
addons_manifest[module] = manifest
statics['/%s/static' % module] = path_static
- for k, v in controllers_class.items():
+ for k, v in controllers_class:
if k not in controllers_object:
o = v()
controllers_object[k] = o
diff --git a/addons/web/controllers/main.py b/addons/web/controllers/main.py
index 1cdeb50cf25..9b4c8335e35 100644
--- a/addons/web/controllers/main.py
+++ b/addons/web/controllers/main.py
@@ -909,11 +909,6 @@ class Menu(openerpweb.Controller):
class DataSet(openerpweb.Controller):
_cp_path = "/web/dataset"
- @openerpweb.jsonrequest
- def fields(self, req, model):
- return {'fields': req.session.model(model).fields_get(False,
- req.session.eval_context(req.context))}
-
@openerpweb.jsonrequest
def search_read(self, req, model, fields=False, offset=0, limit=False, domain=None, sort=None):
return self.do_search_read(req, model, fields, offset, limit, domain, sort)
@@ -949,7 +944,6 @@ class DataSet(openerpweb.Controller):
if fields and fields == ['id']:
# shortcut read if we only want the ids
return {
- 'ids': ids,
'length': length,
'records': [{'id': id} for id in ids]
}
@@ -957,46 +951,10 @@ class DataSet(openerpweb.Controller):
records = Model.read(ids, fields or False, context)
records.sort(key=lambda obj: ids.index(obj['id']))
return {
- 'ids': ids,
'length': length,
'records': records
}
-
- @openerpweb.jsonrequest
- def read(self, req, model, ids, fields=False):
- return self.do_search_read(req, model, ids, fields)
-
- @openerpweb.jsonrequest
- def get(self, req, model, ids, fields=False):
- return self.do_get(req, model, ids, fields)
-
- def do_get(self, req, model, ids, fields=False):
- """ Fetches and returns the records of the model ``model`` whose ids
- are in ``ids``.
-
- The results are in the same order as the inputs, but elements may be
- missing (if there is no record left for the id)
-
- :param req: the JSON-RPC2 request object
- :type req: openerpweb.JsonRequest
- :param model: the model to read from
- :type model: str
- :param ids: a list of identifiers
- :type ids: list
- :param fields: a list of fields to fetch, ``False`` or empty to fetch
- all fields in the model
- :type fields: list | False
- :returns: a list of records, in the same order as the list of ids
- :rtype: list
- """
- Model = req.session.model(model)
- records = Model.read(ids, fields, req.session.eval_context(req.context))
-
- record_map = dict((record['id'], record) for record in records)
-
- return [record_map[id] for id in ids if record_map.get(id)]
-
@openerpweb.jsonrequest
def load(self, req, model, id, fields):
m = req.session.model(model)
@@ -1006,23 +964,6 @@ class DataSet(openerpweb.Controller):
value = r[0]
return {'value': value}
- @openerpweb.jsonrequest
- def create(self, req, model, data):
- m = req.session.model(model)
- r = m.create(data, req.session.eval_context(req.context))
- return {'result': r}
-
- @openerpweb.jsonrequest
- def save(self, req, model, id, data):
- m = req.session.model(model)
- r = m.write([id], data, req.session.eval_context(req.context))
- return {'result': r}
-
- @openerpweb.jsonrequest
- def unlink(self, req, model, ids=()):
- Model = req.session.model(model)
- return Model.unlink(ids, req.session.eval_context(req.context))
-
def call_common(self, req, model, method, args, domain_id=None, context_id=None):
has_domain = domain_id is not None and domain_id < len(args)
has_context = context_id is not None and context_id < len(args)
@@ -1098,19 +1039,7 @@ class DataSet(openerpweb.Controller):
@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):
- Model = req.session.model(model)
- return Model.default_get(fields, req.session.eval_context(req.context))
-
- @openerpweb.jsonrequest
- def name_search(self, req, model, search_str, domain=[], context={}):
- m = req.session.model(model)
- r = m.name_search(search_str+'%', domain, '=ilike', context)
- return {'result': r}
+ return req.session.exec_workflow(model, id, signal)
class DataGroup(openerpweb.Controller):
_cp_path = "/web/group"
diff --git a/addons/web/static/lib/qunit/qunit.css b/addons/web/static/lib/qunit/qunit.css
old mode 100755
new mode 100644
index bcecc4c0daf..58101ea34ce
--- a/addons/web/static/lib/qunit/qunit.css
+++ b/addons/web/static/lib/qunit/qunit.css
@@ -1,9 +1,9 @@
/**
- * QUnit v1.2.0 - A JavaScript Unit Testing Framework
+ * QUnit v1.4.0pre - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
- * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
@@ -54,6 +54,10 @@
color: #fff;
}
+#qunit-header label {
+ display: inline-block;
+}
+
#qunit-banner {
height: 5px;
}
@@ -223,4 +227,6 @@
position: absolute;
top: -10000px;
left: -10000px;
+ width: 1000px;
+ height: 1000px;
}
diff --git a/addons/web/static/lib/qunit/qunit.js b/addons/web/static/lib/qunit/qunit.js
old mode 100755
new mode 100644
index 6d2a8a7b8ab..b71381313c7
--- a/addons/web/static/lib/qunit/qunit.js
+++ b/addons/web/static/lib/qunit/qunit.js
@@ -1,9 +1,9 @@
/**
- * QUnit v1.2.0 - A JavaScript Unit Testing Framework
+ * QUnit v1.4.0pre - A JavaScript Unit Testing Framework
*
* http://docs.jquery.com/QUnit
*
- * Copyright (c) 2011 John Resig, Jörn Zaefferer
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
* Dual licensed under the MIT (MIT-LICENSE.txt)
* or GPL (GPL-LICENSE.txt) licenses.
*/
@@ -13,8 +13,11 @@
var defined = {
setTimeout: typeof window.setTimeout !== "undefined",
sessionStorage: (function() {
+ var x = "qunit-test-string";
try {
- return !!sessionStorage.getItem;
+ sessionStorage.setItem(x, x);
+ sessionStorage.removeItem(x);
+ return true;
} catch(e) {
return false;
}
@@ -25,11 +28,10 @@ var testId = 0,
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty;
-var Test = function(name, testName, expected, testEnvironmentArg, async, callback) {
+var Test = function(name, testName, expected, async, callback) {
this.name = name;
this.testName = testName;
this.expected = expected;
- this.testEnvironmentArg = testEnvironmentArg;
this.async = async;
this.callback = callback;
this.assertions = [];
@@ -62,6 +64,10 @@ Test.prototype = {
runLoggingCallbacks( 'moduleStart', QUnit, {
name: this.module
} );
+ } else if (config.autorun) {
+ runLoggingCallbacks( 'moduleStart', QUnit, {
+ name: this.module
+ } );
}
config.current = this;
@@ -69,9 +75,6 @@ Test.prototype = {
setup: function() {},
teardown: function() {}
}, this.moduleTestEnvironment);
- if (this.testEnvironmentArg) {
- extend(this.testEnvironment, this.testEnvironmentArg);
- }
runLoggingCallbacks( 'testStart', QUnit, {
name: this.testName,
@@ -274,17 +277,12 @@ var QUnit = {
},
test: function(testName, expected, callback, async) {
- var name = '' + testName + '', testEnvironmentArg;
+ var name = '' + escapeInnerText(testName) + '';
if ( arguments.length === 2 ) {
callback = expected;
expected = null;
}
- // is 2nd argument a testEnvironment?
- if ( expected && typeof expected === 'object') {
- testEnvironmentArg = expected;
- expected = null;
- }
if ( config.currentModule ) {
name = '' + config.currentModule + ": " + name;
@@ -294,7 +292,7 @@ var QUnit = {
return;
}
- var test = new Test(name, testName, expected, testEnvironmentArg, async, callback);
+ var test = new Test(name, testName, expected, async, callback);
test.module = config.currentModule;
test.moduleTestEnvironment = config.currentModuleTestEnviroment;
test.queue();
@@ -312,6 +310,9 @@ var QUnit = {
* @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
*/
ok: function(a, msg) {
+ if (!config.current) {
+ throw new Error("ok() assertion outside test context, was " + sourceFromStacktrace(2));
+ }
a = !!a;
var details = {
result: a,
@@ -447,9 +448,14 @@ var QUnit = {
QUnit.constructor = F;
})();
-// Backwards compatibility, deprecated
-QUnit.equals = QUnit.equal;
-QUnit.same = QUnit.deepEqual;
+// deprecated; still export them to window to provide clear error messages
+// next step: remove entirely
+QUnit.equals = function() {
+ throw new Error("QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead");
+};
+QUnit.same = function() {
+ throw new Error("QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead");
+};
// Maintain internal state
var config = {
@@ -513,8 +519,7 @@ if ( typeof exports === "undefined" || typeof require === "undefined" ) {
extend(window, QUnit);
window.QUnit = QUnit;
} else {
- extend(exports, QUnit);
- exports.QUnit = QUnit;
+ module.exports = QUnit;
}
// define these after exposing globals to keep them in these QUnit namespace only
@@ -536,6 +541,16 @@ extend(QUnit, {
semaphore: 0
});
+ var qunit = id( "qunit" );
+ if ( qunit ) {
+ qunit.innerHTML =
+ '
' +
+ '' +
+ '' +
+ '' +
+ '
';
+ }
+
var tests = id( "qunit-tests" ),
banner = id( "qunit-banner" ),
result = id( "qunit-testresult" );
@@ -564,15 +579,15 @@ extend(QUnit, {
/**
* Resets the test setup. Useful for tests that modify the DOM.
*
- * If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
+ * If jQuery is available, uses jQuery's replaceWith(), otherwise use replaceChild
*/
reset: function() {
- if ( window.jQuery ) {
- jQuery( "#qunit-fixture" ).html( config.fixture );
- } else {
- var main = id( 'qunit-fixture' );
- if ( main ) {
- main.innerHTML = config.fixture;
+ var main = id( 'qunit-fixture' );
+ if ( main ) {
+ if ( window.jQuery ) {
+ jQuery( main ).replaceWith( config.fixture.cloneNode(true) );
+ } else {
+ main.parentNode.replaceChild(config.fixture.cloneNode(true), main);
}
}
},
@@ -636,6 +651,9 @@ extend(QUnit, {
},
push: function(result, actual, expected, message) {
+ if (!config.current) {
+ throw new Error("assertion outside test context, was " + sourceFromStacktrace());
+ }
var details = {
result: result,
message: message,
@@ -645,21 +663,22 @@ extend(QUnit, {
message = escapeInnerText(message) || (result ? "okay" : "failed");
message = '' + message + "";
- expected = escapeInnerText(QUnit.jsDump.parse(expected));
- actual = escapeInnerText(QUnit.jsDump.parse(actual));
- var output = message + 'Expected: | ' + expected + ' |
';
- if (actual != expected) {
- output += 'Result: | ' + actual + ' |
';
- output += 'Diff: | ' + QUnit.diff(expected, actual) +' |
';
- }
+ var output = message;
if (!result) {
+ expected = escapeInnerText(QUnit.jsDump.parse(expected));
+ actual = escapeInnerText(QUnit.jsDump.parse(actual));
+ output += 'Expected: | ' + expected + ' |
';
+ if (actual != expected) {
+ output += 'Result: | ' + actual + ' |
';
+ output += 'Diff: | ' + QUnit.diff(expected, actual) +' |
';
+ }
var source = sourceFromStacktrace();
if (source) {
details.source = source;
output += 'Source: | ' + escapeInnerText(source) + ' |
';
}
+ output += "
";
}
- output += "
";
runLoggingCallbacks( 'log', QUnit, details );
@@ -779,7 +798,7 @@ QUnit.load = function() {
var main = id('qunit-fixture');
if ( main ) {
- config.fixture = main.innerHTML;
+ config.fixture = main.cloneNode(true);
}
if (config.autostart) {
@@ -847,6 +866,15 @@ function done() {
].join(" ");
}
+ // clear own sessionStorage items if all tests passed
+ if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+ for (var key in sessionStorage) {
+ if (sessionStorage.hasOwnProperty(key) && key.indexOf("qunit-") === 0 ) {
+ sessionStorage.removeItem(key);
+ }
+ }
+ }
+
runLoggingCallbacks( 'done', QUnit, {
failed: config.stats.bad,
passed: passed,
@@ -881,16 +909,21 @@ function validTest( name ) {
// so far supports only Firefox, Chrome and Opera (buggy)
// could be extended in the future to use something like https://github.com/csnover/TraceKit
-function sourceFromStacktrace() {
+function sourceFromStacktrace(offset) {
+ offset = offset || 3;
try {
throw new Error();
} catch ( e ) {
if (e.stacktrace) {
// Opera
- return e.stacktrace.split("\n")[6];
+ return e.stacktrace.split("\n")[offset + 3];
} else if (e.stack) {
// Firefox, Chrome
- return e.stack.split("\n")[4];
+ var stack = e.stack.split("\n");
+ if (/^error$/i.test(stack[0])) {
+ stack.shift();
+ }
+ return stack[offset];
} else if (e.sourceURL) {
// Safari, PhantomJS
// TODO sourceURL points at the 'throw new Error' line above, useless
@@ -989,6 +1022,7 @@ function fail(message, exception, callback) {
if ( typeof console !== "undefined" && console.error && console.warn ) {
console.error(message);
console.error(exception);
+ console.error(exception.stack);
console.warn(callback.toString());
} else if ( window.opera && opera.postError ) {
@@ -1368,9 +1402,9 @@ QUnit.jsDump = (function() {
var ret = [ ];
QUnit.jsDump.up();
for ( var key in map ) {
- var val = map[key];
+ var val = map[key];
ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(val, undefined, stack));
- }
+ }
QUnit.jsDump.down();
return join( '{', ret, '}' );
},
@@ -1594,4 +1628,5 @@ QUnit.diff = (function() {
};
})();
-})(this);
+// get at whatever the global object is, like window in browsers
+})( (function() {return this}).call() );
diff --git a/addons/web/static/src/js/corelib.js b/addons/web/static/src/js/corelib.js
index 5475f4ad53c..cbd28aca8a3 100644
--- a/addons/web/static/src/js/corelib.js
+++ b/addons/web/static/src/js/corelib.js
@@ -1390,6 +1390,7 @@ instance.web.Connection = instance.web.CallbackEnabled.extend( /** @lends instan
// an invalid session or no session at all), refresh session data
// (should not change, but just in case...)
_.extend(self, {
+ session_id: result.session_id,
db: result.db,
username: result.login,
uid: result.uid,
diff --git a/addons/web/static/src/js/data.js b/addons/web/static/src/js/data.js
index 4f18a4c53d9..2dc66aaa135 100644
--- a/addons/web/static/src/js/data.js
+++ b/addons/web/static/src/js/data.js
@@ -18,7 +18,428 @@ instance.web.serialize_sort = function (criterion) {
}).join(', ');
};
-instance.web.DataGroup = instance.web.OldWidget.extend( /** @lends instance.web.DataGroup# */{
+instance.web.Query = instance.web.Class.extend({
+ init: function (model, fields) {
+ this._model = model;
+ this._fields = fields;
+ this._filter = [];
+ this._context = {};
+ this._limit = false;
+ this._offset = 0;
+ this._order_by = [];
+ },
+ clone: function (to_set) {
+ to_set = to_set || {};
+ var q = new instance.web.Query(this._model, this._fields);
+ q._context = this._context;
+ q._filter = this._filter;
+ q._limit = this._limit;
+ q._offset = this._offset;
+ q._order_by = this._order_by;
+
+ for(var key in to_set) {
+ if (!to_set.hasOwnProperty(key)) { continue; }
+ switch(key) {
+ case 'filter':
+ q._filter = new instance.web.CompoundDomain(
+ q._filter, to_set.filter);
+ break;
+ case 'context':
+ q._context = new instance.web.CompoundContext(
+ q._context, to_set.context);
+ break;
+ case 'limit':
+ case 'offset':
+ case 'order_by':
+ q['_' + key] = to_set[key];
+ }
+ }
+ return q;
+ },
+ _execute: function () {
+ var self = this;
+ return instance.connection.rpc('/web/dataset/search_read', {
+ model: this._model.name,
+ fields: this._fields || false,
+ domain: this._model.domain(this._filter),
+ context: this._model.context(this._context),
+ offset: this._offset,
+ limit: this._limit,
+ sort: instance.web.serialize_sort(this._order_by)
+ }).pipe(function (results) {
+ self._count = results.length;
+ return results.records;
+ }, null);
+ },
+ /**
+ * Fetches the first record matching the query, or null
+ *
+ * @returns {jQuery.Deferred