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/tests/views/basic_model_tests.js | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) (limited to 'addons/web/static/tests/views/basic_model_tests.js') diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 9fbc2acdd88..4684b50a871 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -302,6 +302,89 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('notifyChange on a one2many', function (assert) { + assert.expect(9); + + this.data.partner.records[1].product_ids = [37]; + this.params.fieldNames = ['product_ids']; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + if (args.method === 'name_get') { + assert.strictEqual(args.model, 'product'); + } + return this._super(route, args); + }, + }); + + var o2mParams = { + modelName: 'product', + fields: this.data.product.fields, + fieldNames: ['display_name'], + }; + model.load(this.params).then(function (resultID) { + model.load(o2mParams).then(function (newRecordID) { + var record = model.get(resultID); + var x2mListID = record.data.product_ids.id; + + assert.strictEqual(record.data.product_ids.count, 1, + "there should be one record in the relation"); + + // trigger a 'ADD' command + model.notifyChanges(resultID, {product_ids: {operation: 'ADD', id: newRecordID}}); + + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'ADD', id: newRecordID, + }], "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 2, + "there should be two records in the relation"); + + // trigger a 'UPDATE' command + model.notifyChanges(resultID, {product_ids: {operation: 'UPDATE', id: newRecordID}}); + + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'ADD', id: newRecordID, + }, { + operation: 'UPDATE', id: newRecordID, + }], "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 2, + "there should be two records in the relation"); + + // trigger a 'REMOVE' command on the existing record + var existingRecordID = record.data.product_ids.data[0].id; + model.notifyChanges(resultID, {product_ids: {operation: 'REMOVE', ids: [existingRecordID]}}); + + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'ADD', id: newRecordID, + }, { + operation: 'UPDATE', id: newRecordID, + }, { + operation: 'REMOVE', id: existingRecordID, + }], + "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 1, + "there should be one record in the relation"); + + // trigger a 'REMOVE' command on the new record + model.notifyChanges(resultID, {product_ids: {operation: 'REMOVE', ids: [newRecordID]}}); + + assert.deepEqual(model.localData[x2mListID]._changes, [{ + operation: 'REMOVE', id: existingRecordID, + }], "_changes should be correct"); + record = model.get(resultID); + assert.strictEqual(record.data.product_ids.count, 0, + "there should be no record in the relation"); + }); + + }); + model.destroy(); + }); + QUnit.test('notifyChange on a many2one, without display_name', function (assert) { assert.expect(3); -- cgit v1.2.3 From 4881f942cb7cad51b8472425830c8444cfe09d1a Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Fri, 23 Jun 2017 14:06:03 +0200 Subject: [FIX] web: BasicModel: correctly eval x2manys in domains When x2many fields have to be evaluated in domains (e.g. ['id', 'in', some_x2many_field]), they must be evaluated as the list of ids in the relation (whereas in contexts, they are evaluated as a list of commands). Before this rev., they were always evaluated as a list of commands. Note that this didn't work neither before the new views. --- addons/web/static/tests/views/basic_model_tests.js | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'addons/web/static/tests/views/basic_model_tests.js') diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 4684b50a871..97047365436 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -1783,4 +1783,41 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('x2manys in contexts and domains are correctly evaluated', function (assert) { + assert.expect(4); + + this.data.partner.records[0].product_ids = [37, 41]; + this.params.fieldNames = Object.keys(this.data.partner.fields); + this.params.res_id = 1; + this.params.context = "{'category': category, 'product_ids': product_ids}"; + this.params.domain = "[['id', 'in', category], ['id', 'in', product_ids]]"; + + var model = createModel({ + Model: BasicModel, + data: this.data, + }); + + model.load(this.params).then(function (resultID) { + var record = model.get(resultID); + var context = record.getContext(); + var domain = record.getDomain(); + + assert.deepEqual(context, { + category: [12], + product_ids: [37, 41], + }, "x2many values in context manipulated client-side should be lists of ids"); + assert.strictEqual(JSON.stringify(context), + "{\"category\":[[6,false,[12]]],\"product_ids\":[[4,37,false],[4,41,false]]}", + "x2many values in context sent to the server should be commands"); + assert.deepEqual(domain, [ + ['id', 'in', [12]], + ['id', 'in', [37, 41]], + ], "x2many values in domains should be lists of ids"); + assert.strictEqual(JSON.stringify(domain), + "[[\"id\",\"in\",[12]],[\"id\",\"in\",[37,41]]]", + "x2many values in domains should be lists of ids"); + }); + model.destroy(); + }); + });}); -- 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). --- addons/web/static/tests/views/basic_model_tests.js | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) (limited to 'addons/web/static/tests/views/basic_model_tests.js') diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 97047365436..715f6099e0a 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -20,6 +20,7 @@ QUnit.module('Views', { product_ids: {string: "Favorite products", type: 'one2many', relation: 'product'}, category: {string: "Category M2M", type: 'many2many', relation: 'partner_type'}, date: {string: "Date Field", type: 'date'}, + reference: {string: "Reference Field", type: 'reference', selection: [["product", "Product"], ["partner_type", "Partner Type"], ["partner", "Partner"]]}, }, records: [ {id: 1, foo: 'blip', bar: 1, product_id: 37, category: [12], display_name: "first partner", date: "2017-01-25"}, @@ -1777,6 +1778,7 @@ QUnit.module('Views', { product_id: 37, product_ids: [], qux: false, + reference: false, total: 0 }, "should use the proper eval context"); }); @@ -1820,4 +1822,39 @@ QUnit.module('Views', { model.destroy(); }); + QUnit.test('fetch references in list, with not too many rpcs', function (assert) { + assert.expect(5); + + this.data.partner.records[0].reference = 'product,37'; + this.data.partner.records[1].reference = 'product,41'; + + this.params.fieldNames = ['reference']; + this.params.domain = []; + this.params.groupedBy = []; + this.params.res_id = undefined; + + var model = createModel({ + Model: BasicModel, + data: this.data, + mockRPC: function (route, args) { + assert.step(route); + if (route === "/web/dataset/call_kw/product/name_get") { + assert.deepEqual(args.args, [[37, 41]], + "the name_get should contain the product ids"); + } + return this._super(route, args); + }, + }); + + model.load(this.params).then(function (resultID) { + var record = model.get(resultID); + + assert.strictEqual(record.data[0].data.reference.data.display_name, "xphone", + "name_get should have been correctly fetched"); + assert.verifySteps(["/web/dataset/search_read", "/web/dataset/call_kw/product/name_get"], + "should have done 2 rpc (searchread and name_get for product)"); + }); + model.destroy(); + }); + });}); -- cgit v1.2.3 From b954cd64835eb2c251ba0890293079b7bcebd460 Mon Sep 17 00:00:00 2001 From: Aaron Bohy Date: Wed, 28 Jun 2017 13:20:10 +0200 Subject: [FIX] web: BasicModel: send correct context ... when reading and writing on a record (especially inside lists). Before this rev., the context sent when reading a record, and when writing records inside lists (editable list views) wasn't correct. This caused an issue in Sales > Products (variant activated) > open a product with variants > click on variant prices > edit the prices of a variant: the value wasn't saved correctly (because the inverse function requires 'active_id' to be in the context and it wasn't the case). --- addons/web/static/tests/views/basic_model_tests.js | 32 ++++++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'addons/web/static/tests/views/basic_model_tests.js') diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index 715f6099e0a..eb068f4214c 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -72,9 +72,15 @@ QUnit.module('Views', { Model: BasicModel, data: this.data, mockRPC: function (route, args) { - assert.strictEqual(args.kwargs.context.active_field, 2, - "should have sent the correct context"); + assert.deepEqual(args.kwargs.context, { + active_field: 2, + bin_size: true, + someKey: 'some value', + }, "should have sent the correct context"); return this._super.apply(this, arguments); + }, + session: { + user_context: {someKey: 'some value'}, } }); @@ -1790,9 +1796,23 @@ QUnit.module('Views', { this.data.partner.records[0].product_ids = [37, 41]; this.params.fieldNames = Object.keys(this.data.partner.fields); + this.params.fieldsInfo = { + form: { + qux: { + context: "{'category': category, 'product_ids': product_ids}", + domain: "[['id', 'in', category], ['id', 'in', product_ids]]", + relatedFields: this.data.partner.fields, + }, + category: { + relatedFields: this.data.partner_type.fields, + }, + product_ids: { + relatedFields: this.data.product.fields, + }, + }, + }; + this.params.viewType = 'form'; this.params.res_id = 1; - this.params.context = "{'category': category, 'product_ids': product_ids}"; - this.params.domain = "[['id', 'in', category], ['id', 'in', product_ids]]"; var model = createModel({ Model: BasicModel, @@ -1801,8 +1821,8 @@ QUnit.module('Views', { model.load(this.params).then(function (resultID) { var record = model.get(resultID); - var context = record.getContext(); - var domain = record.getDomain(); + var context = record.getContext({fieldName: 'qux'}); + var domain = record.getDomain({fieldName: 'qux'}); assert.deepEqual(context, { category: [12], -- cgit v1.2.3 From a43b0464b28ce53074b027469747e379bbb389c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 5 Jul 2017 13:41:24 +0200 Subject: [FIX] web: better handling for readonly modifiers Before this commit, the web client read the readonly in the modifiers, and if no readonly was in the modifiers, it fell back to the field property. But the field property was already taken into account in the modifiers, so this was useless. And worse, in some case, it could cause an issue. For example, a field with readonly=true, and readonly="0" in the arch was considered readonly. --- addons/web/static/tests/views/basic_model_tests.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'addons/web/static/tests/views/basic_model_tests.js') diff --git a/addons/web/static/tests/views/basic_model_tests.js b/addons/web/static/tests/views/basic_model_tests.js index eb068f4214c..7f7547262da 100644 --- a/addons/web/static/tests/views/basic_model_tests.js +++ b/addons/web/static/tests/views/basic_model_tests.js @@ -1418,7 +1418,14 @@ QUnit.module('Views', { this.data.partner.onchanges.foo = function (obj) { obj.bar = obj.foo.length; }; - this.data.partner.fields.bar.readonly = true; + this.params.fieldsInfo = { + default: { + foo: {}, + bar: { + modifiers:"{\"readonly\": true}", + }, + } + }; var model = createModel({ Model: BasicModel, -- cgit v1.2.3