diff --git a/addons/web/static/src/js/fields/abstract_field.js b/addons/web/static/src/js/fields/abstract_field.js index 3d7a827ff13..d50fd493521 100644 --- a/addons/web/static/src/js/fields/abstract_field.js +++ b/addons/web/static/src/js/fields/abstract_field.js @@ -207,6 +207,17 @@ var AbstractField = Widget.extend({ this._reset(record, event); return this._render(); }, + /** + * @override performModelRPC from ServicesMixin + * Adds the dataPointID to the option parameter so that the BasicController + * (the FieldManagerMixin) knows the context to bind to the rpc call. + */ + performModelRPC: function (model, method, args, kwargs, options) { + return this._super(model, method, args, kwargs, _.extend({ + dataPointID: this.dataPointID, + fieldName: this.name, + }, options || {})); + }, //-------------------------------------------------------------------------- // Private diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 02c41c8c9e5..de46740bcce 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -224,21 +224,18 @@ var FieldMany2One = AbstractRelationalField.extend({ */ _quickCreate: function (name) { var self = this; - var slow_create = this._searchCreatePopup.bind(this, "form", false, this._createContext(name)); + var slowCreate = this._searchCreatePopup.bind(this, "form", false, this._createContext(name)); if (this.nodeOptions.quick_create) { - this.trigger_up('name_create', { - model: this.field.relation, - name: name, - context: data.build_context(this.record, this.context), - on_success: function (result) { + this.performModelRPC(this.field.relation, "name_create", [name]).then( + function (result) { if (self.mode === "edit") { self.reinitialize({id: result[0], display_name: result[1]}); } }, - on_fail: slow_create, - }); + slowCreate + ); } else { - slow_create(); + slowCreate(); } }, /** @@ -296,69 +293,66 @@ var FieldMany2One = AbstractRelationalField.extend({ exclusion_domain.push(['id', 'not in', blacklisted_ids]); } var domain = new data.CompoundDomain(data.build_domain(this.record, this.domain), exclusion_domain); - this.trigger_up('name_search', { - model: this.field.relation, - search_val: search_val, - domain: pyeval.eval('domain', domain), - operator: 'ilike', + + this.performModelRPC(this.field.relation, "name_search", [], { + name: search_val, + args: pyeval.eval("domain", domain), + operator: "ilike", limit: this.limit + 1, - on_success: function (result) { - // possible selections for the m2o - var values = _.map(result, function (x) { - x[1] = x[1].split("\n")[0]; - return { - label: _.str.escapeHTML(x[1].trim()) || data.noDisplayContent, - value: x[1], - name: x[1], - id: x[0], - }; + }).then(function (result) { + // possible selections for the m2o + var values = _.map(result, function (x) { + x[1] = x[1].split("\n")[0]; + return { + label: _.str.escapeHTML(x[1].trim()) || data.noDisplayContent, + value: x[1], + name: x[1], + id: x[0], + }; + }); + + // search more... if more results than limit + if (values.length > self.limit) { + values = values.slice(0, self.limit); + values.push({ + label: _t("Search More..."), + action: function () { + self.performModelRPC(self.field.relation, "name_search", { + name: search_val, + args: pyeval.eval('domain', domain), + operator: "ilike", + limit: 160, + }).then(self._searchCreatePopup.bind(self, "search")); + }, + classname: 'o_m2o_dropdown_option', }); + } + var create_enabled = self.can_create && !self.nodeOptions.no_create; + // quick create + var raw_result = _.map(result, function (x) { return x[1]; }); + if (create_enabled && !self.nodeOptions.no_quick_create && + search_val.length > 0 && !_.contains(raw_result, search_val)) { + values.push({ + label: _.str.sprintf(_t('Create "%s"'), + $('').text(search_val).html()), + action: self._quickCreate.bind(self, search_val), + classname: 'o_m2o_dropdown_option' + }); + } + // create and edit ... + if (create_enabled && !self.nodeOptions.no_create_edit) { + values.push({ + label: _t("Create and Edit..."), + action: self._searchCreatePopup.bind(self, "form", false, self._createContext(search_val)), + classname: 'o_m2o_dropdown_option', + }); + } else if (values.length === 0) { + values.push({ + label: _t("No results to show..."), + }); + } - // search more... if more results than limit - if (values.length > self.limit) { - values = values.slice(0, self.limit); - values.push({ - label: _t("Search More..."), - action: function () { - self.trigger_up('name_search', { - model: self.field.relation, - search_val: search_val, - domain: pyeval.eval('domain', domain), - operator: 'ilike', - limit: 160, - on_success: self._searchCreatePopup.bind(self, "search"), - }); - }, - classname: 'o_m2o_dropdown_option', - }); - } - var create_enabled = self.can_create && !self.nodeOptions.no_create; - // quick create - var raw_result = _.map(result, function (x) { return x[1]; }); - if (create_enabled && !self.nodeOptions.no_quick_create && - search_val.length > 0 && !_.contains(raw_result, search_val)) { - values.push({ - label: _.str.sprintf(_t('Create "%s"'), - $('').text(search_val).html()), - action: self._quickCreate.bind(self, search_val), - classname: 'o_m2o_dropdown_option' - }); - } - // create and edit ... - if (create_enabled && !self.nodeOptions.no_create_edit) { - values.push({ - label: _t("Create and Edit..."), - action: self._searchCreatePopup.bind(self, "form", false, self._createContext(search_val)), - classname: 'o_m2o_dropdown_option', - }); - } else if (values.length === 0) { - values.push({ - label: _t("No results to show..."), - }); - } - - def.resolve(values); - }, + def.resolve(values); }); return def; diff --git a/addons/web/static/src/js/views/basic/basic_model.js b/addons/web/static/src/js/views/basic/basic_model.js index 4413168cc73..e28daffc587 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -613,42 +613,6 @@ var BasicModel = AbstractModel.extend({ }); return record.id; }, - /** - * This method just does a name_create call to the server. - * - * @todo Maybe this method should also create a local record... - * - * @param {string} model the name of the model - * @param {string} name the desired name of the created record - * @param {Object} context the context to be passed to the rpc - * @returns {Deferred} - */ - nameCreate: function (model, name, context) { - return this.performModelRPC(model, 'name_create', [name], { - context: _.extend({}, session.user_context, context), - }); - }, - /** - * Do a name_search. I think that it should be done directly by the widget - * @todo remove this - * @deprecated - * - * @param {any} model - * @param {any} name - * @param {any} domain - * @param {any} operator - * @param {any} limit - * @returns - */ - nameSearch: function (model, name, domain, operator, limit) { - // fixme: correctly handle context - return this.performModelRPC(model, 'name_search', [], { - name: name || '', - args: domain || [], - operator: operator || 'ilike', - limit: limit || 0, - }); - }, /** * This is an extremely important method. All changes in any field go * through this method. It will then apply them in the local state, check @@ -909,6 +873,20 @@ var BasicModel = AbstractModel.extend({ }); } }, + /** + * Every RPC done by the model need to add some context, which is a + * combination of the context of the session, of the record/list, and/or of + * the concerned field. This method combines all these contexts and evaluate + * them with the proper evalcontext. + * + * @param {string} recordID - the id of an element from the localData + * @see _getContext for the options parameter + * @returns {Object} the evaluated context + */ + getContext: function (recordID, options) { + var record = this.localData[recordID]; + return this._getContext(record, options); + }, //-------------------------------------------------------------------------- // Private @@ -2039,4 +2017,3 @@ var BasicModel = AbstractModel.extend({ return BasicModel; }); - diff --git a/addons/web/static/src/js/views/field_manager_mixin.js b/addons/web/static/src/js/views/field_manager_mixin.js index 92c3da6a1fc..e2cfa681116 100644 --- a/addons/web/static/src/js/views/field_manager_mixin.js +++ b/addons/web/static/src/js/views/field_manager_mixin.js @@ -14,13 +14,11 @@ var core = require('web.core'); var qweb = core.qweb; -return { +var FieldManagerMixin = { custom_events: { field_changed: '_onFieldChanged', - name_create: '_onNameCreate', - name_get: '_onNameGet', - name_search: '_onNameSearch', load: '_onLoad', + call_service: '_onCallService', }, /** * A FieldManagerMixin can be initialized with an instance of a basicModel. @@ -123,52 +121,36 @@ return { }); }, /** - * When a name_create event arrives, the nameCreate method from the model - * should be called. + * Some widgets perform model RPCs. These are intercepted so that the + * context can be automatically added according to the given dataPointID + * option the AbstractField implementation automatically adds. * - * @param {OdooEvent} event + * @param {OdooEvent} e + * @param {string} e.data.service - the service called, here we are only + * interested in the "ajax" service + * @param {string} e.data.method - the service method called, here we are + * only interested by the "rpc" method + * @param {Array} e.data.args + * the args parameters contains the route, the route arguments and + * the RPC call options. This method purpose is to check the RPC + * call options for the dataPointID (and field name) and merge the + * appropriate context with the route arguments' one. */ - _onNameCreate: function (event) { - var data = event.data; - if (!data.on_success) { return; } - this.model - .nameCreate(data.model, data.name) - .then(data.on_success) - .fail(function () { - if (data.on_fail) { - data.on_fail(); - } - }); - }, - /** - * When a name_get event arrives, the name_get method from the model should - * be called. - * - * @param {OdooEvent} event - */ - _onNameGet: function (event) { - var data = event.data; - if (!data.on_success) { return; } - this.model.name_get(data.model, data.ids) - .then(data.on_success); // fixme: handle context - }, - /** - * Some field widgets need to perform a namesearch. For example, a many2one - * widget when it needs to fetch its autocompletion data. - * - * @todo Note: I think that this is wrong, the field widget should just perform - * a name_search rpc on its own, no need for this. - * - * @deprecated Just do a regular name_search - * @param {OdooEvent} event - */ - _onNameSearch: function (event) { - var data = event.data; - if (!data.on_success) { return; } - this.model - .nameSearch(data.model, data.search_val, data.domain, data.operator, data.limit) - .then(data.on_success); + _onCallService: function (e) { + if (e.data.service !== "ajax" || e.data.method !== "rpc") return; + + var args = e.data.args[1]; + var options = e.data.args[2]; + if (args.kwargs && options.dataPointID) { + args.kwargs.context = _.extend( + this.model.getContext(options.dataPointID, { + fieldName: options.fieldName || false, + }), + args.kwargs.context || {} + ); + } }, }; +return FieldManagerMixin; }); diff --git a/addons/web/static/src/js/views/kanban/kanban_controller.js b/addons/web/static/src/js/views/kanban/kanban_controller.js index 7ea09f38eb5..b8010c97899 100644 --- a/addons/web/static/src/js/views/kanban/kanban_controller.js +++ b/addons/web/static/src/js/views/kanban/kanban_controller.js @@ -299,10 +299,12 @@ var KanbanController = BasicController.extend({ }); } data.context['default_' + data.groupedBy[0]] = column.id; - this.model - .nameCreate(data.model, event.data.value, data.context) - .then(add_record) - .fail(function (event) { + + this.performModelRPC(data.model, "name_create", [event.data.value], { + context: data.context, + }).then( + add_record, + function (event) { event.preventDefault(); var popup = new form_common.SelectCreatePopup(this); popup.select_element( @@ -316,7 +318,8 @@ var KanbanController = BasicController.extend({ { default_name: event.data.value } ); popup.on("elements_selected", null, add_record); - }); + } + ); }, /** * @param {OdooEvent} event diff --git a/addons/web/static/test/fields/relational_fields_tests.js b/addons/web/static/test/fields/relational_fields_tests.js index 6ed77912896..cfbbeacc2c2 100644 --- a/addons/web/static/test/fields/relational_fields_tests.js +++ b/addons/web/static/test/fields/relational_fields_tests.js @@ -399,6 +399,41 @@ QUnit.module('relational_fields', { assert.strictEqual(form.$('a').text(), '', 'the tag a should be empty'); }); + QUnit.test('check that the field context is used when doing a name_search in a m2o', function (assert) { + assert.expect(3); + + var form = createView({ + View: FormView, + model: 'partner', + data: this.data, + arch: + '
', + res_id: 1, + session: {user_context: {hey: "ho"}}, + manualDestroy: true, + mockRPC: function (route, args) { + if (route === '/web/dataset/call_kw/product/name_search') { + var context = args.kwargs.context; + assert.strictEqual(context.hey, "ho", + 'the session context should have been used for the rpc'); + assert.strictEqual(context.hello, "world", + 'the field context should have been used for the RPC'); + assert.strictEqual(context.test, "yop", + 'the field context should have been properly evaluated'); + } + return this._super.apply(this, arguments); + }, + }); + + form.$buttons.find('.o_form_button_edit').click(); + form.$('input').first().click(); + + form.destroy(); + }); + QUnit.module('FieldOne2Many'); QUnit.test('one2many basic properties', function (assert) {