[FIX] web: orm service: create several records

Before this commit, the "create" function of the ORM service didn't
allow to pass multiple records. As a consequence, it was needed to
create records one by one calling the ORM create multiple times or call
create via the method call.
This commit allows to use the create method to create several records at
once.

closes odoo/odoo#111965

Related: odoo/enterprise#37290
Signed-off-by: Aaron Bohy (aab) <aab@odoo.com>
This commit is contained in:
Odoo's Mergebot
2023-02-20 19:25:01 +01:00
committed by GitHub
18 changed files with 243 additions and 128 deletions
@@ -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);
+1 -1
View File
@@ -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");
@@ -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" }],
},
@@ -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],
@@ -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'}"/>
</form>`,
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");
}
@@ -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]);
}
}
}
+1 -1
View File
@@ -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);
}
/**
@@ -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;
@@ -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",
[
{
@@ -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: {
+21 -12
View File
@@ -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
@@ -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");
});
});
@@ -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);
}
},
});
@@ -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);
}
},
});
@@ -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");
}
},
@@ -1289,10 +1289,10 @@ QUnit.module("Views", (hooks) => {
<field name="foo" invisible="1"/>
<field name="int_field"/>
</tree>`,
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"));
@@ -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(
+15
View File
@@ -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