diff --git a/addons/base/controllers/main.py b/addons/base/controllers/main.py index 83dae67b2ba..8a2c31b42eb 100644 --- a/addons/base/controllers/main.py +++ b/addons/base/controllers/main.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import glob, os +import pprint from xml.etree import ElementTree from cStringIO import StringIO @@ -334,18 +335,17 @@ class DataSet(openerpweb.Controller): :param int offset: from which index should the results start being returned :param int limit: the maximum number of records to return :param list domain: the search domain for the query - :param dict context: the context in which the search should be executed :param list sort: sorting directives :returns: a list of result records :rtype: list """ Model = request.session.model(model) ids = Model.search(domain or [], offset or 0, limit or False, - sort or False, context or False) + sort or False, request.context) if fields and fields == ['id']: # shortcut read if we only want the ids return map(lambda id: {'id': id}, ids) - return Model.read(ids, fields or False) + return Model.read(ids, fields or False, request.context) @openerpweb.jsonrequest def get(self, request, model, ids, fields=False): @@ -363,6 +363,9 @@ class DataSet(openerpweb.Controller): :type model: str :param ids: a list of identifiers :type ids: list + :param fields: a list of fields to fetch, ``False`` or empty to fetch + all fields in the model + :type fields: list | False :returns: a list of records, in the same order as the list of ids :rtype: list """ @@ -407,16 +410,20 @@ class DataSet(openerpweb.Controller): return {'result': r} class View(openerpweb.Controller): - def fields_view_get(self, session, model, view_id, view_type, transform=True, toolbar=False, submenu=False): - Model = session.model(model) - r = Model.fields_view_get(view_id, view_type, {}, toolbar, submenu) + def fields_view_get(self, request, model, view_id, view_type, + transform=True, toolbar=False, submenu=False): + Model = request.session.model(model) + fvg = Model.fields_view_get(view_id, view_type, request.context, + toolbar, submenu) if transform: - context = {} # TODO: dict(ctx_sesssion, **ctx_action) - xml = self.transform_view(r['arch'], session, context) + evaluation_context = request.session.evaluation_context( + request.context or {}) + xml = self.transform_view( + fvg['arch'], request.session, evaluation_context) else: - xml = ElementTree.fromstring(r['arch']) - r['arch'] = Xml2Json.convert_element(xml) - return r + xml = ElementTree.fromstring(fvg['arch']) + fvg['arch'] = Xml2Json.convert_element(xml) + return fvg def normalize_attrs(self, elem, context): """ Normalize @attrs, @invisible, @required, @readonly and @states, so @@ -430,19 +437,16 @@ class View(openerpweb.Controller): :param dict context: evaluation context """ # If @attrs is normalized in json by server, the eval should be replaced by simplejson.loads - attrs = eval(elem.attrib.get('attrs', '{}')) + attrs = openerpweb.ast.literal_eval(elem.get('attrs', '{}')) if 'states' in elem.attrib: - if 'invisible' not in attrs: - attrs['invisible'] = [] - # This should be done by the server - attrs['invisible'].append(('state', 'not in', elem.attrib['states'].split(','))) - del(elem.attrib['states']) + attrs.setdefault('invisible', [])\ + .append(('state', 'not in', elem.attrib.pop('states').split(','))) if attrs: - elem.attrib['attrs'] = simplejson.dumps(attrs) + elem.set('attrs', simplejson.dumps(attrs)) for a in ['invisible', 'readonly', 'required']: if a in elem.attrib: # In the XML we trust - avalue = bool(eval(elem.attrib.get(a, 'False'), + avalue = bool(eval(elem.get(a, 'False'), {'context': context or {}})) if not avalue: del elem.attrib[a] @@ -514,7 +518,7 @@ class FormView(View): @openerpweb.jsonrequest def load(self, req, model, view_id, toolbar=False): - fields_view = self.fields_view_get(req.session, model, view_id, 'form', toolbar=toolbar) + fields_view = self.fields_view_get(req, model, view_id, 'form', toolbar=toolbar) return {'fields_view': fields_view} class ListView(View): @@ -522,15 +526,86 @@ class ListView(View): @openerpweb.jsonrequest def load(self, req, model, view_id, toolbar=False): - fields_view = self.fields_view_get(req.session, model, view_id, 'tree', toolbar=toolbar) + fields_view = self.fields_view_get(req, model, view_id, 'tree', toolbar=toolbar) return {'fields_view': fields_view} + def fields_view_get(self, request, model, view_id, view_type="tree", + transform=True, toolbar=False, submenu=False): + """ Sets @editable on the view's arch if it isn't already set and + ``set_editable`` is present in the request context + """ + view = super(ListView, self).fields_view_get( + request, model, view_id, view_type, transform, toolbar, submenu) + + view_attributes = view['arch']['attrs'] + if request.context.get('set_editable')\ + and 'editable' not in view_attributes: + view_attributes['editable'] = 'bottom' + return view + + @openerpweb.jsonrequest + def fill(self, request, model, id, domain, + offset=0, limit=False): + return self.do_fill(request, model, id, domain, offset, limit) + + def do_fill(self, request, model, id, domain, + offset=0, limit=False): + """ Returns all information needed to fill a table: + + * view with processed ``editable`` flag + * fields (columns) with processed ``invisible`` flag + * rows with processed ``attrs`` and ``colors`` + + .. note:: context is passed through ``request`` parameter + + :param request: OpenERP request + :type request: openerpweb.openerpweb.JsonRequest + :type str model: OpenERP model for this list view + :type int id: view_id, or False if none provided + :param list domain: the search domain to search for + :param int offset: search offset, for pagination + :param int limit: search limit, for pagination + :returns: hell if I have any idea yet + """ + view = self.fields_view_get(request, model, id) + + rows = DataSet().do_search_read(request, model, + offset=offset, limit=limit, + domain=domain) + eval_context = request.session.evaluation_context( + request.context) + return [ + {'data': dict((key, {'value': value}) + for key, value in row.iteritems()), + 'color': self.process_colors(view, row, eval_context)} + for row in rows + ] + + def process_colors(self, view, row, context): + colors = view['arch']['attrs'].get('colors') + + if not colors: + return None + + color = [ + pair.split(':')[0] + for pair in colors.split(';') + if eval(pair.split(':')[1], dict(context, **row)) + ] + + if not color: + return None + elif len(color) == 1: + return color[0] + return 'maroon' + + class SearchView(View): _cp_path = "/base/searchview" @openerpweb.jsonrequest def load(self, req, model, view_id): - fields_view = self.fields_view_get(req.session, model, view_id, 'search') + fields_view = self.fields_view_get(req, model, view_id, 'search') return {'fields_view': fields_view} class Action(openerpweb.Controller): diff --git a/addons/base/static/src/css/base.css b/addons/base/static/src/css/base.css index c6040f1b0fd..cf6d0beabd4 100644 --- a/addons/base/static/src/css/base.css +++ b/addons/base/static/src/css/base.css @@ -443,6 +443,7 @@ body.openerp { padding-left: 10px; } + .openerp .oe-searchview-render-line { width:100%; } @@ -462,6 +463,75 @@ body.openerp { background: url(../img/icons/gtk-remove.png) repeat-y; padding-left: 18px; } +/* List */ +.openerp .oe-listview table { + clear: right; + width: 100%; + border-spacing: 0; + border: 1px solid silver; +} + +.openerp .oe-listview tr.odd { + background-color: #f3f3f3; +} +.openerp .oe-listview tbody tr:hover { + background-color: #ecebf2; +} +.openerp .oe-listview tbody tr:hover { + background-color: #eae9f0; +} + +.openerp .oe-listview td, +.openerp .oe-listview th { + vertical-align: middle; +} + +.openerp .oe-listview .oe-field-cell button { + padding: 0; + border: none; + background: none; +} +.openerp .oe-listview .oe-field-cell button:active { + opacity: 0.5; +} + +.openerp .oe-listview th.oe-actions { + text-align: left; +} +.openerp .oe-listview th.oe-list-pager { + text-align: right; +} + +/** list rounded corners + + rounded corners are a pain on tables: need to round not only table, but + also on the first and last children of the first and last row + */ +.openerp .oe-listview table { + -webkit-border-radius: 7px; + -moz-border-radius: 7px; + border-radius: 7px; +} +.openerp .oe-listview table thead tr:first-child th:first-child { + -webkit-border-top-left-radius: 7px; + -moz-border-radius-topleft: 7px; + border-top-left-radius: 7px; +} +.openerp .oe-listview table thead tr:first-child th:last-child { + -webkit-border-top-right-radius: 7px; + -moz-border-radius-topright: 7px; + border-top-right-radius: 7px; +} +.openerp .oe-listview table tbody tr:last-child th:first-child { + -webkit-border-bottom-left-radius: 7px; + -moz-border-radius-bottomleft: 7px; + border-bottom-left-radius: 7px; +} +.openerp .oe-listview table tbody tr:last-child td:last-child { + -webkit-border-bottom-right-radius: 7px; + -moz-border-radius-bottomright: 7px; + border-bottom-right-radius: 7px; +} /* Notebook */ .openerp .oe_form_notebook { @@ -489,7 +559,6 @@ body.openerp { background: #f9f9f9; } - /* Form */ .openerp table.oe_frame td { color: #4c4c4c; diff --git a/addons/base/static/src/js/data.js b/addons/base/static/src/js/data.js index 7b1a930c88f..5ed21be533c 100644 --- a/addons/base/static/src/js/data.js +++ b/addons/base/static/src/js/data.js @@ -108,6 +108,8 @@ openerp.base.DataSet = openerp.base.Controller.extend( /** @lends openerp.base. this.notification.notify("Unlink", ids); }, call: function (method, ids, args, callback) { + this.notification.notify( + "Calling", this.model + '#' + method + '(' + ids + ')'); ids = ids || []; args = args || []; return this.rpc('/base/dataset/call', { diff --git a/addons/base/static/src/js/form.js b/addons/base/static/src/js/form.js index b13ac7d8332..350d3e019bb 100644 --- a/addons/base/static/src/js/form.js +++ b/addons/base/static/src/js/form.js @@ -311,6 +311,64 @@ openerp.base.FormView = openerp.base.Controller.extend( /** @lends openerp.base /** @namespace */ openerp.base.form = {}; +openerp.base.form.compute_domain = function(expr, fields) { + var stack = []; + for (var i = expr.length - 1; i >= 0; i--) { + var ex = expr[i]; + if (ex.length == 1) { + var top = stack.pop(); + switch (ex[0]) { + case '|': + stack.push(stack.pop() || top); + continue; + case '&': + stack.push(stack.pop() && top); + continue; + case '!': + stack.push(!top); + continue; + default: + throw new Error('Unknown domain operator ' + ex[0]); + } + } + + var field = fields[ex[0]].value; + var op = ex[1]; + var val = ex[2]; + + switch (op.toLowerCase()) { + case '=': + case '==': + stack.push(field == val); + break; + case '!=': + case '<>': + stack.push(field != val); + break; + case '<': + stack.push(field < val); + break; + case '>': + stack.push(field > val); + break; + case '<=': + stack.push(field <= val); + break; + case '>=': + stack.push(field >= val); + break; + case 'in': + stack.push(_.indexOf(val, field) > -1); + break; + case 'not in': + stack.push(_.indexOf(val, field) == -1); + break; + default: + this.log("Unsupported operator in attrs :", op); + } + } + return _.indexOf(stack, false) == -1; +}, openerp.base.form.Widget = openerp.base.Controller.extend({ init: function(view, node) { this.view = view; @@ -335,69 +393,11 @@ openerp.base.form.Widget = openerp.base.Controller.extend({ this.$element = $('#' + this.element_id); }, process_attrs: function() { + var compute_domain = openerp.base.form.compute_domain; for (var a in this.attrs) { - this[a] = this.eval_attrs(this.attrs[a]); + this[a] = compute_domain(this.attrs[a], this.view.fields); } }, - eval_attrs: function(expr) { - var stack = []; - for (var i = 0; i < expr.length; i++) { - var ex = expr[i]; - if (ex.length == 1) { - stack.push(ex[0]); - continue; - } - - var field = this.view.fields[ex[0]].value; - var op = ex[1]; - var val = ex[2]; - - switch (op.toLowerCase()) { - case '=': - case '==': - stack.push(field == val); - break; - case '!=': - case '<>': - stack.push(field != val); - break; - case '<': - stack.push(field < val); - break; - case '>': - stack.push(field > val); - break; - case '<=': - stack.push(field <= val); - break; - case '>=': - stack.push(field >= val); - break; - case 'in': - stack.push(_.indexOf(val, field) > -1); - break; - case 'not in': - stack.push(_.indexOf(val, field) == -1); - break; - default: - this.log("Unsupported operator in attrs :", op); - } - } - - for (var j = stack.length-1; j >- 1; j--) { - switch (stack[j]) { - case '|': - var result = stack[j + 1] || stack[j + 2]; - stack.splice(j, 3, result); - break; - case '&': - var result = stack[j + 1] && stack[j + 2]; - stack.splice(j, 3, result); - break; - } - } - return _.indexOf(stack, false) == -1; - }, update_dom: function() { this.$element.toggle(!this.invisible); }, diff --git a/addons/base/static/src/js/list.js b/addons/base/static/src/js/list.js index 4b1eeaaf993..d823bc81489 100644 --- a/addons/base/static/src/js/list.js +++ b/addons/base/static/src/js/list.js @@ -8,7 +8,9 @@ openerp.base.ListView = openerp.base.Controller.extend( // list rows can be deleted 'deletable': true, // whether the column headers should be displayed - 'header': true + 'header': true, + // display addition button, with that label + 'addable': "New" }, /** * @constructs @@ -21,6 +23,7 @@ openerp.base.ListView = openerp.base.Controller.extend( * @param {Boolean} [options.selectable=true] determines whether view rows are selectable (e.g. via a checkbox) * @param {Boolean} [options.header=true] should the list's header be displayed * @param {Boolean} [options.deletable=true] are the list rows deletable + * @param {null|String} [options.addable="New"] should the new-record button be displayed, and what should its label be. Use ``null`` to hide the button. */ init: function(view_manager, session, element_id, dataset, view_id, options) { this._super(session, element_id); @@ -35,20 +38,37 @@ openerp.base.ListView = openerp.base.Controller.extend( this.options = _.extend({}, this.defaults, options || {}); }, start: function() { - //this.log('Starting ListView '+this.model+this.view_id) + this.$element.addClass('oe-listview'); return this.rpc("/base/listview/load", {"model": this.model, "view_id":this.view_id, toolbar:!!this.view_manager.sidebar}, this.on_loaded); }, on_loaded: function(data) { + var self = this; this.fields_view = data.fields_view; //this.log(this.fields_view); this.name = "" + this.fields_view.arch.attrs.string; var fields = this.fields_view.fields; + var domain_computer = openerp.base.form.compute_domain; this.columns = _(this.fields_view.arch.children).chain() .map(function (field) { var name = field.attrs.name; - return _.extend({id: name, tag: field.tag}, field.attrs, fields[name]); + var column = _.extend({id: name, tag: field.tag}, + field.attrs, fields[name]); + // attrs computer + if (column.attrs) { + var attrs = eval('(' + column.attrs + ')'); + column.attrs_for = function (fields) { + var result = {}; + for (var attr in attrs) { + result[attr] = domain_computer(attrs[attr], fields); + } + return result; + }; + } else { + column.attrs_for = function () { return {}; }; + } + return column; }).value(); this.visible_columns = _.filter(this.columns, function (column) { @@ -57,20 +77,46 @@ openerp.base.ListView = openerp.base.Controller.extend( this.$element.html(QWeb.render("ListView", this)); // Head hook - this.$element.find('#oe-list-delete').click(this.do_delete_selected); + this.$element.find('#oe-list-add').click(this.do_add_record); + this.$element.find('#oe-list-delete') + .hide() + .click(this.do_delete_selected); + var $table = this.$element.find('table'); // Cell events - this.$element.find('table').delegate( - 'th.oe-record-selector', 'click', function (e) { - // A click in the selection cell should not activate the - // linking feature - e.stopImmediatePropagation(); + $table.delegate( + 'th.oe-record-selector', 'click', function (e) { + // TODO: ~linear performances, would a simple counter work? + if ($table.find('th.oe-record-selector input:checked').length) { + $table.find('#oe-list-delete').show(); + } else { + $table.find('#oe-list-delete').hide(); + } + // A click in the selection cell should not activate the + // linking feature + e.stopImmediatePropagation(); }); - this.$element.find('table').delegate( + $table.delegate( + 'td.oe-field-cell button', 'click', function (e) { + var $cell = $(e.currentTarget).closest('td'); + var col_index = $cell.prevAll('td').length; + var field = self.visible_columns[col_index]; + var action = field.name; + + var $row = $cell.parent('tr'); + var row = self.rows[$row.prevAll().length]; + + var context = _.extend( + {}, self.dataset.context, field.context || {}); + self.dataset.call(action, [row.data.id.value], [context], + self.do_reload); + e.stopImmediatePropagation(); + }); + $table.delegate( 'td.oe-record-delete button', 'click', this.do_delete); // Global rows handlers - this.$element.find('table').delegate( + $table.delegate( 'tr', 'click', this.on_select_row); // sidebar stuff @@ -85,11 +131,27 @@ openerp.base.ListView = openerp.base.Controller.extend( * @returns {Promise} promise to the end of view rendering (list views are asynchronously filled for improved responsiveness) */ do_fill_table: function(records) { + var $table = this.$element.find('table'); this.rows = records; - var $table = this.$element.find('table'); + // Keep current selected record, if it's still in our new search + var current_record_id = this.dataset.ids[this.dataset.index]; + this.dataset.ids = _(records).chain().map(function (record) { + return record.data.id.value; + }).value(); + this.dataset.index = _.indexOf(this.dataset.ids, current_record_id); + if (this.dataset.index === -1) { + this.dataset.index = 0; + } + + // TODO: offset, length, count + var results = this.rows.length; + $table.find('.oe-pager-last').text(results); + $table.find('.oe-pager-total').text(results); + + // remove all data lines - $table.find('tbody').remove(); + var $old_body = $table.find('tbody'); // add new content var columns = this.columns, @@ -100,10 +162,10 @@ openerp.base.ListView = openerp.base.Controller.extend( var PAGE_SIZE = 50, bodies_count = Math.ceil(this.rows.length / PAGE_SIZE), body = 0, - $body = $('
').appendTo($table); + $body = $('').appendTo($table); + var rendered = $.Deferred(); var render_body = function () { - var rendered = $.Deferred(); setTimeout(function () { $body.append( QWeb.render("ListView.rows", { @@ -118,9 +180,29 @@ openerp.base.ListView = openerp.base.Controller.extend( rendered.resolve(); } }, 0); - return rendered.promise(); }; - return render_body(); + render_body(); + + return rendered.promise().then(function () { + $old_body.remove(); + }); + }, + /** + * Asks the view manager to switch to a different view, using the provided + * record index (within the current dataset). + * + * If the index is null, ``switch_to_record`` asks for the creation of a + * new record. + * + * @param {Number|null} index the record index (in the current dataset) to switch to + * @param {String} [view="form"] the view to switch to + */ + switch_to_record:function (index, view) { + view = view || 'form'; + this.dataset.index = index; + _.delay(_.bind(function () { + this.view_manager.on_mode_switch(view); + }, this)); }, on_select_row: function (event) { var $target = $(event.currentTarget); @@ -130,26 +212,41 @@ openerp.base.ListView = openerp.base.Controller.extend( // count number of preceding siblings to line clicked var row = this.rows[$target.prevAll().length]; - var index = _.indexOf(this.dataset.ids, row.id); + var index = _.indexOf(this.dataset.ids, row.data.id.value); if (index == undefined || index === -1) { return; } - this.dataset.index = index; - _.delay(_.bind(function () { - this.view_manager.on_mode_switch('form'); - }, this)); - - }, + this.switch_to_record(index); + }, do_show: function () { - // TODO: re-trigger search this.$element.show(); + if (this.hidden) { + this.do_reload(); + this.hidden = false; + } }, do_hide: function () { this.$element.hide(); + this.hidden = true; + }, + /** + * Reloads the search view based on the current settings (dataset & al) + */ + do_reload: function () { + // TODO: need to do 5 billion tons of pre-processing, bypass + // DataSet for now + //self.dataset.read_slice(self.dataset.fields, 0, self.limit, + // self.do_fill_table); + return this.rpc('/base/listview/fill', { + 'model': this.dataset.model, + 'id': this.view_id, + 'context': this.dataset.context, + 'domain': this.dataset.domain + }, this.do_fill_table); }, do_search: function (domains, contexts, groupbys) { var self = this; - this.rpc('/base/session/eval_domain_and_context', { + return this.rpc('/base/session/eval_domain_and_context', { domains: domains, contexts: contexts, group_by_seq: groupbys @@ -157,12 +254,12 @@ openerp.base.ListView = openerp.base.Controller.extend( // TODO: handle non-empty results.group_by with read_group self.dataset.context = results.context; self.dataset.domain = results.domain; - self.dataset.read_slice(self.dataset.fields, 0, self.limit, self.do_fill_table); + return self.do_reload(); }); }, do_update: function () { var self = this; - self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table); + //self.dataset.read_ids(self.dataset.ids, self.dataset.fields, self.do_fill_table); }, /** * Handles the signal to delete a line from the DOM @@ -173,7 +270,17 @@ openerp.base.ListView = openerp.base.Controller.extend( // don't link to forms e.stopImmediatePropagation(); this.dataset.unlink( - [this.rows[$(e.currentTarget).closest('tr').prevAll().length].id]); + [this.rows[$(e.currentTarget).closest('tr').prevAll().length].data.id.value]); + }, + /** + * Handles signal for the addition of a new record (can be a creation, + * can be the addition from a remote source, ...) + * + * The default implementation is to switch to a new record on the form view + */ + do_add_record: function () { + this.notification.notify('Add', "New record"); + this.switch_to_record(null); }, /** * Handles deletion of all selected lines @@ -195,7 +302,7 @@ openerp.base.ListView = openerp.base.Controller.extend( var rows = this.rows; return this.$element.find('th.oe-record-selector input:checked') .closest('tr').map(function () { - return rows[$(this).prevAll().length].id; + return rows[$(this).prevAll().length].data.id.value; }).get(); } }); diff --git a/addons/base/static/src/js/views.js b/addons/base/static/src/js/views.js index a0bb706714c..a1825824c2e 100644 --- a/addons/base/static/src/js/views.js +++ b/addons/base/static/src/js/views.js @@ -144,8 +144,10 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ this.searchview.stop(); } this.searchview = new openerp.base.SearchView(this, this.session, this.element_id + "_search", this.dataset, view_id, search_defaults); - this.searchview.on_search.add(function() { - self.views[self.active_view].controller.do_search.apply(self, arguments); + this.searchview.on_search.add(function(domains, contexts, groupbys) { + self.views[self.active_view].controller.do_search.call( + self, domains.concat(self.domains()), + contexts.concat(self.contexts()), groupbys); }); return this.searchview.start(); }, @@ -159,6 +161,23 @@ openerp.base.ViewManager = openerp.base.Controller.extend({ on_remove: function() { }, on_edit: function() { + }, + /** + * Domains added on searches by the view manager, to override in subsequent + * view manager in order to add new pieces of domains to searches + * + * @returns an empty list + */ + domains: function () { + return []; + }, + /** + * Contexts added on searches by the view manager. + * + * @returns an empty list + */ + contexts: function () { + return []; } }); @@ -180,8 +199,6 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ this.sidebar.start(); } - // init search view - var view_id = this.action.search_view_id ? this.action.search_view_id[0] || false : false; var search_defaults = {}; _.each(this.action.context, function (value, key) { var match = /^search_default_(.*)$/.exec(key); @@ -189,10 +206,12 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ search_defaults[match[1]] = value; } }); - var searchview_loaded = null; - if (view_id) { - searchview_loaded = this.setup_search_view(view_id,search_defaults); - } + + // init search view + var searchview_id = this.action.search_view_id && this.action.search_view_id[0]; + + var searchview_loaded = this.setup_search_view( + searchview_id || false, search_defaults); // schedule auto_search if (searchview_loaded != null && this.action['auto_search']) { @@ -206,6 +225,28 @@ openerp.base.ViewManagerAction = openerp.base.ViewManager.extend({ this.sidebar.stop(); } this._super(); + }, + /** + * adds action domain to the search domains + * + * @returns the action's domain + */ + domains: function () { + if (!this.action.domain) { + return []; + } + return [this.action.domain]; + }, + /** + * adds action context to the search contexts + * + * @returns the action's context + */ + contexts: function () { + if (!this.action.context) { + return []; + } + return [this.action.context]; } }); diff --git a/addons/base/static/src/xml/base.xml b/addons/base/static/src/xml/base.xml index 9c73bbc66f3..129198f0ea4 100644 --- a/addons/base/static/src/xml/base.xml +++ b/addons/base/static/src/xml/base.xml @@ -154,38 +154,37 @@ -