diff options
| author | Martin Geubelle <mge@odoo.com> | 2017-04-05 13:23:31 +0200 |
|---|---|---|
| committer | Aaron Bohy <aab@odoo.com> | 2017-04-11 19:44:38 +0200 |
| commit | 0cb500594627c4cbe94d4e8c86d69f74ca031f35 (patch) | |
| tree | d5a22a28bb4b9857a7d775ce48badd1966cbf24b /addons | |
| parent | b6ce7b136f561f156fb9ed1cc8694da86aff466e (diff) | |
[FIX] web, *: basic model: makeRecord with X2manys
`makeRecord` is now asynchronous as it will load data
for relational fields (X2manys and many2ones if `display_name`
is not fetched).
This change has implied a refactoring in all `makeRecord`
occurences to adapt with the returned deferred.
Diffstat (limited to 'addons')
4 files changed, 351 insertions, 130 deletions
diff --git a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js index 5bc2740fc5b..6a43610596d 100644 --- a/addons/account/static/src/js/reconciliation/reconciliation_renderer.js +++ b/addons/account/static/src/js/reconciliation/reconciliation_renderer.js @@ -37,23 +37,27 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, { * @override */ start: function () { - this._super(); + var self = this; + var defs = [this._super.apply(this, arguments)]; this.time = Date.now(); this.$progress = this.$('.progress'); if (this._initialState.bank_statement_id) { - this.handleNameRecord = this.model.makeRecord("account.bank.statement", [{ + var def = this.model.makeRecord("account.bank.statement", [{ type: 'char', name: 'name', attrs: {string: ""}, value: this._initialState.bank_statement_id.display_name - }]); - this.name = new basic_fields.FieldChar(this, - 'name', this.model.get(this.handleNameRecord), - {mode: 'edit', required: true}); - - this.name.appendTo(this.$('.statement_name_edition')); - this.$('.statement_name').text(this._initialState.bank_statement_id.display_name); + }]).then(function (recordID) { + self.handleNameRecord = recordID; + self.name = new basic_fields.FieldChar(self, + 'name', self.model.get(self.handleNameRecord), + {mode: 'edit', required: true}); + + self.name.appendTo(self.$('.statement_name_edition')); + self.$('.statement_name').text(self._initialState.bank_statement_id.display_name); + }); + defs.push(def); } this.$('h1.statement_name').text(this._initialState.title); @@ -66,6 +70,8 @@ var StatementRenderer = Widget.extend(FieldManagerMixin, { } }.bind(this); $('body').on('keyup', this.enterHandler); + + return $.when.apply($, defs); }, /** * @override @@ -213,21 +219,23 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { this.model = model; this._initialState = state; - - this.fields = { - 'partner_id' : new relational_fields.FieldMany2One(this, - 'partner_id', - this._makePartnerRecord(state.st_line.partner_id, state.st_line.partner_name), - {mode: 'edit'} - ) - }; }, /** * @override */ start: function () { - this.fields.partner_id.appendTo(this.$('.accounting_view caption')); + var self = this; + var def1 = this._makePartnerRecord(this._initialState.st_line.partner_id, this._initialState.st_line.partner_name).then(function (recordID) { + self.fields = { + partner_id : new relational_fields.FieldMany2One(self, + 'partner_id', + self.model.get(recordID), + {mode: 'edit'} + ) + }; + self.fields.partner_id.appendTo(self.$('.accounting_view caption')); + }); this.$('thead .line_info_button').attr("data-content", qweb.render('reconciliation.line.statement_line.details', {'state': this._initialState})); this.$el.popover({ 'selector': '.line_info_button', @@ -238,7 +246,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { 'animation': false, 'toggle': 'popover' }); - delete this._initialState; + var def2 = this._super.apply(this, arguments); + return $.when(def1, def2); }, //-------------------------------------------------------------------------- @@ -251,15 +260,17 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { * @param {object} state - statement line */ update: function (state) { + var self = this; // isValid this.$('caption .o_buttons button.o_validate').toggleClass('hidden', !!state.balance.type); this.$('caption .o_buttons button.o_reconcile').toggleClass('hidden', state.balance.type <= 0); this.$('caption .o_buttons .o_no_valid').toggleClass('hidden', state.balance.type >= 0); - // parnter_id - var record = this._makePartnerRecord(state.st_line.partner_id, state.st_line.partner_name); - this.fields.partner_id.reset(record); - this.$el.attr('data-partner', state.st_line.partner_id); + // partner_id + this._makePartnerRecord(state.st_line.partner_id, state.st_line.partner_name).then(function (recordID) { + self.fields.partner_id.reset(self.model.get(recordID)); + self.$el.attr('data-partner', state.st_line.partner_id); + }); // mode this.$('.create, .match').each(function () { @@ -332,7 +343,7 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { } var data = this.model.get(this.handleCreateRecord).data; this.model.notifyChanges(this.handleCreateRecord, state.createForm); - record = this.model.get(this.handleCreateRecord); + var record = this.model.get(this.handleCreateRecord); _.each(this.fields, function (field, fieldName) { if (fieldName === "partner_id") return; if ((data[fieldName] || state.createForm[fieldName]) && !_.isEqual(state.createForm[fieldName], data[fieldName])) { @@ -351,15 +362,18 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { * @private * @param {integer} partnerID * @param {string} partnerName - * @returns {object} dataPoint + * @returns {string} local id of the dataPoint */ _makePartnerRecord: function (partnerID, partnerName) { - var recordID = this.model.makeRecord('account.bank.statement.line', [{ - relation: 'res.partner', - type: 'many2one', - name: 'partner_id', - value: [partnerID, partnerName] - }], { + var field = { + relation: 'res.partner', + type: 'many2one', + name: 'partner_id', + }; + if (partnerID) { + field.value = [partnerID, partnerName]; + } + return this.model.makeRecord('account.bank.statement.line', [field], { partner_id: { domain: [["parent_id", "=", false], "|", ["customer", "=", true], ["supplier", "=", true]], options: { @@ -367,7 +381,6 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { } } }); - return this.model.get(recordID); }, /** @@ -377,7 +390,8 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { * @param {object} state - statement line */ _renderCreate: function (state) { - this.handleCreateRecord = this.model.makeRecord('account.bank.statement.line', [{ + var self = this; + this.model.makeRecord('account.bank.statement.line', [{ relation: 'account.account', type: 'many2one', name: 'account_id', @@ -403,35 +417,37 @@ var LineRenderer = Widget.extend(FieldManagerMixin, { account_id: {string: _t("Account")}, label: {string: _t("Label")}, amount: {string: _t("Account")} - }); - var record = this.model.get(this.handleCreateRecord); + }).then(function (recordID) { + self.handleCreateRecord = recordID; + var record = self.model.get(self.handleCreateRecord); - this.fields.account_id = new relational_fields.FieldMany2One(this, - 'account_id', record, {mode: 'edit', required: true}); + self.fields.account_id = new relational_fields.FieldMany2One(self, + 'account_id', record, {mode: 'edit', required: true}); - this.fields.journal_id = new relational_fields.FieldMany2One(this, - 'journal_id', record, {mode: 'edit'}); + self.fields.journal_id = new relational_fields.FieldMany2One(self, + 'journal_id', record, {mode: 'edit'}); - this.fields.tax_id = new relational_fields.FieldMany2One(this, - 'tax_id', record, {mode: 'edit'}); + self.fields.tax_id = new relational_fields.FieldMany2One(self, + 'tax_id', record, {mode: 'edit'}); - this.fields.analytic_account_id = new relational_fields.FieldMany2One(this, - 'analytic_account_id', record, {mode: 'edit'}); + self.fields.analytic_account_id = new relational_fields.FieldMany2One(self, + 'analytic_account_id', record, {mode: 'edit'}); - this.fields.label = new basic_fields.FieldChar(this, - 'label', record, {mode: 'edit', required: true}); + self.fields.label = new basic_fields.FieldChar(self, + 'label', record, {mode: 'edit', required: true}); - this.fields.amount = new basic_fields.FieldFloat(this, - 'amount', record, {mode: 'edit', required: true}); + self.fields.amount = new basic_fields.FieldFloat(self, + 'amount', record, {mode: 'edit', required: true}); - var $create = $(qweb.render("reconciliation.line.create", {'state': state})); - this.fields.account_id.appendTo($create.find('.create_account_id .o_td_field')); - this.fields.journal_id.appendTo($create.find('.create_journal_id .o_td_field')); - this.fields.tax_id.appendTo($create.find('.create_tax_id .o_td_field')); - this.fields.analytic_account_id.appendTo($create.find('.create_analytic_account_id .o_td_field')); - this.fields.label.appendTo($create.find('.create_label .o_td_field')); - this.fields.amount.appendTo($create.find('.create_amount .o_td_field')); - this.$('.create').append($create); + var $create = $(qweb.render("reconciliation.line.create", {'state': state})); + self.fields.account_id.appendTo($create.find('.create_account_id .o_td_field')); + self.fields.journal_id.appendTo($create.find('.create_journal_id .o_td_field')); + self.fields.tax_id.appendTo($create.find('.create_tax_id .o_td_field')); + self.fields.analytic_account_id.appendTo($create.find('.create_analytic_account_id .o_td_field')); + self.fields.label.appendTo($create.find('.create_label .o_td_field')); + self.fields.amount.appendTo($create.find('.create_amount .o_td_field')); + self.$('.create').append($create); + }); }, //-------------------------------------------------------------------------- @@ -598,33 +614,6 @@ var ManualRenderer = StatementRenderer.extend({ */ var ManualLineRenderer = LineRenderer.extend({ template: "reconciliation.manual.line", - - /** - * create partner_id field in readonly mode - * - * @override - */ - init: function (parent, model, state) { - this._super.apply(this, arguments); - if (state.partner_id) { - this.fields.partner_id = new relational_fields.FieldMany2One(this, - 'partner_id', - this._makePartnerRecord(state.partner_id, state.partner_name), - {mode: 'readonly'} - ); - } else { - this.fields.title_account_id = new relational_fields.FieldMany2One(this, - 'account_id', - this.model.get(this.model.makeRecord('account.move.line', [{ - relation: 'account.account', - type: 'many2one', - name: 'account_id', - value: [state.account_id.id, state.account_id.display_name] - }])), - {mode: 'readonly'} - ); - } - }, /** * @override * @param {string} handle @@ -643,13 +632,44 @@ var ManualLineRenderer = LineRenderer.extend({ * @override */ start: function () { - this._super(); - if (!this.fields.title_account_id) { - this.fields.partner_id.$el.prependTo(this.$('.accounting_view thead td:eq(1) span:first')); - } else { - this.fields.partner_id.destroy(); - this.fields.title_account_id.appendTo(this.$('.accounting_view thead td:eq(1) span:first')); - } + var self = this; + return this._super.apply(this, arguments).then(function () { + var defs = []; + var def; + if (self._initialState.partner_id) { + def = self._makePartnerRecord(self._initialState.partner_id, self._initialState.partner_name).then(function (recordID) { + self.fields.partner_id = new relational_fields.FieldMany2One(self, + 'partner_id', + self.model.get(recordID), + {mode: 'readonly'} + ); + }); + defs.push(def); + } else { + def = self.model.makeRecord('account.move.line', [{ + relation: 'account.account', + type: 'many2one', + name: 'account_id', + value: [self._initialState.account_id.id, self._initialState.account_id.display_name], + }]).then(function (recordID) { + self.fields.title_account_id = new relational_fields.FieldMany2One(self, + 'account_id', + self.model.get(recordID), + {mode: 'readonly'} + ); + }); + defs.push(def); + } + + return $.when.apply($, defs).then(function () { + if (!self.fields.title_account_id) { + self.fields.partner_id.$el.prependTo(self.$('.accounting_view thead td:eq(1) span:first')); + } else { + self.fields.partner_id.destroy(); + self.fields.title_account_id.appendTo(self.$('.accounting_view thead td:eq(1) span:first')); + } + }); + }); }, /** * @override 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 1d511b1eb34..7badafa43c5 100644 --- a/addons/web/static/src/js/views/basic/basic_model.js +++ b/addons/web/static/src/js/views/basic/basic_model.js @@ -451,39 +451,92 @@ var BasicModel = AbstractModel.extend({ */ makeRecord: function (model, fields, fieldInfo) { var self = this; + var defs = []; var record_fields = {}; _.each(fields, function (field) { record_fields[field.name] = _.pick(field, 'type', 'relation', 'domain'); }); - var viewInfo = {}; - viewInfo.default = fieldInfo || _.mapObject(fields, function () { - return {}; + fieldInfo = fieldInfo || {}; + var fieldsInfo = {}; + fieldsInfo.default = {}; + _.each(fields, function (field) { + fieldsInfo.default[field.name] = fieldInfo[field.name] || {}; }); var record = this._makeDataPoint({ modelName: model, fields: record_fields, - fieldsInfo: viewInfo, + fieldsInfo: fieldsInfo, viewType: 'default', }); _.each(fields, function (field) { - if ('value' in field) { - if (field.type === 'many2one') { - var id = field.value[0]; - var display_name = field.value[1]; - var relatedRecord = self._makeDataPoint({ + var dataPoint; + if (field.type === 'many2one') { + if (field.value) { + var id = _.isArray(field.value) ? field.value[0] : field.value; + var display_name = _.isArray(field.value) ? field.value[1] : undefined; + dataPoint = self._makeDataPoint({ modelName: field.relation, data: { id: id, display_name: display_name, - } + }, }); - record.data[field.name] = relatedRecord.id; + record.data[field.name] = dataPoint.id; + if (display_name === undefined) { + defs.push(self._fetchNameGet(dataPoint)); + } + } + } else if (field.type === 'one2many' || field.type === 'many2many') { + var relatedFieldsInfo = {}; + relatedFieldsInfo.default = {}; + _.each(field.fields, function (field) { + relatedFieldsInfo.default[field.name] = {}; + }); + var dpParams = { + fieldsInfo: relatedFieldsInfo, + modelName: field.relation, + static: true, + type: 'list', + viewType: 'default', + }; + var needLoad = false; + // As value, you could either pass: + // - a list of ids related to the record + // - a list of object + // We only need to load the datapoint in the first case. + if (field.value && field.value.length) { + if (_.isObject(field.value[0])) { + dpParams.res_ids = _.pluck(field.value, 'id'); + dataPoint = self._makeDataPoint(dpParams); + _.each(field.value, function (data) { + var recordDP = self._makeDataPoint({ + data: data, + modelName: field.relation, + type: 'record', + }); + dataPoint.data.push(recordDP.id); + }); + } else { + dpParams.res_ids = field.value; + dataPoint = self._makeDataPoint(dpParams); + needLoad = true; + } } else { - record.data[field.name] = field.value; + dpParams.res_ids = []; + dataPoint = self._makeDataPoint(dpParams); } + + if (needLoad) { + defs.push(self._load(dataPoint)); + } + record.data[field.name] = dataPoint.id; + } else if (field.value) { + record.data[field.name] = field.value; } }); - return record.id; + return $.when.apply($, defs).then(function () { + return record.id; + }); }, /** * This is an extremely important method. All changes in any field go @@ -832,6 +885,7 @@ var BasicModel = AbstractModel.extend({ var def; if (rel_data.display_name === undefined) { var field = record.fields[fieldName]; + // TODO: refactor this to use _fetchNameGet def = this._rpc({ model: field.relation, method: 'name_get', @@ -1154,6 +1208,22 @@ var BasicModel = AbstractModel.extend({ }); }); }, + /** + * Fetch name_get for a record datapoint. + * + * @param {Object} dataPoint + * @returns {Deferred} + */ + _fetchNameGet: function (dataPoint) { + return this._rpc({ + model: dataPoint.model, + method: 'name_get', + args: [dataPoint.res_id], + context: dataPoint.getContext(), + }).then(function (result) { + dataPoint.data.display_name = result[0][1]; + }); + }, _fetchNameGets: function (list, fieldName) { var self = this; var model; diff --git a/addons/web/static/src/js/views/calendar/calendar_renderer.js b/addons/web/static/src/js/views/calendar/calendar_renderer.js index 84f11b687be..fc76d42544f 100644 --- a/addons/web/static/src/js/views/calendar/calendar_renderer.js +++ b/addons/web/static/src/js/views/calendar/calendar_renderer.js @@ -48,6 +48,7 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { FieldManagerMixin.init.call(this); this.title = options.title; + this.fields = options.fields; this.fieldName = options.fieldName; this.write_model = options.write_model; this.write_field = options.write_field; @@ -56,20 +57,29 @@ var SidebarFilter = Widget.extend(FieldManagerMixin, { this.filters = options.filters; this.label = options.label; this.getColor = options.getColor; + }, + willStart: function () { + var self = this; + var defs = [this._super.apply(this, arguments)]; if (this.write_model || this.write_field) { - this.many2one = new SidebarFilterM2O(this, - this.write_field, - this.model.get(this.model.makeRecord(this.write_model, [{ - name: this.write_field, - relation: options.fields[this.fieldName].relation, - type: 'many2one', - }])), - { - mode: 'edit', - can_create: false, - }); + var def = this.model.makeRecord(this.write_model, [{ + name: this.write_field, + relation: this.fields[this.fieldName].relation, + type: 'many2one', + }]).then(function (recordID) { + self.many2one = new SidebarFilterM2O(self, + self.write_field, + self.model.get(recordID), + { + mode: 'edit', + can_create: false, + }); + }); + defs.push(def); } + return $.when.apply($, defs); + }, start: function () { this._super(); diff --git a/addons/web/static/test/views/basic_model_tests.js b/addons/web/static/test/views/basic_model_tests.js index b15998c36dd..15947103e6e 100644 --- a/addons/web/static/test/views/basic_model_tests.js +++ b/addons/web/static/test/views/basic_model_tests.js @@ -747,19 +747,18 @@ QUnit.module('Views', { var record = model.get(resultID, {raw: true}); assert.deepEqual(record.data.product_ids, [37], "should have correct initial value"); - var relatedRecordID = model.makeRecord('product', [{ + model.makeRecord('product', [{ name: 'name', string: "Product Name", type: "char", value: "xpod" } - ]); - - model.notifyChanges(record.id, { - product_ids: {operation: "ADD", id: relatedRecordID} + ]).then(function (relatedRecordID) { + model.notifyChanges(record.id, { + product_ids: {operation: "ADD", id: relatedRecordID} + }); + model.save(record.id); }); - - model.save(record.id); }); model.destroy(); }); @@ -794,22 +793,23 @@ QUnit.module('Views', { assert.strictEqual(record.data.product_ids.data.length, 0, "one2many should start with a list of length 0"); - var relatedRecordID = model.makeRecord('product', [{ + model.makeRecord('product', [{ name: 'name', string: "Product Name", type: "char", value: "xpod" } - ]); - model.notifyChanges(resultID, { - product_ids: {operation: "ADD", id: relatedRecordID} + ]).then(function (relatedRecordID) { + model.notifyChanges(resultID, { + product_ids: {operation: "ADD", id: relatedRecordID} + }); + + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.data.length, 1, + "one2many should be a list of length 1"); + assert.strictEqual(record.data.product_ids.data[0].data.name, "xpod", + "one2many should have correct data"); }); - - record = model.get(resultID); - assert.strictEqual(record.data.product_ids.data.length, 1, - "one2many should be a list of length 1"); - assert.strictEqual(record.data.product_ids.data[0].data.name, "xpod", - "one2many should have correct data"); }); model.destroy(); }); @@ -1096,4 +1096,125 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('call makeRecord with a pre-fetched many2one field', function (assert) { + assert.expect(3); + var rpcCount = 0; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); + + model.makeRecord('coucou', [{ + name: 'partner_id', + relation: 'partner', + type: 'many2one', + value: [1, 'first partner'], + }], { + partner_id: { + options: { + no_open: true, + }, + }, + }).then(function (recordID) { + var record = model.get(recordID); + assert.deepEqual(record.fieldsInfo.default.partner_id, {options: {no_open: true}}, + "makeRecord should have generated the fieldsInfo"); + assert.deepEqual(record.data.partner_id.data, {id: 1, display_name: 'first partner'}, + "many2one should contain the partner with id 1"); + assert.strictEqual(rpcCount, 0, "makeRecord should not have done any rpc"); + }); + model.destroy(); + }); + + QUnit.test('call makeRecord with a many2many field', function (assert) { + assert.expect(5); + var rpcCount = 0; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); + + model.makeRecord('coucou', [{ + name: 'partner_ids', + fields: [{ + name: 'id', + type: 'integer', + }, { + name: 'display_name', + type: 'char', + }], + relation: 'partner', + type: 'many2many', + value: [1, 2], + }]).then(function (recordID) { + var record = model.get(recordID); + assert.deepEqual(record.fieldsInfo.default.partner_ids, {}, + "makeRecord should have generated the fieldsInfo"); + assert.strictEqual(record.data.partner_ids.count, 2, + "there should be 2 elements in the many2many"); + assert.strictEqual(record.data.partner_ids.data.length, 2, + "many2many should be a list of length 2"); + assert.deepEqual(record.data.partner_ids.data[0].data, {id: 1, display_name: 'first partner'}, + "many2many should contain the partner with id 1"); + assert.strictEqual(rpcCount, 1, "makeRecord should have done 1 rpc"); + }); + model.destroy(); + }); + + QUnit.test('call makeRecord with a pre-fetched many2many field', function (assert) { + assert.expect(5); + var rpcCount = 0; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + rpcCount++; + return this._super(route, args); + }, + }); + + model.makeRecord('coucou', [{ + name: 'partner_ids', + fields: [{ + name: 'id', + type: 'integer', + }, { + name: 'display_name', + type: 'char', + }], + relation: 'partner', + type: 'many2many', + value: [{ + id: 1, + display_name: "first partner", + }, { + id: 2, + display_name: "second partner", + }], + }]).then(function (recordID) { + var record = model.get(recordID); + assert.deepEqual(record.fieldsInfo.default.partner_ids, {}, + "makeRecord should have generated the fieldsInfo"); + assert.strictEqual(record.data.partner_ids.count, 2, + "there should be 2 elements in the many2many"); + assert.strictEqual(record.data.partner_ids.data.length, 2, + "many2many should be a list of length 2"); + assert.deepEqual(record.data.partner_ids.data[0].data, {id: 1, display_name: 'first partner'}, + "many2many should contain the partner with id 1"); + assert.strictEqual(rpcCount, 0, "makeRecord should not have done any rpc"); + }); + model.destroy(); + }); + });}); |
