From 17e198153ecfd9c5c32b3b22f43e2f1b8100a1c3 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 16 Jun 2017 15:44:43 +0200 Subject: [FIX] web: BasicModel: store changes in x2many as operations Datapoints of the BasicModel store the data fetched from the server and changes done (not saved yet) by the user. For x2many fields, datapoints are of type 'list' and have a 'res_ids' key containing the ids of all records in the relation. They also have a 'data' key containing datapoints of type 'record', representing the records in the relation that have been fetched (with a limit set to 40 or 80 depending on the subview used). So basically, the length of res_ids may be different than the length of data. Before this rev., the changes in x2manys were saved under the key '_changes' as a duplication of the content of 'data', i.e. same list of datapoints of type 'record', modulo the changes done (it contained the new records added and didn't contained the removed records, whereas the 'data' remained unchanged). This didn't worked at all when there were more records than the limit in the relation. Indeed, when this happened, the commands to send to the server couldn't be generated correctly. This rev. modifies the way changes are stored in those datapoints. Now, each change is stored as an operation. An operation could be of type 'ADD', 'REMOVE', 'REMOVE_ALL' or 'UPDATE'. As before, 'data' remains unchanged and always keeps the datapoints of records that have been fetched (the ones that are currently displayed in the list or kanban subview). This way, commands can now be properly computed. This had a nice side-effect of fixing two other bugs linked to x2manys. First, when used in a context (e.g. {some_key: some_x2many_field}), the value of some_x2many_field (i.e. a list of commands) was always [] if the field was invisible="1" in the view, because it's subrecords weren't fetched, and the generated commands were based on them. Now, they are generated from the list of res_ids in the relation, so it works fine. Second, the x2many records are supposed to be sorted client-side, after being read, and after each edition. This was working in the first case, but not in the second. Now it's working even after a record edition. This rev. also correctly sets the 'parentID' and 'static' keys in datapoints. Finally, we also fixed the pager in x2manys as it was badly displayed (small less tweaks). --- addons/web/static/src/js/fields/relational_fields.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'addons/web/static/src/js/fields/relational_fields.js') diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 419d1f5f646..5b9114b6c82 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -657,7 +657,14 @@ var FieldX2Many = AbstractField.extend({ reset: function (record, ev) { if (ev && ev.target === this && ev.data.changes && this.view.arch.tag === 'tree') { var command = ev.data.changes[this.name]; - if (command.operation === 'UPDATE') { + // Here, we only consider 'UPDATE' commands with data, which occur + // with editable list view. In order to keep the current line in + // edition, we call confirmChange which will reset the widgets + // instead of re-rendering the list. 'UPDATE' commands with no data + // can be ignored: they occur in one2manys when the record is + // updated from a dialog and in this case, we can re-render the + // whole subview. + if (command.operation === 'UPDATE' && command.data) { var state = record.data[this.name]; var fieldNames = state.getFieldNames(); this.renderer.confirmChange(state, command.id, fieldNames, ev.initialEvent); @@ -1068,9 +1075,15 @@ var FieldOne2Many = FieldX2Many.extend({ // we don't want interference with the components upstream. ev.stopPropagation(); + var id = ev.data.id; + // trigger an empty 'UPDATE' operation when the user clicks on 'Save' in + // the dialog, to notify the main record that a subrecord of this + // relational field has changed (those changes will be already stored on + // that subrecord, thanks to the 'Save'). + var onSaved = this._setValue.bind(this, { operation: 'UPDATE', id: id }, {}); this._openFormDialog({ - id: ev.data.id, - on_saved: this._setValue.bind(this, { operation: 'NOOP' }, {}), + id: id, + on_saved: onSaved, readonly: this.mode === 'readonly', }); }, -- cgit v1.2.3 From ec3bc2eeaf88c957340615ed0da71ac7b91a596b Mon Sep 17 00:00:00 2001 From: Martin Geubelle Date: Mon, 26 Jun 2017 18:11:56 +0200 Subject: [FIX] web: deal with reference fields The reference fields haven't been correctly implemented with the new views. This commit restores their behaviour. Note that an small improvement has been introduced in this commit. Before saas-16, a `name_get` was done for each record (so 80 if the field was displayed in a full list view), which is not optimal. The calls are now batched by model (so one `name_get` by model appearing in the field values). --- .../web/static/src/js/fields/relational_fields.js | 102 +++++++++++++++++++++ 1 file changed, 102 insertions(+) (limited to 'addons/web/static/src/js/fields/relational_fields.js') diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 5b9114b6c82..44f884f130c 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1943,6 +1943,106 @@ var FieldRadio = FieldSelection.extend({ }, }); +/** + * The FieldReference is a combination of a select (for the model) and + * a FieldMany2one for its value. + * Its intern representation is similar to the many2one (a datapoint with a + * `name_get` as data). + */ +var FieldReference = FieldMany2One.extend({ + supportedFieldTypes: ['reference'], + template: 'FieldReference', + events: _.extend({}, FieldMany2One.prototype.events, { + 'change select': '_onSelectionChange', + }), + /** + * @override + */ + init: function () { + this._super.apply(this, arguments); + + // needs to be copied as it is an unmutable object + this.field = _.extend({}, this.field); + if (this.value) { + this._setRelation(this.value.model); + } + }, + /** + * @override + */ + start: function () { + this.$('select').val(this.field.relation); + return this._super.apply(this, arguments); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * Add a select in edit mode (for the model). + * + * @override + */ + _renderEdit: function () { + this._super.apply(this, arguments); + + if (this.$('select').val()) { + this.$('.o_input_dropdown').show(); + this.$el.addClass('o_row'); // this class is used to display the two + // components (select & input) on the same line + } else { + // hide the many2one if the selection is empty + this.$('.o_input_dropdown').hide(); + } + + }, + /** + * @override + * @private + */ + _reset: function () { + this._super.apply(this, arguments); + this._setRelation(this.value && this.value.model); + }, + /** + * Set `relation` key in field properties. + * + * @private + * @param {string} model + */ + _setRelation: function (model) { + // used to generate the search in many2one + this.field.relation = model; + }, + /** + * @override + * @private + */ + _setValue: function (value, options) { + value = value || {}; + // we need to specify the model for the change in basic_model + // the value is then now a dict with id, display_name and model + value.model = this.$('select').val(); + return this._super(value, options); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * When the selection (model) changes, the many2one is reset. + * + * @private + */ + _onSelectionChange: function () { + var value = this.$('select').val(); + this.reinitialize(false); + this._setRelation(value); + }, +}); + return { FieldMany2One: FieldMany2One, KanbanFieldMany2One: KanbanFieldMany2One, @@ -1960,6 +2060,8 @@ return { FieldRadio: FieldRadio, FieldSelection: FieldSelection, FieldStatus: FieldStatus, + + FieldReference: FieldReference, }; }); -- cgit v1.2.3 From 8473bde80e6609d552af8b00396c72a20bd3c47f Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Thu, 29 Jun 2017 11:46:46 +0200 Subject: [FIX] web: handle 'domain' key returned by onchanges Onchange RPCs return an object that may contain a 'domain' key. When it does, its value is an object whose keys are field names and values are the new domain for the corresponding field. Before this rev., the 'domain' key was totally ignored by the BasicModel. This feature is used for example in the account.payment form view (go to an open invoice, click on 'Register payment'): the domain of field payment_method_id is updated by the onchanges (e.g. when changing the journal_id). As the domain was ignored, it always displayed all possible payment methods. --- .../web/static/src/js/fields/relational_fields.js | 104 +++++++++++++++------ 1 file changed, 74 insertions(+), 30 deletions(-) (limited to 'addons/web/static/src/js/fields/relational_fields.js') diff --git a/addons/web/static/src/js/fields/relational_fields.js b/addons/web/static/src/js/fields/relational_fields.js index 44f884f130c..df7f018369d 100644 --- a/addons/web/static/src/js/fields/relational_fields.js +++ b/addons/web/static/src/js/fields/relational_fields.js @@ -1785,15 +1785,7 @@ var FieldSelection = AbstractField.extend({ */ init: function () { this._super.apply(this, arguments); - if (this.field.type === 'many2one') { - this.values = this.record.specialData[this.name]; - this.formatType = 'many2one'; - } else { - this.values = _.reject(this.field.selection, function (v) { - return v[0] === false && v[1] === ''; - }); - } - this.values = [[false, this.attrs.placeholder || '']].concat(this.values); + this._setValues(); }, //-------------------------------------------------------------------------- @@ -1823,13 +1815,12 @@ var FieldSelection = AbstractField.extend({ * @private */ _renderEdit: function () { - if (!this.$el.children().length) { - for (var i = 0 ; i < this.values.length ; i++) { - this.$el.append($('