diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index bc6bc141863..9869adad92a 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -171,8 +171,9 @@ class Session(openerpweb.Controller): a list of fields to group by, potentially empty (in which case no group by should be performed) """ - context = req.session.eval_context(openerpweb.nonliterals.CompoundContext(*contexts)) - domain = req.session.eval_domain(openerpweb.nonliterals.CompoundDomain(*(domains or [])), context) + context, domain = eval_context_and_domain(req.session, + openerpweb.nonliterals.CompoundContext(*(contexts or [])), + openerpweb.nonliterals.CompoundDomain(*(domains or []))) group_by_sequence = [] for candidate in (group_by_seq or []): @@ -233,12 +234,12 @@ class Session(openerpweb.Controller): def eval_context_and_domain(session, context, domain=None): e_context = session.eval_context(context) - e_domain = session.eval_domain(domain or [], e_context) + # should we give the evaluated context as an evaluation context to the domain? + e_domain = session.eval_domain(domain or []) return (e_context, e_domain) def load_actions_from_ir_values(req, key, key2, models, meta, context): - context['bin_size'] = False # Possible upstream bug. Antony says not to loose time on this. Values = req.session.model('ir.values') actions = Values.get(key, key2, models, meta, context) @@ -249,12 +250,12 @@ def load_actions_from_ir_values(req, key, key2, models, meta, context): def clean_action(action, session): # values come from the server, we can just eval them - if isinstance(action['context'], basestring): + if isinstance(action.get('context', None), basestring): action['context'] = eval( action['context'], session.evaluation_context()) or {} - if isinstance(action['domain'], basestring): + if isinstance(action.get('domain', None), basestring): action['domain'] = eval( action['domain'], session.evaluation_context( @@ -285,13 +286,15 @@ def fix_view_modes(action): if action.pop('view_type') != 'form': return - action['view_mode'] = ','.join( - mode if mode != 'tree' else 'list' - for mode in action['view_mode'].split(',')) - action['views'] = [ - [id, mode if mode != 'tree' else 'list'] - for id, mode in action['views'] - ] + if action.has_key('view_mode'): + action['view_mode'] = ','.join( + mode if mode != 'tree' else 'list' + for mode in action['view_mode'].split(',')) + if action.has_key('views'): + action['views'] = [ + [id, mode if mode != 'tree' else 'list'] + for id, mode in action['views'] + ] return action class Menu(openerpweb.Controller): @@ -439,8 +442,7 @@ class DataSet(openerpweb.Controller): Model = request.session.model(model) return Model.unlink(ids, request.session.eval_context(request.context)) - @openerpweb.jsonrequest - def call(self, req, model, method, args, domain_id=None, context_id=None): + def call_common(self, req, model, method, args, domain_id=None, context_id=None): domain = args[domain_id] if domain_id and len(args) - 1 >= domain_id else [] context = args[context_id] if context_id and len(args) - 1 >= context_id else {} c, d = eval_context_and_domain(req.session, context, domain); @@ -448,10 +450,20 @@ class DataSet(openerpweb.Controller): args[domain_id] = d if(context_id and len(args) - 1 >= context_id): args[context_id] = c - + m = req.session.model(model) - r = getattr(m, method)(*args) - return {'result': r} + return getattr(m, method)(*args) + + @openerpweb.jsonrequest + def call(self, req, model, method, args, domain_id=None, context_id=None): + return {'result': self.call_common(req, model, method, args, domain_id, context_id)} + + @openerpweb.jsonrequest + def call_button(self, req, model, method, args, domain_id=None, context_id=None): + action = self.call_common(req, model, method, args, domain_id, context_id) + if isinstance(action, dict) and action.get('type') != '': + clean_action(action, req.session) + return {'result': action} @openerpweb.jsonrequest def exec_workflow(self, req, model, id, signal): @@ -756,7 +768,6 @@ class Action(openerpweb.Controller): Actions = req.session.model('ir.actions.actions') value = False context = req.session.eval_context(req.context) - context["bin_size"] = False action_type = Actions.read([action_id], ['type'], context) if action_type: action = req.session.model(action_type[0]['type']).read([action_id], False, diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index 7488e4c8c71..39ff577b356 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -505,9 +505,10 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%, background: #AAAAAA; } .openerp .filter_icon { + height: 22px; padding: 1px 2px 0 2px; - margin-left: 0; - margin-right: 0; + margin: 0; + vertical-align: bottom; } .openerp .filter_label { font-weight: bold; @@ -568,14 +569,6 @@ background: linear-gradient(top, #ffffff 0%,#d8d8d8 11%,#afafaf 86%,#333333 91%, .openerp .searchview_group_content { padding-left: 10px; } -.openerp .searchview_group_content .oe-searchview-render-line { - width:0; -} - - -.openerp .oe-searchview-render-line { - width:100%; -} .openerp .searchview_extended_group { padding: 3px; @@ -1086,6 +1079,7 @@ background: linear-gradient(top, #ffffff 0%,#ebe9e9 100%); /* W3C */ .openerp .oe-m2o-drop-down-button img, .openerp .oe-m2o-cm-button img { margin-bottom: -4px; + cursor: pointer; } .openerp .oe-m2o-disabled-cm { color: grey; diff --git a/addons/base/static/src/js/chrome.js b/addons/base/static/src/js/chrome.js index 3c15fcb1e68..0087720adde 100644 --- a/addons/base/static/src/js/chrome.js +++ b/addons/base/static/src/js/chrome.js @@ -11,8 +11,8 @@ openerp.base.callback = function(obj, method) { for(var i = 0; i < callback.callback_chain.length; i++) { var c = callback.callback_chain[i]; if(c.unique) { - // al: obscure but shortening C-style hack, sorry - callback.callback_chain.pop(i--); + callback.callback_chain.splice(i, 1); + i -= 1; } r = c.callback.apply(c.self, c.args.concat(args)); // TODO special value to stop the chain @@ -137,6 +137,29 @@ openerp.base.Registry = Class.extend( /** @lends openerp.base.Registry# */ { } return object_match; }, + /** + * Tries a number of keys, and returns the first object matching one of + * the keys. + * + * @param {Array} keys a sequence of keys to fetch the object for + * @returns {Class} the first class found matching an object + * + * @throws {openerp.base.KeyNotFound} if none of the keys was in the mapping + * @trows {openerp.base.ObjectNotFound} if a found object path was invalid + */ + get_any: function (keys) { + for (var i=0; i self.limit) { values = values.slice(0, self.limit); values.push({label: "   Search More...", action: function() { - dataset.name_search([search_val, self.field.domain || [], 'ilike', - build_relation_context(self), false], function(data) { + dataset.name_search(search_val, build_relation_domain(self), 'ilike' + , false, function(data) { self._change_int_value(null); self._search_create_popup("search", data.result); }); @@ -1239,27 +1264,27 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ }, _quick_create: function(name) { var self = this; - var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []); - dataset.call("name_create", [name, build_relation_context(self)], function(data) { + var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self)); + dataset.name_create(name, function(data) { self._change_int_ext_value(data.result); - }, function(a, b) { + }).fail(function() { self._change_int_value(null); self._search_create_popup("form", undefined, {"default_name": name}); }); }, // all search/create popup handling _search_create_popup: function(view, ids, context) { - var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []); var self = this; var pop = new openerp.base.form.SelectCreatePopup(null, self.view.session); pop.select_element(self.field.relation,{ initial_ids: ids ? _.map(ids, function(x) {return x[0]}) : undefined, initial_view: view, disable_multiple_selection: true - }, self.view.domain || [], - new openerp.base.CompoundContext(build_relation_context(self)).add(context || {})); + }, build_relation_domain(self), + new openerp.base.CompoundContext(build_relation_context(self), context || {})); pop.on_select_elements.add(function(element_ids) { - dataset.call("name_get", [element_ids[0]], function(data) { + var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self)); + dataset.name_get([element_ids[0]], function(data) { self._change_int_ext_value(data.result[0]); pop.stop(); }); @@ -1271,9 +1296,15 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ }, _change_int_value: function(value) { this.value = value; - if (this.original_value === undefined || (this.value !== undefined && - ((this.original_value ? this.original_value[0] : null) !== (this.value ? this.value[0] : null)))) { - this.original_value = undefined; + var back_orig_value = this.original_value; + if (this.value === null || this.value) { + this.original_value = this.value; + } + if (back_orig_value === undefined) { // first use after a set_value() + return; + } + if (this.value !== undefined && ((back_orig_value ? back_orig_value[0] : null) + !== (this.value ? this.value[0] : null))) { this.on_ui_change(); } }, @@ -1284,14 +1315,14 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ var _super = this._super; this.tmp_value = value; var real_set_value = function(rval) { - this.tmp_value = undefined; + self.tmp_value = undefined; _super.apply(self, rval); - self.original_value = rval; + self.original_value = undefined; self._change_int_ext_value(rval); }; if(typeof(value) === "number") { - var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, []); - dataset.call("name_get", [value], function(data) { + var dataset = new openerp.base.DataSetStatic(this.session, this.field.relation, build_relation_context(self)); + dataset.name_get([value], function(data) { real_set_value(data.result[0]); }).fail(function() {self.tmp_value = undefined;}); } else { @@ -1303,10 +1334,10 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ if (this.tmp_value instanceof Array) { return this.tmp_value[0]; } - return this.tmp_value; + return this.tmp_value ? this.tmp_value : false; } if (this.value === undefined) - throw "theorically unreachable state"; + return this.original_value ? this.original_value[0] : false; return this.value ? this.value[0] : false; }, validate: function() { @@ -1325,7 +1356,43 @@ openerp.base.form.FieldMany2One = openerp.base.form.Field.extend({ # (4, ID) link # (5) unlink all (only valid for one2many) */ - +var commands = { + // (0, _, {values}) + CREATE: 0, + 'create': function (values) { + return [commands.CREATE, false, values]; + }, + // (1, id, {values}) + UPDATE: 1, + 'update': function (id, values) { + return [commands.UPDATE, id, values]; + }, + // (2, id[, _]) + DELETE: 2, + 'delete': function (id) { + return [commands.DELETE, id, false]; + }, + // (3, id[, _]) removes relation, but not linked record itself + FORGET: 3, + 'forget': function (id) { + return [commands.FORGET, id, false]; + }, + // (4, id[, _]) + LINK_TO: 4, + 'link_to': function (id) { + return [commands.LINK_TO, id, false]; + }, + // (5[, _[, _]]) + FORGET_ALL: 5, + 'forget_all': function () { + return [5, false, false]; + }, + // (6, _, ids) replaces all linked records with provided ids + REPLACE_WITH: 6, + 'replace_with': function (ids) { + return [6, false, ids]; + } +}; openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ init: function(view, node) { this._super(view, node); @@ -1360,11 +1427,9 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ this.viewmanager = new openerp.base.ViewManager(this.view.session, this.element_id, this.dataset, views); - var reg = new openerp.base.Registry(); - reg.add("form", openerp.base.views.map["form"]); - reg.add("graph", openerp.base.views.map["graph"]); - reg.add("list", "openerp.base.form.One2ManyListView"); - this.viewmanager.registry = reg; + this.viewmanager.registry = openerp.base.views.clone({ + list: 'openerp.base.form.One2ManyListView' + }); this.viewmanager.on_controller_inited.add_last(function(view_type, controller) { if (view_type == "list") { @@ -1387,20 +1452,59 @@ openerp.base.form.FieldOne2Many = openerp.base.form.Field.extend({ }, set_value_from_ui: function() {}, set_value: function(value) { - value = value || [] - this._super(value); - this.dataset.reset_ids(value); + value = value || []; var self = this; + if(value.length >= 1 && value[0] instanceof Array) { + var ids = []; + _.each(value, function(command) { + var obj = {values: command[2]}; + switch (command[0]) { + case commands.CREATE: + obj['id'] = _.uniqueId(self.dataset.virtual_id_prefix); + self.dataset.to_create.push(obj); + self.dataset.cache.push(_.clone(obj)); + ids.push(obj.id); + return; + case commands.UPDATE: + obj['id'] = command[1]; + self.dataset.to_write.push(obj); + self.dataset.cache.push(_.clone(obj)); + ids.push(obj.id); + return; + case commands.DELETE: + self.dataset.to_delete.push({id: command[1]}); + return; + case commands.LINK_TO: + ids.push(command[1]); + return; + } + }); + this._super(ids); + this.dataset.set_ids(ids); + } else { + this._super(value); + this.dataset.reset_ids(value); + } $.when(this.is_started).then(function() { self.reload_current_view(); }); }, get_value: function() { - var val = _.map(this.dataset.to_delete, function(v, k) {return [2, x.id];}); - val = val.concat(_.map(this.dataset.to_create, function(x) {return [0, 0, x.values];})); - val = val.concat(_.map(this.dataset.to_write, function(x) {return [1, x.id, x.values];})); - debugger; - return val; + var self = this; + var val = _.map(this.dataset.ids, function(id) { + var alter_order = _.detect(self.dataset.to_create, function(x) {return x.id === id;}); + if (alter_order) { + return commands.create(alter_order.values); + } + alter_order = _.detect(self.dataset.to_write, function(x) {return x.id === id;}); + if (alter_order) { + return commands.update(alter_order.id, alter_order.values); + } + return commands.link_to(id); + }); + return val.concat(_.map( + this.dataset.to_delete, function(x) { + return commands['delete'](x.id);})); }, validate: function() { this.invalid = false; @@ -1458,6 +1562,9 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ }, set_value: function(value) { value = value || []; + if (value.length >= 1 && value[0] instanceof Array) { + value = value[0][2]; + } this._super(value); this.dataset.set_ids(value); var self = this; @@ -1466,7 +1573,7 @@ openerp.base.form.FieldMany2Many = openerp.base.form.Field.extend({ }); }, get_value: function() { - return [[6,false,this.dataset.ids]]; + return [commands.replace_with(this.dataset.ids)]; } }); @@ -1543,7 +1650,7 @@ openerp.base.form.SelectCreatePopup = openerp.base.BaseWidget.extend({ }); this.searchview.on_search.add(function(domains, contexts, groupbys) { if (self.initial_ids) { - self.view_list.do_search.call(self,[[["id", "in", self.initial_ids]]], + self.view_list.do_search.call(self, domains.concat([[["id", "in", self.initial_ids]]]), contexts, groupbys); self.initial_ids = undefined; } else { diff --git a/addons/base/static/src/js/search.js b/addons/base/static/src/js/search.js index cd69ec4f0e8..6efa6988fe8 100644 --- a/addons/base/static/src/js/search.js +++ b/addons/base/static/src/js/search.js @@ -377,6 +377,7 @@ openerp.base.search.FilterGroup = openerp.base.search.Widget.extend({ init: function (filters, view) { this._super(view); this.filters = filters; + this.length = filters.length; }, start: function () { this._super(); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index 8b87d9cbdbe..ee4388c8971 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -18,7 +18,7 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ * Process an action * Supported actions: act_window */ - do_action: function(action) { + do_action: function(action, on_closed) { var self = this; action.flags = _.extend({ sidebar : action.target != 'new', @@ -31,6 +31,9 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ // instantiate the right controllers by understanding the action switch (action.type) { case 'ir.actions.act_window': + if (!action.target && this.dialog_stack.length) { + action.flags.new_window = true; + } if (action.target == 'new') { var element_id = _.uniqueId("act_window_dialog"); $('
', {id: element_id}).dialog({ @@ -44,6 +47,8 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ }); var viewmanager = new openerp.base.ViewManagerAction(this.session, element_id, action); viewmanager.start(); + viewmanager.on_act_window_closed.add(on_closed); + viewmanager.is_dialog = true; this.dialog_stack.push(viewmanager); } else if (action.flags.new_window) { action.flags.new_window = false; @@ -62,6 +67,9 @@ openerp.base.ActionManager = openerp.base.Controller.extend({ break; case 'ir.actions.act_window_close': var dialog = this.dialog_stack.pop(); + if (!action.special) { + dialog.on_act_window_closed(); + } dialog.$element.dialog('destroy'); dialog.stop(); break; @@ -84,6 +92,7 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ this.flags = this.flags || {}; this.sidebar = new openerp.base.NullSidebar(); this.registry = openerp.base.views; + this.is_dialog = false; }, /** * @returns {jQuery.Deferred} initial view loading promise @@ -126,17 +135,20 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ controller.set_embedded_view(view.embedded_view); } if (view_type === 'list' && this.flags.search_view === false && this.action && this.action['auto_search']) { - // In case the search view is not instanciated: manually call ListView#search + // In case the search view is not instantiated: manually call ListView#search + var domains = !_(self.action.domain).isEmpty() + ? [self.action.domain] : [], + contexts = !_(self.action.context).isEmpty() + ? [self.action.context] : []; controller.on_loaded.add({ callback: function () { - controller.do_search([self.action.domain], [self.action.context], []); + controller.do_search(domains, contexts, []); }, position: 'last', unique: true }); } view_promise = controller.start(); - var self = this; $.when(view_promise).then(function() { self.on_controller_inited(view_type, controller); }); @@ -157,12 +169,13 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ .filter('[data-view-type="' + view_type + '"]') .attr('disabled', true); - for (var i in this.views) { - if (this.views[i].controller) { - if (i === view_type) { - $.when(view_promise).then(this.views[i].controller.do_show); + for (var view_name in this.views) { + if (!this.views.hasOwnProperty(view_name)) { continue; } + if (this.views[view_name].controller) { + if (view_name === view_type) { + $.when(view_promise).then(this.views[view_name].controller.do_show); } else { - this.views[i].controller.do_hide(); + this.views[view_name].controller.do_hide(); } } } @@ -197,6 +210,11 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ }); return this.searchview.start(); }, + /** + * Called when this view manager has been created by an action 'act_window@target=new' is closed + */ + on_act_window_closed : function() { + }, /** * Called when one of the view want to execute an action */ @@ -238,12 +256,14 @@ openerp.base.NullViewManager = openerp.base.generate_null_object_class(openerp.b openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ init: function(session, element_id, action) { var dataset; - if(!action.res_id) { - dataset = new openerp.base.DataSetSearch(session, action.res_model); + if (!action.res_id) { + dataset = new openerp.base.DataSetSearch(session, action.res_model, action.context || null); } else { - dataset = new openerp.base.DataSetStatic(session, action.res_model); - dataset.ids = [action.res_id]; - dataset.count = 1; + dataset = new openerp.base.DataSetStatic(session, action.res_model, {}, [action.res_id]); + if (action.context) { + // TODO fme: should normalize all DataSets constructors to (session, model, context, ...) + dataset.context = action.context; + } } this._super(session, element_id, dataset, action.views); this.action = action; @@ -336,16 +356,12 @@ openerp.base.Sidebar = openerp.base.BaseWidget.extend({ }, do_refresh: function(new_view) { var view = this.view_manager.active_view; - the_condition = this.sections.length > 0 && _.detect(this.sections, + var the_condition = this.sections.length > 0 && _.detect(this.sections, function(x) {return x.elements.length > 0;}) != undefined && (!new_view || view != 'list'); - if (!the_condition) { - this.$element.addClass('closed-sidebar'); - this.$element.removeClass('open-sidebar'); - } else { - this.$element.addClass('open-sidebar'); - this.$element.removeClass('closed-sidebar'); - } + + this.$element.toggleClass('open-sidebar', the_condition) + .toggleClass('closed-sidebar', !the_condition); this.$element.html(QWeb.render("ViewManager.sidebar.internal", { sidebar: this, view: view })); @@ -409,14 +425,17 @@ openerp.base.View = openerp.base.Controller.extend({ * @param {Object} [record_id] the identifier of the object on which the action is to be applied * @param {Function} on_no_action callback to execute if the action does not generate any result (no new action) */ - execute_action: function (action_data, dataset, action_manager, record_id, on_no_action) { + execute_action: function (action_data, dataset, action_manager, record_id, on_no_action, on_closed) { + var self = this; var handler = function (r) { var action = r.result; if (action && action.constructor == Object) { action.context = action.context || {}; - action.context['active_id'] = dataset.ids[dataset.index]; - action.context['active_ids'] = [dataset.ids[dataset.index]]; - action.context['active_model'] = dataset.model; + _.extend(action.context, { + active_id: record_id || false, + active_ids: [record_id || false], + active_model: dataset.model + }); action.flags = { sidebar : false, search_view : false, @@ -424,7 +443,12 @@ openerp.base.View = openerp.base.Controller.extend({ action_buttons : false, pager : false }; - action_manager.do_action(action); + action_manager.do_action(action, on_closed); + if (self.view_manager.is_dialog && action.type != 'ir.actions.act_window_close') { + handler({ + result : { type: 'ir.actions.act_window_close' } + }); + } } else { on_no_action(action); } @@ -432,13 +456,13 @@ openerp.base.View = openerp.base.Controller.extend({ if (action_data.special) { handler({ - result : { type: 'ir.actions.act_window_close' } + result : { type: 'ir.actions.act_window_close', special: action_data.special } }); } else { - var context = new openerp.base.CompoundContext(dataset.context).add(action_data.context || {}); + var context = new openerp.base.CompoundContext(dataset.context, action_data.context || {}); switch(action_data.type) { case 'object': - return dataset.call_and_eval(action_data.name, [[record_id], context], null, 1, handler); + return dataset.call_button(action_data.name, [[record_id], context], handler); case 'action': return this.rpc('/base/action/load', { action_id: parseInt(action_data.name, 10), context: context }, handler); default: @@ -476,7 +500,7 @@ openerp.base.json_node_to_xml = function(node, single_quote, indent) { throw("Node a json node"); } indent = indent || 0; - var sindent = Array(indent + 1).join('\t'), + var sindent = new Array(indent + 1).join('\t'), r = sindent + '<' + node.tag; for (var attr in node.attrs) { var vattr = node.attrs[attr]; diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index f703f4fcea9..6e1be85491f 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -499,6 +499,9 @@ : + +

+