diff --git a/.bzrignore b/.bzrignore index 6e9359571f4..b83a43bf1d4 100644 --- a/.bzrignore +++ b/.bzrignore @@ -1,12 +1,11 @@ -RE:lib/(?!populate.sh).* .* *.egg-info *.orig -bin/ build/ -dist/ -include/ +RE:^bin/ +RE:^dist/ +RE:^include/ -share/ -man/ -lib/ +RE:^share/ +RE:^man/ +RE:^lib/ diff --git a/addons/base/__openerp__.py b/addons/base/__openerp__.py index 2d5ecfb99ef..faada1fda70 100644 --- a/addons/base/__openerp__.py +++ b/addons/base/__openerp__.py @@ -22,7 +22,7 @@ "static/lib/underscore/underscore.string.js", "static/src/js/boot.js", "static/src/js/core.js", - "static/src/js/dates.js", + "static/src/js/formats.js", "static/src/js/chrome.js", "static/src/js/views.js", "static/src/js/data.js", diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 214062493f3..08ef89b2cdf 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -391,10 +391,10 @@ def load_actions_from_ir_values(req, key, key2, models, meta, context): Values = req.session.model('ir.values') actions = Values.get(key, key2, models, meta, context) - return [(id, name, clean_action(action, req.session)) + return [(id, name, clean_action(action, req.session, context=context)) for id, name, action in actions] -def clean_action(action, session): +def clean_action(action, session, context=None): action.setdefault('flags', {}) if action['type'] != 'ir.actions.act_window': return action @@ -402,7 +402,7 @@ def clean_action(action, session): if isinstance(action.get('context'), basestring): action['context'] = eval( action['context'], - session.evaluation_context()) or {} + session.evaluation_context(context=context)) or {} if isinstance(action.get('domain'), basestring): action['domain'] = eval( @@ -560,6 +560,7 @@ class DataSet(openerpweb.Controller): :rtype: list """ Model = request.session.model(model) + context, domain = eval_context_and_domain( request.session, request.context, domain) @@ -582,9 +583,14 @@ class DataSet(openerpweb.Controller): } + @openerpweb.jsonrequest + def read(self, request, model, ids, fields=False): + return self.do_search_read(request, model, ids, fields) + @openerpweb.jsonrequest def get(self, request, model, ids, fields=False): return self.do_get(request, model, ids, fields) + def do_get(self, request, model, ids, fields=False): """ Fetches and returns the records of the model ``model`` whose ids are in ``ids``. @@ -669,6 +675,12 @@ class DataSet(openerpweb.Controller): 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} + class DataGroup(openerpweb.Controller): _cp_path = "/base/group" @openerpweb.jsonrequest @@ -761,8 +773,8 @@ class View(openerpweb.Controller): """ Parses an arbitrary string containing a domain, transforms it to either a literal domain or a :class:`openerpweb.nonliterals.Domain` - :param domain: the domain to parse, if the domain is not a string it is assumed to - be a literal domain and is returned as-is + :param domain: the domain to parse, if the domain is not a string it + is assumed to be a literal domain and is returned as-is :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ @@ -778,8 +790,8 @@ class View(openerpweb.Controller): """ Parses an arbitrary string containing a context, transforms it to either a literal context or a :class:`openerpweb.nonliterals.Context` - :param context: the context to parse, if the context is not a string it is assumed to - be a literal domain and is returned as-is + :param context: the context to parse, if the context is not a string it + is assumed to be a literal domain and is returned as-is :param session: Current OpenERP session :type session: openerpweb.openerpweb.OpenERPSession """ @@ -1002,6 +1014,19 @@ class Action(openerpweb.Controller): return clean_action(req.session.model('ir.actions.server').run( [action_id], req.session.eval_context(req.context)), req.session) +class TreeView(View): + _cp_path = "/base/treeview" + + @openerpweb.jsonrequest + def load(self, req, model, view_id, toolbar=False): + return self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar) + + @openerpweb.jsonrequest + def action(self, req, model, id): + return load_actions_from_ir_values( + req,'action', 'tree_but_open',[(model, id)], + False, req.session.eval_context(req.context)) + def export_csv(fields, result): fp = StringIO() writer = csv.writer(fp, quoting=csv.QUOTE_ALL) diff --git a/addons/base/static/lib/qweb/qweb2.js b/addons/base/static/lib/qweb/qweb2.js index 7430464e2d8..89a77bf1b85 100644 --- a/addons/base/static/lib/qweb/qweb2.js +++ b/addons/base/static/lib/qweb/qweb2.js @@ -1,5 +1,7 @@ // TODO: trim support // TODO: line number -> https://bugzilla.mozilla.org/show_bug.cgi?id=618650 +// TODO: templates orverwritten could be called by t-call="__super__" ? +// TODO: t-set + t-value + children node == scoped variable ? var QWeb2 = { expressions_cache: {}, reserved_words: 'true,false,NaN,null,undefined,debugger,in,instanceof,new,function,return,this,typeof,eval,Math,RegExp,Array,Object,Date'.split(','), @@ -21,6 +23,11 @@ var QWeb2 = { } throw new Error(prefix + ": " + message); }, + warning : function(message) { + if (typeof(window) !== 'undefined' && window.console) { + window.console.warn(message); + } + }, trim: function(s, mode) { switch (mode) { case "left": @@ -638,6 +645,9 @@ QWeb2.Element = (function() { compile_action_set : function(value) { var variable = this.format_expression(value); if (this.actions['value']) { + if (this.children.length) { + this.engine.tools.warning("@set with @value plus node chidren found. Children are ignored."); + } this.top(variable + " = (" + (this.format_expression(this.actions['value'])) + ");"); this.process_children = false; } else { diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 7f670be071f..13294819f6b 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -269,7 +269,7 @@ label.error { border-radius: 4px; -moz-border-radius: 4px; -webkit-border-radius: 4px; - + color: #eee; text-shadow: #222 0 1px 0; text-decoration: none; @@ -923,11 +923,11 @@ label.error { height: 15px; } .openerp td.required input, .openerp td.required select { - background-color: #D2D2FF; + background-color: #D2D2FF !important; } .openerp td.invalid input, .openerp td.invalid select, .openerp td.invalid textarea { - background-color: #F66; - border: 1px solid #D00; + background-color: #F66 !important; + border: 1px solid #D00 !important; } .openerp div.oe-progressbar span { position: absolute; @@ -1053,7 +1053,7 @@ label.error { padding-left: 10px; padding-right: 21px; height: 21px; - + background: #ffffff; /* Old browsers */ background: -moz-linear-gradient(top, #ffffff 0%, #ebe9e9 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ffffff), color-stop(100%,#ebe9e9)); /* Chrome,Safari4+ */ @@ -1062,7 +1062,7 @@ background: -o-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* Opera11.10+ */ background: -ms-linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* IE10+ */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#FFFFFF', endColorstr='#EBE9E9',GradientType=0 ); /* IE6-9 */ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */ - + border: 1px solid #D2CFCF; border-right-width: 0; border-left-width: 0; @@ -1167,7 +1167,9 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */ .openerp .oe-m2o-disabled-cm { color: grey; } - +.parent_top { + vertical-align: text-top; +} .openerp .oe-dialog-warning p { padding-left: 1em; font-size: 1.2em; @@ -1180,3 +1182,30 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */ -webkit-box-shadow: none; box-shadow: none; } + +.openerp .oe-treeview-table { + width: 100%; +} +.treeview-tr, .treeview-td { + cursor: pointer; + vertical-align: top; + text-align: left; +} +.treeview-tr span, .treeview-td span { + display: block; +} +.treeview-tr:first-of-type { + background: transparent url(/base/static/src/img/expand.gif) 0 50% no-repeat; +} +.oe-open .treeview-tr:first-of-type { + background-image: url(/base/static/src/img/collapse.gif); +} +.treeview-tr:first-of-type span, +.treeview-td:first-of-type span { + margin-left: 16px; +} + +.treeview-header { + text-align: left; + vertical-align: top; +} diff --git a/addons/base/static/src/js/boot.js b/addons/base/static/src/js/boot.js index 5a8d0b84576..13b3b24b1bb 100644 --- a/addons/base/static/src/js/boot.js +++ b/addons/base/static/src/js/boot.js @@ -1,6 +1,6 @@ -//--------------------------------------------------------- -// OpenERP Web Boostrap -//--------------------------------------------------------- +/*--------------------------------------------------------- + * OpenERP Web Boostrap Code + *---------------------------------------------------------*/ /** * @name openerp @@ -14,13 +14,8 @@ /** @lends openerp */ var openerp = this.openerp = { - /** - * Debug flag turns on logging - */ + // debug flag debug: true, - // element_ids registry linked to all controllers on the page - // TODO rename to elements, or keep gtk naming? - screen: {}, // Per session namespace // openerp. will map to // openerp.sessions.sessionname. using a closure @@ -40,8 +35,6 @@ // this unique id will be replaced by hostname_databasename by // openerp.base.Connection on the first connection _session_id: "session" + session_counter++, - screen: openerp.screen, - sessions: openerp.sessions, base: {}, web_mobile: {} }; @@ -51,16 +44,17 @@ } return new_instance; } + // TODO add initrpc to init core only for RPC }; })(); -//--------------------------------------------------------- -// OpenERP base module split -//--------------------------------------------------------- +/*--------------------------------------------------------- + * OpenERP Web base module split + *---------------------------------------------------------*/ openerp.base = function(instance) { openerp.base.core(instance); - openerp.base.dates(instance); + openerp.base.formats(instance); openerp.base.chrome(instance); openerp.base.data(instance); if (openerp.base.views) { diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 3ac161f70de..1293f37c41a 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -1,285 +1,9 @@ /*--------------------------------------------------------- - * OpenERP base library + * OpenERP Web chrome *---------------------------------------------------------*/ openerp.base.chrome = function(openerp) { -openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{ - /** - * @constructs - * @param element_id to use for exception reporting - * @param server - * @param port - */ - init: function(parent, element_id, server, port) { - this._super(parent, element_id); - this.server = (server == undefined) ? location.hostname : server; - this.port = (port == undefined) ? location.port : port; - this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; - this.debug = true; - this.db = ""; - this.login = ""; - this.password = ""; - this.uid = false; - this.session_id = false; - this.module_list = []; - this.module_loaded = {"base": true}; - this.context = {}; - }, - start: function() { - this.session_restore(); - }, - /** - * Executes an RPC call, registering the provided callbacks. - * - * Registers a default error callback if none is provided, and handles - * setting the correct session id and session context in the parameter - * objects - * - * @param {String} url RPC endpoint - * @param {Object} params call parameters - * @param {Function} success_callback function to execute on RPC call success - * @param {Function} error_callback function to execute on RPC call failure - * @returns {jQuery.Deferred} jquery-provided ajax deferred - */ - rpc: function(url, params, success_callback, error_callback) { - var self = this; - // Construct a JSON-RPC2 request, method is currently unused - params.session_id = this.session_id; - - // Call using the rpc_mode - var deferred = $.Deferred(); - this.rpc_ajax(url, { - jsonrpc: "2.0", - method: "call", - params: params, - id:null - }).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-based 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(); - $.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] } - }; - deferred.reject(error); - }); - return deferred.promise(); - }, - on_rpc_request: function() { - }, - on_rpc_response: function() { - }, - on_rpc_error: function(error) { - }, - /** - * The session is validated either by login or by restoration of a previous session - */ - on_session_valid: function() { - if(!openerp._modules_loaded) - this.load_modules(); - }, - on_session_invalid: function(contination) { - }, - session_is_valid: function() { - return this.uid; - }, - session_login: function(db, login, password, success_callback) { - var self = this; - this.db = db; - this.login = login; - this.password = password; - var params = { db: this.db, login: this.login, password: this.password }; - this.rpc("/base/session/login", params, function(result) { - self.session_id = result.session_id; - self.uid = result.uid; - self.session_save(); - self.on_session_valid(); - if (success_callback) - success_callback(); - }); - }, - session_logout: function() { - this.uid = false; - }, - /** - * Reloads uid and session_id from local storage, if they exist - */ - session_restore: function () { - this.uid = this.get_cookie('uid'); - this.session_id = this.get_cookie('session_id'); - this.db = this.get_cookie('db'); - this.login = this.get_cookie('login'); - // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login - // then call on_session_valid - this.on_session_valid(); - }, - /** - * Saves the session id and uid locally - */ - session_save: function () { - this.set_cookie('uid', this.uid); - this.set_cookie('session_id', this.session_id); - this.set_cookie('db', this.db); - this.set_cookie('login', this.login); - }, - logout: function() { - delete this.uid; - delete this.session_id; - delete this.db; - delete this.login; - this.set_cookie('uid', ''); - this.set_cookie('session_id', ''); - this.set_cookie('db', ''); - this.set_cookie('login', ''); - this.on_session_invalid(function() {}); - }, - /** - * Fetches a cookie stored by an openerp session - * - * @private - * @param name the cookie's name - */ - get_cookie: function (name) { - var nameEQ = this.element_id + '|' + name + '='; - var cookies = document.cookie.split(';'); - for(var i=0; i').dialog(this.dialog_options); - if (auto_open !== false) { - this.open(); - } this._super(); + return this; }, open: function(dialog_options) { // TODO fme: bind window on resize @@ -949,12 +669,10 @@ openerp.base.Homepage = openerp.base.Widget.extend({ openerp.base.Preferences = openerp.base.Widget.extend({ }); -openerp.base.ImportExport = openerp.base.Widget.extend({ -}); - openerp.base.WebClient = openerp.base.Widget.extend({ init: function(element_id) { this._super(null, element_id); + openerp.webclient = this; QWeb.add_template("/base/static/src/xml/base.xml"); var params = {}; @@ -966,7 +684,7 @@ openerp.base.WebClient = openerp.base.Widget.extend({ this.session = new openerp.base.Session(this,"oe_errors"); this.loading = new openerp.base.Loading(this,"oe_loading"); this.crashmanager = new openerp.base.CrashManager(this); - this.crashmanager.start(false); + this.crashmanager.start(); // Do you autorize this ? will be replaced by notify() in controller openerp.base.Widget.prototype.notification = new openerp.base.Notification(this, "oe_notification"); @@ -991,8 +709,10 @@ openerp.base.WebClient = openerp.base.Widget.extend({ this.notification.notify("OpenERP Client", "The openerp client has been initialized."); }, on_logged: function() { - this.action_manager = new openerp.base.ActionManager(this, "oe_app"); - this.action_manager.start(); + if(this.action_manager) + this.action_manager.stop(); + this.action_manager = new openerp.base.ActionManager(this); + this.action_manager.appendTo($("#oe_app")); // if using saved actions, load the action and give it to action manager var parameters = jQuery.deparam(jQuery.param.querystring()); @@ -1032,7 +752,8 @@ openerp.base.WebClient = openerp.base.Widget.extend({ self.execute_home_action(home_action[0], ds); }) }, - default_home: function () { }, + default_home: function () { + }, /** * Bundles the execution of the home action * diff --git a/addons/base/static/src/js/core.js b/addons/base/static/src/js/core.js index 80dba3cd10c..da9b5abf3e8 100644 --- a/addons/base/static/src/js/core.js +++ b/addons/base/static/src/js/core.js @@ -1,5 +1,5 @@ /*--------------------------------------------------------- - * OpenERP controller framework + * OpenERP Web core *--------------------------------------------------------*/ openerp.base.core = function(openerp) { @@ -373,59 +373,6 @@ openerp.base.SessionAware = openerp.base.Class.extend({ } }); -/** - * Base class for all visual components. Provides a lot of functionalities helpful - * for the management of a part of the DOM. - * - * Widget handles: - * - Rendering with QWeb. - * - Life-cycle management and parenting (when a parent is destroyed, all its children are - * destroyed too). - * - Insertion in DOM. - * - * Widget also extends SessionAware for ease of use. - * - * Guide to create implementations of the Widget class: - * ============================================== - * - * Here is a sample child class: - * - * MyWidget = openerp.base.Widget.extend({ - * // the name of the QWeb template to use for rendering - * template: "MyQWebTemplate", - * // identifier prefix, it is useful to put an obvious one for debugging - * identifier_prefix: 'my-id-prefix-', - * - * init: function(parent) { - * this._super(parent); - * // stuff that you want to init before the rendering - * }, - * start: function() { - * this._super(); - * // stuff you want to make after the rendering, `this.$element` holds a correct value - * this.$element.find(".my_button").click(/* an example of event binding * /); - * - * // if you have some asynchronous operations, it's a good idea to return - * // a promise in start() - * var promise = this.rpc(...); - * return promise; - * } - * }); - * - * Now this class can simply be used with the following syntax: - * - * var my_widget = new MyWidget(this); - * my_widget.appendTo($(".some-div")); - * - * With these two lines, the MyWidget instance was inited, rendered, it was inserted into the - * DOM inside the ".some-div" div and its events were binded. - * - * And of course, when you don't need that widget anymore, just do: - * - * my_widget.stop(); - * - * That will kill the widget in a clean way and erase its content from the dom. - */ openerp.base.Widget = openerp.base.SessionAware.extend({ /** * The name of the QWeb template that will be used for rendering. Must be @@ -574,6 +521,18 @@ openerp.base.Widget = openerp.base.SessionAware.extend({ } return false; }, + do_notify: function() { + if (this.widget_parent) { + return this.widget_parent.do_notify.apply(this,arguments); + } + return false; + }, + do_warn: function() { + if (this.widget_parent) { + return this.widget_parent.do_warn.apply(this,arguments); + } + return false; + }, rpc: function(url, data, success, error) { var def = $.Deferred().then(success, error); var self = this; @@ -599,5 +558,282 @@ openerp.base.OldWidget = openerp.base.Widget.extend({ } }); +openerp.base.Session = openerp.base.Widget.extend( /** @lends openerp.base.Session# */{ + /** + * @constructs + * @param element_id to use for exception reporting + * @param server + * @param port + */ + init: function(parent, element_id, server, port) { + this._super(parent, element_id); + this.server = (server == undefined) ? location.hostname : server; + this.port = (port == undefined) ? location.port : port; + this.rpc_mode = (server == location.hostname) ? "ajax" : "jsonp"; + this.debug = true; + this.db = ""; + this.login = ""; + this.password = ""; + this.uid = false; + this.session_id = false; + this.module_list = []; + this.module_loaded = {"base": true}; + this.context = {}; + }, + start: function() { + this.session_restore(); + }, + /** + * Executes an RPC call, registering the provided callbacks. + * + * Registers a default error callback if none is provided, and handles + * setting the correct session id and session context in the parameter + * objects + * + * @param {String} url RPC endpoint + * @param {Object} params call parameters + * @param {Function} success_callback function to execute on RPC call success + * @param {Function} error_callback function to execute on RPC call failure + * @returns {jQuery.Deferred} jquery-provided ajax deferred + */ + rpc: function(url, params, success_callback, error_callback) { + var self = this; + // Construct a JSON-RPC2 request, method is currently unused + params.session_id = this.session_id; + + // Call using the rpc_mode + var deferred = $.Deferred(); + this.rpc_ajax(url, { + jsonrpc: "2.0", + method: "call", + params: params, + id:null + }).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-based 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(); + $.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] } + }; + deferred.reject(error); + }); + return deferred.promise(); + }, + on_rpc_request: function() { + }, + on_rpc_response: function() { + }, + on_rpc_error: function(error) { + }, + /** + * The session is validated either by login or by restoration of a previous session + */ + on_session_valid: function() { + if(!openerp._modules_loaded) + this.load_modules(); + }, + on_session_invalid: function(contination) { + }, + session_is_valid: function() { + return this.uid; + }, + session_login: function(db, login, password, success_callback) { + var self = this; + this.db = db; + this.login = login; + this.password = password; + var params = { db: this.db, login: this.login, password: this.password }; + this.rpc("/base/session/login", params, function(result) { + self.session_id = result.session_id; + self.uid = result.uid; + self.session_save(); + self.on_session_valid(); + if (success_callback) + success_callback(); + }); + }, + session_logout: function() { + this.uid = false; + }, + /** + * Reloads uid and session_id from local storage, if they exist + */ + session_restore: function () { + this.uid = this.get_cookie('uid'); + this.session_id = this.get_cookie('session_id'); + this.db = this.get_cookie('db'); + this.login = this.get_cookie('login'); + // we should do an rpc to confirm that this session_id is valid and if it is retrieve the information about db and login + // then call on_session_valid + this.on_session_valid(); + }, + /** + * Saves the session id and uid locally + */ + session_save: function () { + this.set_cookie('uid', this.uid); + this.set_cookie('session_id', this.session_id); + this.set_cookie('db', this.db); + this.set_cookie('login', this.login); + }, + logout: function() { + delete this.uid; + delete this.session_id; + delete this.db; + delete this.login; + this.set_cookie('uid', ''); + this.set_cookie('session_id', ''); + this.set_cookie('db', ''); + this.set_cookie('login', ''); + this.on_session_invalid(function() {}); + }, + /** + * Fetches a cookie stored by an openerp session + * + * @private + * @param name the cookie's name + */ + get_cookie: function (name) { + var nameEQ = this.element_id + '|' + name + '='; + var cookies = document.cookie.split(';'); + for(var i=0; i= limit) { - // TODO: check if this could work do only read if possible - // return read_ids(ids.slice(start,start+limit),fields,callback) - } - } + var offset = options.offset || 0; return this.rpc('/base/dataset/search_read', { model: this.model, - fields: fields, - domain: this.domain, - context: this.get_context(), + fields: options.fields || false, + domain: this.get_domain(options.domain), + context: this.get_context(options.context), sort: this.sort(), offset: offset, - limit: limit + limit: options.limit || false }, function (result) { self.ids = result.ids; self.offset = offset; @@ -453,6 +551,12 @@ openerp.base.DataSetSearch = openerp.base.DataSet.extend({ } }); }, + get_domain: function (other_domain) { + if (other_domain) { + return new openerp.base.CompoundDomain(this.domain, other_domain); + } + return this.domain; + }, /** * Reads or changes sort criteria on the dataset. * @@ -628,48 +732,51 @@ openerp.base.ReadOnlyDataSetSearch = openerp.base.DataSetSearch.extend({ on_unlink: function(ids) {} }); -openerp.base.CompoundContext = function() { - this.__ref = "compound_context"; - this.__contexts = []; - this.__eval_context = null; - var self = this; - _.each(arguments, function(x) { - self.add(x); - }); -}; -openerp.base.CompoundContext.prototype.add = function(context) { - this.__contexts.push(context); - return this; -}; -openerp.base.CompoundContext.prototype.set_eval_context = function(eval_context) { - this.__eval_context = eval_context; - return this; -}; -openerp.base.CompoundContext.prototype.get_eval_context = function() { - return this.__eval_context; -}; - -openerp.base.CompoundDomain = function() { - this.__ref = "compound_domain"; - this.__domains = []; - this.__eval_context = null; - var self = this; - _.each(arguments, function(x) { - self.add(x); - }); -}; -openerp.base.CompoundDomain.prototype.add = function(domain) { - this.__domains.push(domain); - return this; -}; -openerp.base.CompoundDomain.prototype.set_eval_context = function(eval_context) { - this.__eval_context = eval_context; - return this; -}; -openerp.base.CompoundDomain.prototype.get_eval_context = function() { - return this.__eval_context; -}; +openerp.base.CompoundContext = openerp.base.Class.extend({ + init: function () { + this.__ref = "compound_context"; + this.__contexts = []; + this.__eval_context = null; + var self = this; + _.each(arguments, function(x) { + self.add(x); + }); + }, + add: function (context) { + this.__contexts.push(context); + return this; + }, + set_eval_context: function (eval_context) { + this.__eval_context = eval_context; + return this; + }, + get_eval_context: function () { + return this.__eval_context; + } +}); +openerp.base.CompoundDomain = openerp.base.Class.extend({ + init: function () { + this.__ref = "compound_domain"; + this.__domains = []; + this.__eval_context = null; + var self = this; + _.each(arguments, function(x) { + self.add(x); + }); + }, + add: function(domain) { + this.__domains.push(domain); + return this; + }, + set_eval_context: function(eval_context) { + this.__eval_context = eval_context; + return this; + }, + get_eval_context: function() { + return this.__eval_context; + } +}); }; // vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax: diff --git a/addons/base/static/src/js/dates.js b/addons/base/static/src/js/dates.js deleted file mode 100644 index 8daea39773f..00000000000 --- a/addons/base/static/src/js/dates.js +++ /dev/null @@ -1,137 +0,0 @@ - -openerp.base.dates = function(openerp) { - -/** - * Converts a string to a Date javascript object using OpenERP's - * datetime string format (exemple: '2011-12-01 15:12:35'). - * - * The timezone is assumed to be UTC (standard for OpenERP 6.1) - * and will be converted to the browser's timezone. - * - * @param {String} str A string representing a datetime. - * @returns {Date} - */ -openerp.base.parse_datetime = function(str) { - if(!str) { - return str; - } - var regex = /\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/; - var res = regex.exec(str); - if ( res[0] != str ) { - throw "'" + str + "' is not a valid datetime"; - } - var obj = Date.parse(str + " GMT"); - if (! obj) { - throw "'" + str + "' is not a valid datetime"; - } - return obj; -}; - -/** - * Converts a string to a Date javascript object using OpenERP's - * date string format (exemple: '2011-12-01'). - * - * @param {String} str A string representing a date. - * @returns {Date} - */ -openerp.base.parse_date = function(str) { - if(!str) { - return str; - } - var regex = /\d\d\d\d-\d\d-\d\d/; - var res = regex.exec(str); - if ( res[0] != str ) { - throw "'" + str + "' is not a valid date"; - } - var obj = Date.parse(str); - if (! obj) { - throw "'" + str + "' is not a valid date"; - } - return obj; -}; - -/** - * Converts a string to a Date javascript object using OpenERP's - * time string format (exemple: '15:12:35'). - * - * @param {String} str A string representing a time. - * @returns {Date} - */ -openerp.base.parse_time = function(str) { - if(!str) { - return str; - } - var regex = /\d\d:\d\d:\d\d/; - var res = regex.exec(str); - if ( res[0] != str ) { - throw "'" + str + "' is not a valid time"; - } - var obj = Date.parse(str); - if (! obj) { - throw "'" + str + "' is not a valid time"; - } - return obj; -}; - -/* - * Just a simple function to add some '0' if an integer it too small. - */ -var fts = function(str, size) { - str = "" + str; - var to_add = ""; - _.each(_.range(size - str.length), function() { - to_add = to_add + "0"; - }); - return to_add + str; -}; - -/** - * Converts a Date javascript object to a string using OpenERP's - * datetime string format (exemple: '2011-12-01 15:12:35'). - * - * The timezone of the Date object is assumed to be the one of the - * browser and it will be converted to UTC (standard for OpenERP 6.1). - * - * @param {Date} obj - * @returns {String} A string representing a datetime. - */ -openerp.base.format_datetime = function(obj) { - if (!obj) { - return false; - } - return fts(obj.getUTCFullYear(),4) + "-" + fts(obj.getUTCMonth() + 1,2) + "-" - + fts(obj.getUTCDate(),2) + " " + fts(obj.getUTCHours(),2) + ":" - + fts(obj.getUTCMinutes(),2) + ":" + fts(obj.getUTCSeconds(),2); -}; - -/** - * Converts a Date javascript object to a string using OpenERP's - * date string format (exemple: '2011-12-01'). - * - * @param {Date} obj - * @returns {String} A string representing a date. - */ -openerp.base.format_date = function(obj) { - if (!obj) { - return false; - } - return fts(obj.getFullYear(),4) + "-" + fts(obj.getMonth() + 1,2) + "-" - + fts(obj.getDate(),2); -}; - -/** - * Converts a Date javascript object to a string using OpenERP's - * time string format (exemple: '15:12:35'). - * - * @param {Date} obj - * @returns {String} A string representing a time. - */ -openerp.base.format_time = function(obj) { - if (!obj) { - return false; - } - return fts(obj.getHours(),2) + ":" + fts(obj.getMinutes(),2) + ":" - + fts(obj.getSeconds(),2); -}; - -}; diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index 5db5c62e856..4e7876b1768 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -451,12 +451,14 @@ openerp.base.form.SidebarAttachments = openerp.base.Widget.extend({ this.on_attachments_loaded([]); } else { (new openerp.base.DataSetSearch( - this, 'ir.attachment', this.view.dataset.get_context(), - [['res_model', '=', this.view.dataset.model], - ['res_id', '=', this.view.datarecord.id], - ['type', 'in', ['binary', 'url']]])).read_slice( - ['name', 'url', 'type'], false, false, - this.on_attachments_loaded); + this, 'ir.attachment', this.view.dataset.get_context(), + [ + ['res_model', '=', this.view.dataset.model], + ['res_id', '=', this.view.datarecord.id], + ['type', 'in', ['binary', 'url']] + ])).read_slice( + {fields: ['name', 'url', 'type']}, + this.on_attachments_loaded); } }, on_attachments_loaded: function(attachments) { @@ -510,36 +512,40 @@ openerp.base.form.compute_domain = function(expr, fields) { } } - var field = fields[ex[0]].get_value ? fields[ex[0]].get_value() : fields[ex[0]].value; + var field = fields[ex[0]]; + if (!field) { + throw new Error("Domain references unknown field : " + ex[0]); + } + var field_value = field.get_value ? fields[ex[0]].get_value() : fields[ex[0]].value; var op = ex[1]; var val = ex[2]; switch (op.toLowerCase()) { case '=': case '==': - stack.push(field == val); + stack.push(field_value == val); break; case '!=': case '<>': - stack.push(field != val); + stack.push(field_value != val); break; case '<': - stack.push(field < val); + stack.push(field_value < val); break; case '>': - stack.push(field > val); + stack.push(field_value > val); break; case '<=': - stack.push(field <= val); + stack.push(field_value <= val); break; case '>=': - stack.push(field >= val); + stack.push(field_value >= val); break; case 'in': - stack.push(_(val).contains(field)); + stack.push(_(val).contains(field_value)); break; case 'not in': - stack.push(!_(val).contains(field)); + stack.push(!_(val).contains(field_value)); break; default: this.log("Unsupported operator in modifiers :", op); @@ -767,8 +773,7 @@ openerp.base.form.WidgetButton = openerp.base.form.Widget.extend({ var self = this; this.view.execute_action( - this.node.attrs, this.view.dataset, this.session.action_manager, - this.view.datarecord.id, function () { + this.node.attrs, this.view.dataset, this.view.datarecord.id, function () { self.view.reload(); }); } @@ -803,7 +808,7 @@ openerp.base.form.WidgetLabel = openerp.base.form.Widget.extend({ var self = this; this.$element.find("label").dblclick(function() { var widget = self['for'] || self; - console.log(widget.element_id , widget); + self.log(widget.element_id , widget); }); } }); @@ -1315,7 +1320,7 @@ openerp.base.form.dialog = function(content, options) { }, options || {}); options.autoOpen = true; var dialog = new openerp.base.Dialog(null, options); - dialog.$dialog = $(content).dialog(dialog.options); + dialog.$dialog = $(content).dialog(dialog.dialog_options); return dialog.$dialog; } @@ -1501,7 +1506,6 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ var dataset = new openerp.base.DataSetStatic(self, self.field.relation, self.build_context()); dataset.name_get([element_ids[0]], function(data) { self._change_int_ext_value(data[0]); - pop.stop(); }); }); }, @@ -1648,7 +1652,7 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ }); this.views = views; - this.viewmanager = new openerp.base.ViewManager(this, this.element_id, this.dataset, views); + this.viewmanager = new openerp.base.ViewManager(this, this.dataset, views); this.viewmanager.registry = openerp.base.views.clone({ list: 'openerp.base.form.One2ManyListView', form: 'openerp.base.form.One2ManyFormView' @@ -1674,7 +1678,7 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ self.save_form_view(); }); setTimeout(function () { - self.viewmanager.start(); + self.viewmanager.appendTo(self.$element); }, 0); }, reload_current_view: function() { @@ -1837,16 +1841,16 @@ openerp.base.form.One2ManyListView = openerp.base.ListView.extend({ pop.select_element(self.o2m.field.relation,{ initial_view: "form", alternative_form_view: self.o2m.field.views ? self.o2m.field.views["form"] : undefined, - auto_create: false, + create_function: function(data) { + return self.o2m.dataset.create(data, function(r) { + self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); + self.o2m.dataset.on_change(); + }); + }, parent_view: self.o2m.view }, self.o2m.build_domain(), self.o2m.build_context()); - pop.on_create.add(function(data) { - self.o2m.dataset.create(data, function(r) { - self.o2m.dataset.set_ids(self.o2m.dataset.ids.concat([r.result])); - self.o2m.dataset.on_change(); - pop.stop(); - self.o2m.reload_current_view(); - }); + pop.on_select_elements.add_last(function() { + self.o2m.reload_current_view(); }); } }, @@ -1885,7 +1889,7 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ self.on_ui_change(); }); - this.list_view = new openerp.base.form.Many2ManyListView(new openerp.base.NullViewManager(this), this.list_id, this.dataset, false, { + this.list_view = new openerp.base.form.Many2ManyListView(this, this.list_id, this.dataset, false, { 'addable': 'Add', 'selectable': self.multi_selection }); @@ -1941,7 +1945,6 @@ openerp.base.form.Many2ManyListView = openerp.base.ListView.extend({ self.reload_content(); } }); - pop.stop(); }); }, do_activate_record: function(index, id) { @@ -1963,16 +1966,22 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ * - initial_view: form or search (default search) * - disable_multiple_selection * - alternative_form_view - * - auto_create (default true) + * - create_function (defaults to a naive saving behavior) * - parent_view */ select_element: function(model, options, domain, context) { + var self = this; this.model = model; this.domain = domain || []; this.context = context || {}; - this.options = _.defaults(options || {}, {"initial_view": "search", "auto_create": true}); + this.options = _.defaults(options || {}, {"initial_view": "search", "create_function": function() { + return self.create_row.apply(self, arguments); + }}); this.initial_ids = this.options.initial_ids; - openerp.base.form.dialog(this.render()); + this.created_elements = []; + openerp.base.form.dialog(this.render(), {close:function() { + self.check_exit(); + }}); this.start(); }, start: function() { @@ -2018,6 +2027,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ } $sbutton.click(function() { self.on_select_elements(self.selected_ids); + self.stop(); }); self.view_list = new openerp.base.form.SelectCreateListView(self, self.element_id + "_view_list", self.dataset, false, @@ -2030,15 +2040,11 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ }); this.searchview.start(); }, - on_create: function(data) { - if (!this.options.auto_create) - return; + create_row: function(data) { var self = this; var wdataset = new openerp.base.DataSetSearch(this, this.model, this.context, this.domain); wdataset.parent_view = this.options.parent_view; - wdataset.create(data, function(r) { - self.on_select_elements([r.result]); - }); + return wdataset.create(data); }, on_select_elements: function(element_ids) { }, @@ -2066,18 +2072,43 @@ openerp.base.form.SelectCreatePopup = openerp.base.OldWidget.extend({ this.view_form.start(); this.view_form.on_loaded.add_last(function() { var $buttons = self.view_form.$element.find(".oe_form_buttons"); - $buttons.html(QWeb.render("SelectCreatePopup.form.buttons")); + $buttons.html(QWeb.render("SelectCreatePopup.form.buttons", {widget:self})); + var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save-new"); + $nbutton.click(function() { + self._created = $.Deferred().then(function() { + self._created = undefined; + self.view_form.on_button_new(); + }); + self.view_form.do_save(); + }); var $nbutton = $buttons.find(".oe_selectcreatepopup-form-save"); $nbutton.click(function() { + self._created = $.Deferred().then(function() { + self._created = undefined; + self.check_exit(); + }); self.view_form.do_save(); }); var $cbutton = $buttons.find(".oe_selectcreatepopup-form-close"); $cbutton.click(function() { - self.stop(); + self.check_exit(); + }); + }); + this.dataset.on_create.add(function(data) { + self.options.create_function(data).then(function(r) { + self.created_elements.push(r.result); + if (self._created) { + self._created.resolve(); + } }); }); - this.dataset.on_create.add(this.on_create); this.view_form.do_show(); + }, + check_exit: function() { + if (this.created_elements.length > 0) { + this.on_select_elements(this.created_elements); + } + this.stop(); } }); @@ -2087,6 +2118,7 @@ openerp.base.form.SelectCreateListView = openerp.base.ListView.extend({ }, select_record: function(index) { this.popup.on_select_elements([this.dataset.ids[index]]); + this.popup.stop(); }, do_select: function(ids, records) { this._super(ids, records); @@ -2210,8 +2242,8 @@ openerp.base.form.FieldBinary = openerp.base.form.Field.extend({ delete(window[this.iframe]); if (size === false) { this.notification.warn("File Upload", "There was a problem while uploading your file"); - // TODO: use openerp web exception handler - console.log("Error while uploading file : ", name); + // TODO: use openerp web crashmanager + this.log("Error while uploading file : ", name); } else { this.on_file_uploaded_and_valid.apply(this, arguments); this.on_ui_change(); @@ -2320,10 +2352,8 @@ openerp.base.form.widgets = new openerp.base.Registry({ 'url' : 'openerp.base.form.FieldUrl', 'text' : 'openerp.base.form.FieldText', 'text_wiki' : 'openerp.base.form.FieldText', -// 'date' : 'openerp.base.form.FieldDate', -// 'datetime' : 'openerp.base.form.FieldDatetime', - 'date' : 'openerp.base.form.FieldChar', - 'datetime' : 'openerp.base.form.FieldChar', + 'date' : 'openerp.base.form.FieldDate', + 'datetime' : 'openerp.base.form.FieldDatetime', 'selection' : 'openerp.base.form.FieldSelection', 'many2one' : 'openerp.base.form.FieldMany2One', 'many2many' : 'openerp.base.form.FieldMany2Many', diff --git a/addons/base/static/src/js/formats.js b/addons/base/static/src/js/formats.js new file mode 100644 index 00000000000..074cabe5eee --- /dev/null +++ b/addons/base/static/src/js/formats.js @@ -0,0 +1,208 @@ + +openerp.base.formats = function(openerp) { + +/** + * Converts a string to a Date javascript object using OpenERP's + * datetime string format (exemple: '2011-12-01 15:12:35'). + * + * The timezone is assumed to be UTC (standard for OpenERP 6.1) + * and will be converted to the browser's timezone. + * + * @param {String} str A string representing a datetime. + * @returns {Date} + */ +openerp.base.parse_datetime = function(str) { + if(!str) { + return str; + } + var regex = /\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d/; + var res = regex.exec(str); + if ( res[0] != str ) { + throw "'" + str + "' is not a valid datetime"; + } + var obj = Date.parse(str + " GMT"); + if (! obj) { + throw "'" + str + "' is not a valid datetime"; + } + return obj; +}; + +/** + * Converts a string to a Date javascript object using OpenERP's + * date string format (exemple: '2011-12-01'). + * + * @param {String} str A string representing a date. + * @returns {Date} + */ +openerp.base.parse_date = function(str) { + if(!str) { + return str; + } + var regex = /\d\d\d\d-\d\d-\d\d/; + var res = regex.exec(str); + if ( res[0] != str ) { + throw "'" + str + "' is not a valid date"; + } + var obj = Date.parse(str); + if (! obj) { + throw "'" + str + "' is not a valid date"; + } + return obj; +}; + +/** + * Converts a string to a Date javascript object using OpenERP's + * time string format (exemple: '15:12:35'). + * + * @param {String} str A string representing a time. + * @returns {Date} + */ +openerp.base.parse_time = function(str) { + if(!str) { + return str; + } + var regex = /\d\d:\d\d:\d\d/; + var res = regex.exec(str); + if ( res[0] != str ) { + throw "'" + str + "' is not a valid time"; + } + var obj = Date.parse(str); + if (! obj) { + throw "'" + str + "' is not a valid time"; + } + return obj; +}; + +/* + * Left-pad provided arg 1 with zeroes until reaching size provided by second + * argument. + * + * @param {Number|String} str value to pad + * @param {Number} size size to reach on the final padded value + * @returns {String} padded string + */ +var zpad = function(str, size) { + str = "" + str; + return new Array(size - str.length + 1).join('0') + str; +}; + +/** + * Converts a Date javascript object to a string using OpenERP's + * datetime string format (exemple: '2011-12-01 15:12:35'). + * + * The timezone of the Date object is assumed to be the one of the + * browser and it will be converted to UTC (standard for OpenERP 6.1). + * + * @param {Date} obj + * @returns {String} A string representing a datetime. + */ +openerp.base.format_datetime = function(obj) { + if (!obj) { + return false; + } + return zpad(obj.getUTCFullYear(),4) + "-" + zpad(obj.getUTCMonth() + 1,2) + "-" + + zpad(obj.getUTCDate(),2) + " " + zpad(obj.getUTCHours(),2) + ":" + + zpad(obj.getUTCMinutes(),2) + ":" + zpad(obj.getUTCSeconds(),2); +}; + +/** + * Converts a Date javascript object to a string using OpenERP's + * date string format (exemple: '2011-12-01'). + * + * @param {Date} obj + * @returns {String} A string representing a date. + */ +openerp.base.format_date = function(obj) { + if (!obj) { + return false; + } + return zpad(obj.getFullYear(),4) + "-" + zpad(obj.getMonth() + 1,2) + "-" + + zpad(obj.getDate(),2); +}; + +/** + * Converts a Date javascript object to a string using OpenERP's + * time string format (exemple: '15:12:35'). + * + * @param {Date} obj + * @returns {String} A string representing a time. + */ +openerp.base.format_time = function(obj) { + if (!obj) { + return false; + } + return zpad(obj.getHours(),2) + ":" + zpad(obj.getMinutes(),2) + ":" + + zpad(obj.getSeconds(),2); +}; + +/** + * Formats a single atomic value based on a field descriptor + * + * @param {Object} value read from OpenERP + * @param {Object} descriptor union of orm field and view field + * @param {Object} [descriptor.widget] widget to use to display the value + * @param {Object} descriptor.type fallback if no widget is provided, or if the provided widget is unknown + * @param {Object} [descriptor.digits] used for the formatting of floats + * @param {String} [value_if_empty=''] returned if the ``value`` argument is considered empty + */ +openerp.base.format_value = function (value, descriptor, value_if_empty) { + // If NaN value, display as with a `false` (empty cell) + if (typeof value === 'number' && isNaN(value)) { + value = false; + } + switch (value) { + case false: + case Infinity: + case -Infinity: + return value_if_empty === undefined ? '' : value_if_empty; + } + switch (descriptor.widget || descriptor.type) { + case 'integer': + return _.sprintf('%d', value); + case 'float': + var precision = descriptor.digits ? descriptor.digits[1] : 2; + return _.sprintf('%.' + precision + 'f', value); + case 'float_time': + return _.sprintf("%02d:%02d", + Math.floor(value), + Math.round((value % 1) * 60)); + case 'progressbar': + return _.sprintf( + '%.2f%%', + value, value); + case 'many2one': + // name_get value format + return value[1]; + default: + return value; + } +}; + +/** + * Formats a provided cell based on its field type + * + * @param {Object} row_data record whose values should be displayed in the cell + * @param {Object} column column descriptor + * @param {"button"|"field"} column.tag base control type + * @param {String} column.type widget type for a field control + * @param {String} [column.string] button label + * @param {String} [column.icon] button icon + * @param {String} [value_if_empty=''] what to display if the field's value is ``false`` + */ +openerp.base.format_cell = function (row_data, column, value_if_empty) { + var attrs = column.modifiers_for(row_data); + if (attrs.invisible) { return ''; } + if (column.tag === 'button') { + return [ + '' + ].join('') + } + + return openerp.base.format_value( + row_data[column.id].value, column, value_if_empty); +} + +}; diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index ea9cdaea1ee..a0440221520 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -1,63 +1,5 @@ openerp.base.list = function (openerp) { openerp.base.views.add('list', 'openerp.base.ListView'); -openerp.base.list = { - /** - * Formats the rendring of a given value based on its field type - * - * @param {Object} row_data record whose values should be displayed in the cell - * @param {Object} column column descriptor - * @param {"button"|"field"} column.tag base control type - * @param {String} column.type widget type for a field control - * @param {String} [column.string] button label - * @param {String} [column.icon] button icon - * @param {String} [value_if_empty=''] what to display if the field's value is ``false`` - */ - render_cell: function (row_data, column, value_if_empty) { - var attrs = column.modifiers_for(row_data); - if (attrs.invisible) { return ''; } - if (column.tag === 'button') { - return [ - '' - ].join('') - } - - var value = row_data[column.id].value; - - // If NaN value, display as with a `false` (empty cell) - if (typeof value === 'number' && isNaN(value)) { - value = false; - } - switch (value) { - case false: - case Infinity: - case -Infinity: - return value_if_empty === undefined ? '' : value_if_empty; - } - switch (column.widget || column.type) { - case 'integer': - return _.sprintf('%d', value); - case 'float': - var precision = column.digits ? column.digits[1] : 2; - return _.sprintf('%.' + precision + 'f', value); - case 'float_time': - return _.sprintf("%02d:%02d", - Math.floor(value), - Math.round((value % 1) * 60)); - case 'progressbar': - return _.sprintf( - '%.2f%%', - value, value); - case 'many2one': - // name_get value format - return value[1]; - default: - return value; - } - } -}; openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListView# */ { defaults: { // records can be selected one by one @@ -119,7 +61,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi /** * Retrieves the view's number of records per page (|| section) * - * options > defaults > indefinite + * options > defaults > parent.action.limit > indefinite * * @returns {Number|null} */ @@ -127,6 +69,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi if (this._limit === undefined) { this._limit = (this.options.limit || this.defaults.limit + || (this.widget_parent.action || {}).limit || null); } return this._limit; @@ -152,7 +95,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi self.do_delete(ids); }, 'action': function (e, action_name, id, callback) { - self.do_action(action_name, id, callback); + self.do_button_action(action_name, id, callback); }, 'row_link': function (e, id, dataset) { self.do_activate_record(dataset.index, id, dataset); @@ -409,8 +352,9 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * Reloads the list view based on the current settings (dataset & al) * * @param {Boolean} [grouped] Should the list be displayed grouped + * @param {Object} [context] context to send the server while loading the view */ - reload_view: function (grouped) { + reload_view: function (grouped, context) { var self = this; var callback = function (field_view_get) { self.on_loaded(field_view_get, grouped); @@ -421,7 +365,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi return this.rpc('/base/listview/load', { model: this.model, view_id: this.view_id, - context: this.dataset.get_context(), + context: this.dataset.get_context(context), toolbar: this.options.sidebar }, callback); } @@ -457,11 +401,10 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Object} results results of evaluating domain and process for a search */ do_actual_search: function (results) { - this.dataset.context = results.context; - this.dataset.domain = results.domain; this.groups.datagroup = new openerp.base.DataGroup( this, this.model, - results.domain, results.context, + this.dataset.get_domain(results.domain), + this.dataset.get_context(results.context), results.group_by); this.groups.datagroup.sort = this.dataset._sort; @@ -469,7 +412,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi results.group_by = null; } - this.reload_view(!!results.group_by).then( + this.reload_view(!!results.group_by, results.context).then( $.proxy(this, 'reload_content')); }, /** @@ -512,17 +455,16 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi * @param {Object} id id of the record the action should be called on * @param {Function} callback should be called after the action is executed, if non-null */ - do_action: function (name, id, callback) { + do_button_action: function (name, id, callback) { var self = this, action = _.detect(this.columns, function (field) { return field.name === name; }); if (!action) { return; } - this.execute_action( - action, this.dataset, this.session.action_manager, id, function () { - $.when(callback.apply(this, arguments).then(function () { - self.compute_aggregates(); - })); + this.execute_action(action, this.dataset, id, function () { + $.when(callback.apply(this, arguments).then(function () { + self.compute_aggregates(); + })); }); }, /** @@ -534,11 +476,11 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi */ do_activate_record: function (index, id, dataset) { var self = this; - _.extend(this.dataset, { - domain: dataset.domain, - context: dataset.get_context() - }).read_slice([], 0, false, function () { - self.select_record(index); + this.dataset.read_slice({ + context: dataset.get_context(), + domain: dataset.get_domain() + }, function () { + self.select_record(index); }); }, /** @@ -637,7 +579,7 @@ openerp.base.ListView = openerp.base.View.extend( /** @lends openerp.base.ListVi } $footer_cells.filter(_.sprintf('[data-field=%s]', column.id)) - .html(openerp.base.list.render_cell(aggregation, column)); + .html(openerp.base.format_cell(aggregation, column)); }); } // TODO: implement reorder (drag and drop rows) @@ -725,7 +667,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. this.$current = this.$_element.clone(true); this.$current.empty().append( QWeb.render('ListView.rows', _.extend({ - render_cell: openerp.base.list.render_cell}, this))); + render_cell: openerp.base.format_cell}, this))); }, /** * Gets the ids of all currently selected records, if any @@ -830,7 +772,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. var old_index = this.dataset.index; this.dataset.index = record_index; read_p = this.dataset.read_index( - _.filter(_.pluck(this.columns, 'name'), _.identity), + _.pluck(_(this.columns).filter(function (r) {return r.tag === 'field';}), 'name'), function (record) { var form_record = self.transform_record(record); self.rows.splice(record_index, 1, form_record); @@ -856,7 +798,7 @@ openerp.base.ListView.List = openerp.base.Class.extend( /** @lends openerp.base. row: this.rows[record_index], row_parity: (record_index % 2 === 0) ? 'even' : 'odd', row_index: record_index, - render_cell: openerp.base.list.render_cell + render_cell: openerp.base.format_cell }); }, /** @@ -1016,7 +958,7 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas row_data[group.grouped_on] = group; var group_column = _(self.columns).detect(function (column) { return column.id === group.grouped_on; }); - $group_column.html(openerp.base.list.render_cell( + $group_column.html(openerp.base.format_cell( row_data, group_column, "Undefined" )); if (group.openable) { @@ -1092,10 +1034,11 @@ openerp.base.ListView.Groups = openerp.base.Class.extend( /** @lends openerp.bas d = new $.Deferred(), page = this.datagroup.openable ? this.page : view.page; - dataset.read_slice( - _.filter(_.pluck(_.select(this.columns, function(x) {return x.tag == "field";}), 'name'), _.identity), - page * limit, limit, - function (records) { + dataset.read_slice({ + fields: _.pluck(_.select(this.columns, function(x) {return x.tag == "field"}), 'name'), + offset: page * limit, + limit: limit + }, function (records) { if (!self.datagroup.openable) { view.configure_pager(dataset); } else { diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index 0a1f2396123..63ccec088a6 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -3,7 +3,6 @@ openerp.base.search = function(openerp) { openerp.base.SearchView = openerp.base.Widget.extend({ init: function(parent, element_id, dataset, view_id, defaults) { this._super(parent, element_id); - this.view_manager = parent || new openerp.base.NullViewManager(); this.dataset = dataset; this.model = dataset.model; this.view_id = view_id; @@ -362,9 +361,9 @@ openerp.base.search.fields = new openerp.base.Registry({ 'selection': 'openerp.base.search.SelectionField', 'datetime': 'openerp.base.search.DateTimeField', 'date': 'openerp.base.search.DateField', - 'one2many': 'openerp.base.search.OneToManyField', 'many2one': 'openerp.base.search.ManyToOneField', - 'many2many': 'openerp.base.search.ManyToManyField' + 'many2many': 'openerp.base.search.CharField', + 'one2many': 'openerp.base.search.CharField' }); openerp.base.search.Invalid = openerp.base.Class.extend( /** @lends openerp.base.search.Invalid# */{ /** @@ -624,39 +623,6 @@ openerp.base.search.CharField = openerp.base.search.Field.extend( /** @lends ope return this.$element.val(); } }); -openerp.base.search.BooleanField = openerp.base.search.Field.extend({ - template: 'SearchView.field.selection', - init: function () { - this._super.apply(this, arguments); - this.attrs.selection = [ - ['true', 'Yes'], - ['false', 'No'] - ]; - }, - /** - * Search defaults likely to be boolean values (for a boolean field). - * - * In the HTML, we only get strings, and our strings here are - * 'true' and 'false', so ensure we get only - * those by truth-testing the default value. - * - * @param {Object} defaults default values for this search view - */ - render: function (defaults) { - var name = this.attrs.name; - if (name in defaults) { - defaults[name] = defaults[name] ? "true" : "false"; - } - return this._super(defaults); - }, - get_value: function () { - switch (this.$element.val()) { - case 'false': return false; - case 'true': return true; - default: return null; - } - } -}); openerp.base.search.NumberField = openerp.base.search.Field.extend(/** @lends openerp.base.search.NumberField# */{ get_value: function () { if (!this.$element.val()) { @@ -693,93 +659,73 @@ openerp.base.search.FloatField = openerp.base.search.NumberField.extend(/** @len return parseFloat(value); } }); -openerp.base.search.SelectionField = openerp.base.search.Field.extend({ +/** + * @class + * @extends openerp.base.search.Field + */ +openerp.base.search.SelectionField = openerp.base.search.Field.extend(/** @lends openerp.base.search.SelectionField# */{ template: 'SearchView.field.selection', get_value: function () { return this.$element.val(); } }); +openerp.base.search.BooleanField = openerp.base.search.SelectionField.extend(/** @lends openerp.base.search.BooleanField# */{ + /** + * @constructs + * @extends openerp.base.search.BooleanField + */ + init: function () { + this._super.apply(this, arguments); + this.attrs.selection = [ + ['true', 'Yes'], + ['false', 'No'] + ]; + }, + /** + * Search defaults likely to be boolean values (for a boolean field). + * + * In the HTML, we only want/get strings, and our strings here are ``true`` + * and ``false``, so ensure we use precisely those by truth-testing the + * default value (iif there is one in the view's defaults). + * + * @param {Object} defaults default values for this search view + * @returns {String} rendered boolean field + */ + render: function (defaults) { + var name = this.attrs.name; + if (name in defaults) { + defaults[name] = defaults[name] ? "true" : "false"; + } + return this._super(defaults); + }, + get_value: function () { + switch (this.$element.val()) { + case 'false': return false; + case 'true': return true; + default: return null; + } + } +}); openerp.base.search.DateField = openerp.base.search.Field.extend( /** @lends openerp.base.search.DateField# */{ - template: 'SearchView.fields.date', /** * enables date picker on the HTML widgets */ start: function () { this._super(); - this.$element.find('input').datepicker({ + this.$element.addClass('field_date').datepicker({ dateFormat: 'yy-mm-dd' }); }, stop: function () { - this.$element.find('input').datepicker('destroy'); + this.$element.datepicker('destroy'); }, - /** - * Returns an object with two optional keys ``from`` and ``to`` providing - * the values for resp. the from and to sections of the date widget. - * - * If a key is absent, then the corresponding field was not filled. - * - * @returns {Object} - */ - get_values: function () { - var values_array = this.$element.find('input').serializeArray(); - - if (!values_array || !values_array[0]) { - throw new openerp.base.search.Invalid( - this.attrs.name, null, "widget not ready"); - } - var from = values_array[0].value, - to = values_array[1].value; - - var field_values = {}; - if (from) { - field_values.from = from; - } - if (to) { - field_values.to = to; - } - return field_values; - }, - get_context: function () { - var values = this.get_values(); - if (!this.attrs.context || _.isEmpty(values)) { - return null; - } - return _.extend( - {}, this.attrs.context, - {own_values: {self: values}}); - }, - get_domain: function () { - var values = this.get_values(); - if (_.isEmpty(values)) { - return null; - } - var domain = this.attrs['filter_domain']; - if (!domain) { - domain = []; - if (values.from) { - domain.push([this.attrs.name, '>=', values.from]); - } - if (values.to) { - domain.push([this.attrs.name, '<=', values.to]); - } - return domain; - } - - return _.extend( - {}, domain, - {own_values: {self: values}}); + get_value: function () { + return this.$element.val(); } }); openerp.base.search.DateTimeField = openerp.base.search.DateField.extend({ - // TODO: time? -}); -openerp.base.search.OneToManyField = openerp.base.search.CharField.extend({ - // TODO: .relation, .context, .domain }); openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ - // TODO: @widget - // TODO: .selection, .context, .domain init: function (view_section, field, view) { this._super(view_section, field, view); var self = this; @@ -847,9 +793,6 @@ openerp.base.search.ManyToOneField = openerp.base.search.CharField.extend({ return this._super(); } }); -openerp.base.search.ManyToManyField = openerp.base.search.CharField.extend({ - // TODO: .related_columns (Array), .context, .domain -}); openerp.base.search.ExtendedSearch = openerp.base.OldWidget.extend({ template: 'SearchView.extended_search', @@ -932,11 +875,10 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({ this._super(); var _this = this; this.add_prop(); - this.$element.find('.searchview_extended_add_proposition').click(function (e) { + this.$element.find('.searchview_extended_add_proposition').click(function () { _this.add_prop(); }); - var delete_btn = this.$element.find('.searchview_extended_delete_group'); - delete_btn.click(function (e) { + this.$element.find('.searchview_extended_delete_group').click(function () { _this.stop(); }); }, @@ -946,7 +888,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({ }).compact().value(); var choice = this.$element.find(".searchview_extended_group_choice").val(); var op = choice == "all" ? "&" : "|"; - return [].concat(choice == "none" ? ['!'] : [], + return choice == "none" ? ['!'] : [].concat( _.map(_.range(_.max([0,props.length - 1])), function() { return op; }), props); }, @@ -958,10 +900,7 @@ openerp.base.search.ExtendedSearchGroup = openerp.base.OldWidget.extend({ parent.check_last_element(); }, set_last_group: function(is_last) { - if(is_last) - this.$element.addClass("last_group"); - else - this.$element.removeClass("last_group"); + this.$element.toggleClass('last_group', is_last); } }); @@ -984,8 +923,7 @@ openerp.base.search.ExtendedSearchProposition = openerp.base.OldWidget.extend({ this.$element.find(".searchview_extended_prop_field").change(function() { _this.changed(); }); - var delete_btn = this.$element.find('.searchview_extended_delete_prop'); - delete_btn.click(function (e) { + this.$element.find('.searchview_extended_delete_prop').click(function () { _this.stop(); }); }, diff --git a/addons/base/static/src/js/view_tree.js b/addons/base/static/src/js/view_tree.js index 90972f84973..5c3c7457e1c 100644 --- a/addons/base/static/src/js/view_tree.js +++ b/addons/base/static/src/js/view_tree.js @@ -5,22 +5,202 @@ openerp.base.view_tree = function(openerp) { openerp.base.views.add('tree', 'openerp.base.TreeView'); -openerp.base.TreeView = openerp.base.Widget.extend({ /** * Genuine tree view (the one displayed as a tree, not the list) */ +openerp.base.TreeView = openerp.base.View.extend({ + /** + * Indicates that this view is not searchable, and thus that no search + * view should be displayed (if there is one active). + */ + searchable : false, + + init: function(parent, element_id, dataset, view_id, options) { + this._super(parent, element_id); + this.dataset = dataset; + this.model = dataset.model; + this.view_id = view_id; + + this.records = {}; + + this.options = _.extend({}, this.defaults, options || {}); + }, + start: function () { this._super(); - this.$element.append('Tree view'); + return this.rpc("/base/treeview/load", { + model: this.model, + view_id: this.view_id, + toolbar: this.view_manager ? !!this.view_manager.sidebar : false + }, this.on_loaded); }, + /** + * Returns the list of fields needed to correctly read objects. + * + * Gathers the names of all fields in fields_view_get, and adds the + * field_parent (children_field in the tree view) if it's not already one + * of the fields to fetch + * + * @returns {Array} an array of fields which can be provided to DataSet.read_slice and others + */ + fields_list: function () { + var fields = _.keys(this.fields); + if (!_(fields).contains(this.children_field)) { + fields.push(this.children_field); + } + return fields; + }, + on_loaded: function (fields_view) { + var self = this; + var has_toolbar = !!fields_view.arch.attrs.toolbar; + // field name in OpenERP is kinda stupid: this is the name of the field + // holding the ids to the children of the current node, why call it + // field_parent? + this.children_field = fields_view['field_parent']; + this.fields_view = fields_view; + _(this.fields_view.arch.children).each(function (field) { + if (field.attrs.modifiers) { + field.attrs.modifiers = JSON.parse(field.attrs.modifiers); + } + }); + this.fields = fields_view.fields; + this.hook_row_click(); + this.$element.html(QWeb.render('TreeView', { + 'title': this.fields_view.arch.attrs.string, + 'fields_view': this.fields_view.arch.children, + 'fields': this.fields, + 'toolbar': has_toolbar + })); + + this.dataset.read_slice({fields: this.fields_list()}, function (records) { + if (!has_toolbar) { + // WARNING: will do a second read on the same ids, but only on + // first load so not very important + self.getdata(null, _(records).pluck('id')); + return; + } + + var $select = self.$element.find('select') + .change(function () { + var $option = $(this).find(':selected'); + self.getdata($option.val(), $option.data('children')); + }); + _(records).each(function (record) { + self.records[record.id] = record; + $('