diff --git a/addons/account/static/src/components/bills_upload/bills_upload.js b/addons/account/static/src/components/bills_upload/bills_upload.js index 39963c445f4..f5b9dafd86a 100644 --- a/addons/account/static/src/components/bills_upload/bills_upload.js +++ b/addons/account/static/src/components/bills_upload/bills_upload.js @@ -35,7 +35,7 @@ export class AccountFileUploader extends Component { mimetype: file.type, datas: file.data, }; - const att_id = await this.orm.create("ir.attachment", [att_data], { + const [att_id] = await this.orm.create("ir.attachment", [att_data], { context: { ...this.extraContext, ...this.env.searchModel.context }, }); this.attachmentIdsToProcess.push(att_id); diff --git a/addons/account/static/tests/bills_upload.js b/addons/account/static/tests/bills_upload.js index cab934497ab..667c1c3dd51 100644 --- a/addons/account/static/tests/bills_upload.js +++ b/addons/account/static/tests/bills_upload.js @@ -54,7 +54,7 @@ QUnit.module("Widgets", (hooks) => { mockRPC(route, args) { if (args.method === "create") { assert.deepEqual(args.model, "ir.attachment", "create ir.attachment") - return 99; + return [99]; } if (args.method === "create_document_from_attachment" && args.model === "account.journal") { assert.equal(args.kwargs.context.default_journal_id, 7, "create documents in correct journal"); diff --git a/addons/mail/static/tests/qunit_suite_tests/models/messaging_tests.js b/addons/mail/static/tests/qunit_suite_tests/models/messaging_tests.js index bf5f5759fbd..14567fd7674 100644 --- a/addons/mail/static/tests/qunit_suite_tests/models/messaging_tests.js +++ b/addons/mail/static/tests/qunit_suite_tests/models/messaging_tests.js @@ -130,7 +130,7 @@ QUnit.module("mail", {}, function () { assert.expect(1); const { messaging, pyEnv } = await start(); - const partnerId = await messaging.rpc({ + const [partnerId] = await messaging.rpc({ method: "create", model: "res.partner", args: [[{ name: "foo" }]], @@ -143,9 +143,9 @@ QUnit.module("mail", {}, function () { assert.expect(1); const { messaging, pyEnv } = await start(); - const partnerId = await messaging.rpc({ - method: "create", + const [partnerId] = await messaging.rpc({ model: "res.partner", + method: "create", kwargs: { vals_list: [{ name: "foo" }], }, diff --git a/addons/mass_mailing/static/src/js/mailing_m2o_filter.js b/addons/mass_mailing/static/src/js/mailing_m2o_filter.js index 9012d3888dc..f4d442b8cbe 100644 --- a/addons/mass_mailing/static/src/js/mailing_m2o_filter.js +++ b/addons/mass_mailing/static/src/js/mailing_m2o_filter.js @@ -125,7 +125,7 @@ export class FieldMany2OneMailingFilter extends Many2OneField { ev.stopPropagation(); filterInput.focus(); } else { - const newFilterId = await this.env.model.orm.create("mailing.filter", [{ + const [newFilterId] = await this.env.model.orm.create("mailing.filter", [{ name: filterName, mailing_domain: this.props.record.data[this.props.domain_field], mailing_model_id: this.props.record.data[this.props.model_field][0], diff --git a/addons/mass_mailing/static/tests/mass_mailing_favourite_filter_tests.js b/addons/mass_mailing/static/tests/mass_mailing_favourite_filter_tests.js index 2be1d469f40..b2ffa268db4 100644 --- a/addons/mass_mailing/static/tests/mass_mailing_favourite_filter_tests.js +++ b/addons/mass_mailing/static/tests/mass_mailing_favourite_filter_tests.js @@ -129,9 +129,9 @@ QUnit.module('favorite filter widget', (hooks) => { widget="mailing_filter" options="{'no_create': '1', 'no_open': '1', 'domain_field': 'mailing_domain', 'model': 'mailing_model_id'}"/> `, - mockRPC: function (route, args) { - if (args.method === 'create' && args.model === 'mailing.filter') { - assert.deepEqual(args.args, + mockRPC: function (_, { args, model, method }) { + if (method === 'create' && model === 'mailing.filter') { + assert.deepEqual(args[0], [{mailing_domain: '[["new_user","=",True]]', mailing_model_id: 1, name: 'event promo - new users'}], "should pass correct data in create"); } diff --git a/addons/stock/static/tests/inventory_report_list_tests.js b/addons/stock/static/tests/inventory_report_list_tests.js index 138b7b59570..3b34c8ae4ca 100644 --- a/addons/stock/static/tests/inventory_report_list_tests.js +++ b/addons/stock/static/tests/inventory_report_list_tests.js @@ -18,20 +18,21 @@ const arch = const setup_date = DateTime.fromISO('2022-01-03T08:03:44+00:00').toSQL(); -function mockRPC(route, args) { +function mockRPC(route, { args }) { if (route === '/web/dataset/call_kw/person/create') { // simulate 'stock.quant' create function which can return existing record - args.args[0].create_date = DateTime.now().toSQL(); - args.args[0].write_date = args.args[0].create_date; - var name = args.args[0].name; - var age = args.args[0].age; - var job = args.args[0].job; + const [values] = args[0]; + values.create_date = DateTime.now().toSQL(); + values.write_date = values.create_date; + var name = values.name; + var age = values.age; + var job = values.job; for (var d of serverData.models.person.records) { if (d.name === name) { d.age = age; d.job = job; - d.write_date = args.args[0].write_date; - return Promise.resolve(d.id); + d.write_date = values.write_date; + return Promise.resolve([d.id]); } } } diff --git a/addons/web/static/src/core/orm_service.js b/addons/web/static/src/core/orm_service.js index 3c19754d6eb..3fbcbec6ac4 100644 --- a/addons/web/static/src/core/orm_service.js +++ b/addons/web/static/src/core/orm_service.js @@ -131,7 +131,7 @@ export class ORM { for (const record of records) { validateObject("record", record); } - return this.call(model, "create", records, kwargs); + return this.call(model, "create", [records], kwargs); } /** diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index a830beaeccd..3b594e4a611 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -1348,7 +1348,8 @@ export class Record extends DataPoint { ); this.resId = resId; } else { - this.resId = await this.model.orm.create(this.resModel, [changes], { context }); + const [resId] = await this.model.orm.create(this.resModel, [changes], { context }); + this.resId = resId; } delete this.virtualId; this.data.id = this.resId; diff --git a/addons/web/static/src/views/view_dialogs/export_data_dialog.js b/addons/web/static/src/views/view_dialogs/export_data_dialog.js index 057c9aa131a..ac7047a0eda 100644 --- a/addons/web/static/src/views/view_dialogs/export_data_dialog.js +++ b/addons/web/static/src/views/view_dialogs/export_data_dialog.js @@ -263,7 +263,7 @@ export class ExportDataDialog extends Component { type: "danger", }); } - const id = await this.orm.create( + const [id] = await this.orm.create( "ir.exports", [ { diff --git a/addons/web/static/tests/core/orm_service_tests.js b/addons/web/static/tests/core/orm_service_tests.js index 5609509fe89..fbd481c76c0 100644 --- a/addons/web/static/tests/core/orm_service_tests.js +++ b/addons/web/static/tests/core/orm_service_tests.js @@ -91,7 +91,7 @@ QUnit.test("basic method call of model", async (assert) => { }); }); -QUnit.test("create method", async (assert) => { +QUnit.test("create method: one record", async (assert) => { const [query, rpc] = makeFakeRPC(); serviceRegistry.add("rpc", rpc); const env = await makeTestEnv(); @@ -99,9 +99,40 @@ QUnit.test("create method", async (assert) => { assert.strictEqual(query.route, "/web/dataset/call_kw/partner/create"); assert.deepEqual(query.params, { args: [ - { - color: "red", + [ + { + color: "red", + }, + ], + ], + kwargs: { + context: { + lang: "en", + tz: "taht", + uid: 7, }, + }, + method: "create", + model: "partner", + }); +}); + +QUnit.test("create method: several records", async (assert) => { + const [query, rpc] = makeFakeRPC(); + serviceRegistry.add("rpc", rpc); + const env = await makeTestEnv(); + await env.services.orm.create("partner", [{ color: "red" }, { color: "green" }]); + assert.strictEqual(query.route, "/web/dataset/call_kw/partner/create"); + assert.deepEqual(query.params, { + args: [ + [ + { + color: "red", + }, + { + color: "green", + }, + ], ], kwargs: { context: { diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js index 3ebacb7bde7..c9f4403c1a5 100644 --- a/addons/web/static/tests/helpers/mock_server.js +++ b/addons/web/static/tests/helpers/mock_server.js @@ -694,18 +694,27 @@ export class MockServer { return newID; } - mockCreate(modelName, values, kwargs = {}) { - if ("id" in values) { - throw new Error("Cannot create a record with a predefinite id"); + mockCreate(modelName, valsList, kwargs = {}) { + let returnArrayOfIds = true; + if (!Array.isArray(valsList)) { + valsList = [valsList]; + returnArrayOfIds = false; } const model = this.models[modelName]; - const id = this.getUnusedID(modelName); - const record = { id }; - model.records.push(record); - this.applyDefaults(model, values, kwargs.context); - this.writeRecord(modelName, values, id); - this.updateComodelRelationalFields(modelName, record); - return id; + const ids = []; + for (const values of valsList) { + if ("id" in values) { + throw new Error("Cannot create a record with a predefinite id"); + } + const id = this.getUnusedID(modelName); + ids.push(id); + const record = { id }; + model.records.push(record); + this.applyDefaults(model, values, kwargs.context); + this.writeRecord(modelName, values, id); + this.updateComodelRelationalFields(modelName, record); + } + return returnArrayOfIds ? ids : ids[0]; } /** @@ -818,7 +827,7 @@ export class MockServer { name: name, display_name: name, }; - const id = this.mockCreate(modelName, values, kwargs); + const [id] = this.mockCreate(modelName, [values], kwargs); return [id, name]; } @@ -2306,7 +2315,7 @@ export class MockServer { if (inverseFieldName) { inverseData[inverseFieldName] = id; } - const newId = this.mockCreate(field.relation, inverseData); + const [newId] = this.mockCreate(field.relation, [inverseData]); ids.push(newId); } else if (command[0] === 1) { // UPDATE diff --git a/addons/web/static/tests/mock_server_tests.js b/addons/web/static/tests/mock_server_tests.js index 899bc535eaa..7aa2015aa99 100644 --- a/addons/web/static/tests/mock_server_tests.js +++ b/addons/web/static/tests/mock_server_tests.js @@ -1467,4 +1467,38 @@ QUnit.module("MockServer", (hooks) => { const { views } = mockServer.mockGetViews("bar", { views: [[10001, "list"]], options: {} }); assert.deepEqual(views.list.arch, expectedList); }); + + QUnit.test("performRPC: create one record (old API)", async function (assert) { + const server = new MockServer(data, {}); + const result = await server.performRPC("", { + model: "bar", + method: "create", + args: [{ foo: "A" }], + }); + assert.strictEqual(result, 7); + assert.strictEqual(data.models.bar.records.find((r) => r.id === 7).foo, "A"); + }); + + QUnit.test("performRPC: create one record (new API)", async function (assert) { + const server = new MockServer(data, {}); + const result = await server.performRPC("", { + model: "bar", + method: "create", + args: [[{ foo: "A" }]], + }); + assert.deepEqual(result, [7]); + assert.strictEqual(data.models.bar.records.find((r) => r.id === 7).foo, "A"); + }); + + QUnit.test("performRPC: create several records (new API)", async function (assert) { + const server = new MockServer(data, {}); + const result = await server.performRPC("", { + model: "bar", + method: "create", + args: [[{ foo: "A" }, { foo: "B" }]], + }); + assert.deepEqual(result, [7, 8]); + assert.strictEqual(data.models.bar.records.find((r) => r.id === 7).foo, "A"); + assert.strictEqual(data.models.bar.records.find((r) => r.id === 8).foo, "B"); + }); }); diff --git a/addons/web/static/tests/views/calendar/calendar_view_tests.js b/addons/web/static/tests/views/calendar/calendar_view_tests.js index 243320a993c..66e1b3c117e 100644 --- a/addons/web/static/tests/views/calendar/calendar_view_tests.js +++ b/addons/web/static/tests/views/calendar/calendar_view_tests.js @@ -104,7 +104,7 @@ QUnit.module("Views", ({ beforeEach }) => { start: { string: "start datetime", type: "datetime" }, stop: { string: "stop datetime", type: "datetime" }, delay: { string: "delay", type: "float" }, - duration: { string: "Duration", type:"float", default: 1}, + duration: { string: "Duration", type: "float", default: 1 }, allday: { string: "allday", type: "boolean" }, partner_ids: { string: "attendees", @@ -309,28 +309,31 @@ QUnit.module("Views", ({ beforeEach }) => { QUnit.test(`simple calendar rendering`, async (assert) => { assert.expect(25); - serverData.models.event.records.push({ - id: 8, - user_id: uid, - partner_id: false, - name: "event 7", - start: "2016-12-18 09:00:00", - stop: "2016-12-18 10:00:00", - allday: false, - partner_ids: [2], - type: 1, - }, { - id: 9, - user_id: uid, - partner_id: false, - name: "event 8", - start: "2016-12-11 05:15:00", - stop: "2016-12-11 05:30:00", - allday: false, - partner_ids: [1, 2, 3], - duration: 0.25, - type: 1, - }); + serverData.models.event.records.push( + { + id: 8, + user_id: uid, + partner_id: false, + name: "event 7", + start: "2016-12-18 09:00:00", + stop: "2016-12-18 10:00:00", + allday: false, + partner_ids: [2], + type: 1, + }, + { + id: 9, + user_id: uid, + partner_id: false, + name: "event 8", + start: "2016-12-11 05:15:00", + stop: "2016-12-11 05:30:00", + allday: false, + partner_ids: [1, 2, 3], + duration: 0.25, + type: 1, + } + ); await makeView({ type: "calendar", @@ -1030,10 +1033,12 @@ QUnit.module("Views", ({ beforeEach }) => { if (method === "create") { assert.deepEqual( args[0], - { - x_name: "custom event in quick create", - x_start_date: "2016-12-13", - }, + [ + { + x_name: "custom event in quick create", + x_start_date: "2016-12-13", + }, + ], "the custom create_name_field should be used instead of `name`" ); } @@ -1161,7 +1166,7 @@ QUnit.module("Views", ({ beforeEach }) => { mockRPC(route, { method, args }) { if (method === "create") { assert.deepEqual( - args, + args[0], [ { allday: false, @@ -1879,12 +1884,14 @@ QUnit.module("Views", ({ beforeEach }) => { `, mockRPC(route, { args, method }) { if (method === "create") { - assert.deepEqual(args[0], { - name: "new event", - start: "2016-12-14", - stop: "2016-12-15", - allday: true, - }); + assert.deepEqual(args[0], [ + { + name: "new event", + start: "2016-12-14", + stop: "2016-12-15", + allday: true, + }, + ]); } }, }); @@ -1918,12 +1925,14 @@ QUnit.module("Views", ({ beforeEach }) => { `, mockRPC(route, { args, method }) { if (method === "create") { - assert.deepEqual(args[0], { - name: "new event", - start: "2016-12-14", - stop: "2016-12-14", - allday: true, - }); + assert.deepEqual(args[0], [ + { + name: "new event", + start: "2016-12-14", + stop: "2016-12-14", + allday: true, + }, + ]); } }, }); @@ -1967,12 +1976,14 @@ QUnit.module("Views", ({ beforeEach }) => { `, mockRPC(route, { args, method }) { if (method === "create") { - assert.deepEqual(args[0], { - name: "new event", - start: "2016-12-14", - stop: "2016-12-14", - allday: true, - }); + assert.deepEqual(args[0], [ + { + name: "new event", + start: "2016-12-14", + stop: "2016-12-14", + allday: true, + }, + ]); } }, }); @@ -2111,11 +2122,13 @@ QUnit.module("Views", ({ beforeEach }) => { if (method === "create") { assert.deepEqual( args[0], - { - name: "new event", - start: "2016-12-14 05:00:00", - stop: "2016-12-15 17:00:00", - }, + [ + { + name: "new event", + start: "2016-12-14 05:00:00", + stop: "2016-12-15 17:00:00", + }, + ], "should send the correct data to create events" ); } @@ -3568,7 +3581,8 @@ QUnit.module("Views", ({ beforeEach }) => { `, mockRPC(route, { method, args }) { if (method === "create") { - assert.strictEqual(args[0].start_date, "2016-12-20"); + const [values] = args[0]; + assert.strictEqual(values.start_date, "2016-12-20"); } if (method === "write") { assert.step(args[1].start_date); @@ -4553,7 +4567,8 @@ QUnit.module("Views", ({ beforeEach }) => { `, mockRPC(route, { method, args }) { if (method === "create") { - assert.deepEqual(args[0], expectedEvent); + const [values] = args[0]; + assert.deepEqual(values, expectedEvent); } }, }); diff --git a/addons/web/static/tests/views/fields/many2one_field_tests.js b/addons/web/static/tests/views/fields/many2one_field_tests.js index 42e2209aa88..20e833b2b40 100644 --- a/addons/web/static/tests/views/fields/many2one_field_tests.js +++ b/addons/web/static/tests/views/fields/many2one_field_tests.js @@ -1720,7 +1720,8 @@ QUnit.module("Fields", (hooks) => { } if (method === "create") { assert.step("create"); - assert.strictEqual(args[0].trululu, newRecordId); + const [values] = args[0]; + assert.strictEqual(values.trululu, newRecordId); } }, }); diff --git a/addons/web/static/tests/views/kanban_view_tests.js b/addons/web/static/tests/views/kanban_view_tests.js index 2c43614b27d..458cc66a10c 100644 --- a/addons/web/static/tests/views/kanban_view_tests.js +++ b/addons/web/static/tests/views/kanban_view_tests.js @@ -1726,11 +1726,13 @@ QUnit.module("Views", (hooks) => { if (args.method === "create") { assert.deepEqual( args.args[0], - { - foo: "new partner", - int_field: 4, - state: "def", - }, + [ + { + foo: "new partner", + int_field: 4, + state: "def", + }, + ], "should send the correct values" ); } @@ -1801,11 +1803,13 @@ QUnit.module("Views", (hooks) => { if (args.method === "create") { assert.deepEqual( args.args[0], - { - foo: "new partner", - int_field: 4, - state: "def", - }, + [ + { + foo: "new partner", + int_field: 4, + state: "def", + }, + ], "should send the correct values" ); } @@ -2026,11 +2030,13 @@ QUnit.module("Views", (hooks) => { if (method === "create") { assert.deepEqual( args[0], - { - foo: "new partner", - int_field: 4, - state: "def", - }, + [ + { + foo: "new partner", + int_field: 4, + state: "def", + }, + ], "should send the correct values" ); const { default_product_id, default_qux } = kwargs.context; @@ -2206,9 +2212,11 @@ QUnit.module("Views", (hooks) => { async mockRPC(route, { method, args, kwargs }) { assert.step(method || route); if (method === "create") { - assert.deepEqual(args[0], { - foo: "new partner", - }); + assert.deepEqual(args[0], [ + { + foo: "new partner", + }, + ]); const { default_category_ids } = kwargs.context; assert.deepEqual(default_category_ids, [6]); } @@ -2269,10 +2277,12 @@ QUnit.module("Views", (hooks) => { async mockRPC(route, { method, args, kwargs }) { assert.step(method || route); if (method === "create") { - assert.deepEqual(args[0], { - category_ids: [[6, false, [6]]], - foo: "new partner", - }); + assert.deepEqual(args[0], [ + { + category_ids: [[6, false, [6]]], + foo: "new partner", + }, + ]); const { default_category_ids } = kwargs.context; assert.deepEqual(default_category_ids, [6]); } @@ -2960,8 +2970,9 @@ QUnit.module("Views", (hooks) => { } case "create": { assert.step(method); - assert.strictEqual(args[0].foo, "new partner"); - assert.strictEqual(args[0].int_field, 3); + const [values] = args[0]; + assert.strictEqual(values.foo, "new partner"); + assert.strictEqual(values.int_field, 3); break; } } @@ -3066,7 +3077,7 @@ QUnit.module("Views", (hooks) => { } if (args.method === "create") { assert.step("create"); - assert.deepEqual(_.pick(args.args[0], "foo", "int_field"), { + assert.deepEqual(_.pick(args.args[0][0], "foo", "int_field"), { foo: "new partner", int_field: 3, }); @@ -4143,7 +4154,7 @@ QUnit.module("Views", (hooks) => { groupBy: ["foo"], async mockRPC(route, { method, args, kwargs }) { if (method === "create") { - assert.deepEqual(args[0], { foo: "blip" }); + assert.deepEqual(args[0], [{ foo: "blip" }]); assert.strictEqual(kwargs.context.default_foo, "blip"); } }, @@ -4185,7 +4196,7 @@ QUnit.module("Views", (hooks) => { groupBy: ["bar"], async mockRPC(route, { method, args, kwargs }) { if (method === "create") { - assert.deepEqual(args[0], { bar: true }); + assert.deepEqual(args[0], [{ bar: true }]); assert.strictEqual(kwargs.context.default_bar, true); } }, @@ -4231,7 +4242,7 @@ QUnit.module("Views", (hooks) => { groupBy: ["state"], async mockRPC(route, { method, args, kwargs }) { if (method === "create") { - assert.deepEqual(args[0], { state: "abc" }); + assert.deepEqual(args[0], [{ state: "abc" }]); assert.strictEqual(kwargs.context.default_state, "abc"); } }, diff --git a/addons/web/static/tests/views/list_view_tests.js b/addons/web/static/tests/views/list_view_tests.js index 1768dcef3df..23903c2c110 100644 --- a/addons/web/static/tests/views/list_view_tests.js +++ b/addons/web/static/tests/views/list_view_tests.js @@ -1289,10 +1289,10 @@ QUnit.module("Views", (hooks) => { `, - mockRPC(route, args) { - assert.step(args.method); - if (args.method === "create") { - assert.deepEqual(args.args, [{ int_field: 1 }]); + mockRPC(_, { args, method }) { + assert.step(method); + if (method === "create") { + assert.deepEqual(args[0], [{ int_field: 1 }]); } }, }); @@ -7472,11 +7472,7 @@ QUnit.module("Views", (hooks) => { ".o_view_nocontent", "should have a no content helper displayed" ); - assert.containsOnce( - target, - "div.table-responsive", - "should have a div.table-responsive" - ); + assert.containsOnce(target, "div.table-responsive", "should have a div.table-responsive"); assert.containsOnce(target, "table", "should have rendered a table"); await click(target.querySelector(".o_list_button_add")); diff --git a/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js b/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js index 42493b74f7c..569c1cfd2b4 100644 --- a/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js +++ b/addons/web/static/tests/views/view_dialogs/export_data_dialog_tests.js @@ -243,12 +243,13 @@ QUnit.module("ViewDialogs", (hooks) => { mockRPC(route, args) { if (args.method === "create") { assert.strictEqual(args.model, "ir.exports"); + const [values] = args.args[0]; assert.strictEqual( - args.args[0].name, + values.name, "Export template", "the template name is correctly sent" ); - return 2; + return [2]; } if (args.method === "search_read") { assert.deepEqual( diff --git a/doc/cla/corporate/cloudition.md b/doc/cla/corporate/cloudition.md new file mode 100644 index 00000000000..67d5492222f --- /dev/null +++ b/doc/cla/corporate/cloudition.md @@ -0,0 +1,15 @@ +Germany, 2023-01-11 + +cloudition GmbH agrees to the terms of the Odoo Corporate Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Stefan Reisich stefan.reisich@cloudition.de https://github.com/sreisich + +List of contributors: + +Stefan Reisich stefan.reisich@cloudition.de https://github.com/sreisich