[FIX] web: relationalModel: basic keys supported in record context
Have an editable list view, displaying a many2many tags field. The m2m field
has a context, mentioning `active_id`. (`context="{'default_field': active_id}"`)
"basic context keys" is to be understood as the usual suspects: active_id, active_ids, current_company_id, active_model
Type something in the m2m input, and selet create and edit, in order to create a new record in a dialog form view.
Before this commit, there was a crash because active_id was not present in the evaluation context.
After this commit, the new record opens in its form view, with the right value for the field that must have a default.
closes odoo/odoo#96910
Signed-off-by: Lucas Perais (lpe) <lpe@odoo.com>
This commit is contained in:
@@ -36,7 +36,7 @@ QUnit.module('Views', function (hooks) {
|
||||
}
|
||||
}
|
||||
setupControlPanelServiceRegistry();
|
||||
serviceRegistry.add("company", companyService);
|
||||
serviceRegistry.add("company", companyService, { force: true });
|
||||
serviceRegistry.add("dialog", dialogService);
|
||||
});
|
||||
|
||||
|
||||
@@ -457,6 +457,10 @@ export class Record extends DataPoint {
|
||||
return {
|
||||
// ...
|
||||
...this.dataContext,
|
||||
active_id: this.resId || false,
|
||||
active_ids: this.resId ? [this.resId] : [],
|
||||
active_model: this.resModel,
|
||||
current_company_id: this.model.company.currentCompany.id,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3199,12 +3203,13 @@ export class StaticList extends DataPoint {
|
||||
StaticList.DEFAULT_LIMIT = 40;
|
||||
|
||||
export class RelationalModel extends Model {
|
||||
setup(params, { action, dialog, notification, rpc, user, view }) {
|
||||
setup(params, { action, dialog, notification, rpc, user, view, company }) {
|
||||
this.action = action;
|
||||
this.dialogService = dialog;
|
||||
this.notificationService = notification;
|
||||
this.rpc = rpc;
|
||||
this.user = user;
|
||||
this.company = company;
|
||||
this.viewService = view;
|
||||
this.orm = new RequestBatcherORM(rpc, user);
|
||||
this.keepLast = new KeepLast();
|
||||
@@ -3342,7 +3347,7 @@ export class RelationalModel extends Model {
|
||||
}
|
||||
}
|
||||
|
||||
RelationalModel.services = ["action", "dialog", "notification", "rpc", "user", "view"];
|
||||
RelationalModel.services = ["action", "dialog", "notification", "rpc", "user", "view", "company"];
|
||||
RelationalModel.Record = Record;
|
||||
RelationalModel.Group = Group;
|
||||
RelationalModel.DynamicRecordList = DynamicRecordList;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ActionDialog } from "@web/webclient/actions/action_dialog";
|
||||
import { hotkeyService } from "@web/core/hotkeys/hotkey_service";
|
||||
import { makeTestEnv, utils } from "../../helpers/mock_env";
|
||||
import {
|
||||
fakeCompanyService,
|
||||
fakeCommandService,
|
||||
makeFakeDialogService,
|
||||
makeFakeLocalizationService,
|
||||
@@ -296,6 +297,7 @@ QUnit.module("DebugMenu", (hooks) => {
|
||||
}
|
||||
};
|
||||
prepareRegistriesWithCleanup();
|
||||
registry.category("services").add("company", fakeCompanyService);
|
||||
|
||||
patchWithCleanup(odoo, {
|
||||
debug: true,
|
||||
@@ -387,6 +389,7 @@ QUnit.module("DebugMenu", (hooks) => {
|
||||
}
|
||||
};
|
||||
prepareRegistriesWithCleanup();
|
||||
registry.category("services").add("company", fakeCompanyService);
|
||||
|
||||
patchWithCleanup(odoo, {
|
||||
debug: true,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { effectService } from "@web/core/effects/effect_service";
|
||||
import { objectToUrlEncodedString } from "@web/core/utils/urls";
|
||||
import { registerCleanup } from "./cleanup";
|
||||
import { patchWithCleanup } from "./utils";
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
import { uiService } from "@web/core/ui/ui_service";
|
||||
import { ConnectionAbortedError } from "../../src/core/network/rpc_service";
|
||||
|
||||
@@ -70,7 +69,8 @@ export function makeFakeRPCService(mockRPC) {
|
||||
.then(resolve)
|
||||
.catch(reject);
|
||||
});
|
||||
rpcProm.abort = () => rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
|
||||
rpcProm.abort = () =>
|
||||
rejectFn(new ConnectionAbortedError("XmlHttpRequestError abort"));
|
||||
return rpcProm;
|
||||
};
|
||||
},
|
||||
@@ -79,7 +79,7 @@ export function makeFakeRPCService(mockRPC) {
|
||||
}
|
||||
|
||||
export function makeMockXHR(response, sendCb, def) {
|
||||
let MockXHR = function () {
|
||||
const MockXHR = function () {
|
||||
return {
|
||||
_loadListener: null,
|
||||
url: "",
|
||||
@@ -269,6 +269,17 @@ export function makeFakeUserService(hasGroup = () => false) {
|
||||
};
|
||||
}
|
||||
|
||||
export const fakeCompanyService = {
|
||||
start() {
|
||||
return {
|
||||
availableCompanies: {},
|
||||
allowedCompanyIds: [],
|
||||
currentCompany: {},
|
||||
setCompanies: () => {},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export function makeFakeHTTPService(getResponse, postResponse) {
|
||||
getResponse =
|
||||
getResponse ||
|
||||
@@ -295,7 +306,7 @@ export function makeFakeHTTPService(getResponse, postResponse) {
|
||||
}
|
||||
|
||||
export const mocks = {
|
||||
company: () => companyService,
|
||||
company: () => fakeCompanyService,
|
||||
command: () => fakeCommandService,
|
||||
cookie: () => fakeCookieService,
|
||||
effect: () => effectService, // BOI The real service ? Is this what we want ?
|
||||
|
||||
@@ -16,6 +16,9 @@ import {
|
||||
import { editSearch, validateSearch } from "@web/../tests/search/helpers";
|
||||
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
|
||||
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
|
||||
import { session } from "@web/session";
|
||||
import { companyService } from "@web/webclient/company_service";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
let target;
|
||||
let serverData;
|
||||
@@ -1869,4 +1872,184 @@ QUnit.module("Fields", (hooks) => {
|
||||
assert.containsOnce(target, ".modal");
|
||||
assert.strictEqual(target.querySelector(".modal-title").textContent, "Search: pokemon");
|
||||
});
|
||||
|
||||
QUnit.test("many2many basic keys in field evalcontext -- in list", async (assert) => {
|
||||
assert.expect(6);
|
||||
serverData.models.partner_type.fields.partner_id = {
|
||||
string: "Partners",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="partner_id" /></form>`,
|
||||
};
|
||||
|
||||
patchWithCleanup(session, {
|
||||
user_companies: {
|
||||
allowed_companies: {
|
||||
3: { id: 3, name: "Hermit", sequence: 1 },
|
||||
2: { id: 2, name: "Herman's", sequence: 2 },
|
||||
1: { id: 1, name: "Heroes TM", sequence: 3 },
|
||||
},
|
||||
current_company: 3,
|
||||
},
|
||||
});
|
||||
|
||||
registry.category("services").add("company", companyService, { force: true });
|
||||
|
||||
patchWithCleanup(AutoComplete, {
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "list",
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': active_id, 'ids': active_ids, 'model': active_model, 'company_id': current_company_id}"/>
|
||||
</tree>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 1);
|
||||
assert.strictEqual(args.kwargs.context.model, "partner");
|
||||
assert.deepEqual(args.kwargs.context.ids, [1]);
|
||||
assert.strictEqual(args.kwargs.context.company_id, 3);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await click(target.querySelector(".o_data_cell"));
|
||||
await editInput(target, ".o_field_many2many_selection input", "indianapolis");
|
||||
await clickOpenedDropdownItem(target, "timmy", "Create and edit...");
|
||||
assert.containsOnce(target, ".modal .o_field_many2one");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_field_many2one input").value,
|
||||
"first record"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("many2many basic keys in field evalcontext -- in form", async (assert) => {
|
||||
assert.expect(6);
|
||||
serverData.models.partner_type.fields.partner_id = {
|
||||
string: "Partners",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="partner_id" /></form>`,
|
||||
};
|
||||
|
||||
patchWithCleanup(session, {
|
||||
user_companies: {
|
||||
allowed_companies: {
|
||||
3: { id: 3, name: "Hermit", sequence: 1 },
|
||||
2: { id: 2, name: "Herman's", sequence: 2 },
|
||||
1: { id: 1, name: "Heroes TM", sequence: 3 },
|
||||
},
|
||||
current_company: 3,
|
||||
},
|
||||
});
|
||||
|
||||
registry.category("services").add("company", companyService, { force: true });
|
||||
|
||||
patchWithCleanup(AutoComplete, {
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resId: 1,
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': active_id, 'ids': active_ids, 'model': active_model, 'company_id': current_company_id}"/>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 1);
|
||||
assert.strictEqual(args.kwargs.context.model, "partner");
|
||||
assert.deepEqual(args.kwargs.context.ids, [1]);
|
||||
assert.strictEqual(args.kwargs.context.company_id, 3);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await clickEdit(target);
|
||||
await editInput(target, ".o_field_many2many_selection input", "indianapolis");
|
||||
await clickOpenedDropdownItem(target, "timmy", "Create and edit...");
|
||||
assert.containsOnce(target, ".modal .o_field_many2one");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_field_many2one input").value,
|
||||
"first record"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
"many2many basic keys in field evalcontext -- in a x2many in form",
|
||||
async (assert) => {
|
||||
assert.expect(6);
|
||||
serverData.models.partner_type.fields.partner_id = {
|
||||
string: "Partners",
|
||||
type: "many2one",
|
||||
relation: "partner",
|
||||
};
|
||||
serverData.views = {
|
||||
"partner_type,false,form": `<form><field name="partner_id" /></form>`,
|
||||
};
|
||||
|
||||
const rec = serverData.models.partner.records.find(({ id }) => id === 2);
|
||||
rec.p = [1];
|
||||
|
||||
patchWithCleanup(session, {
|
||||
user_companies: {
|
||||
allowed_companies: {
|
||||
3: { id: 3, name: "Hermit", sequence: 1 },
|
||||
2: { id: 2, name: "Herman's", sequence: 2 },
|
||||
1: { id: 1, name: "Heroes TM", sequence: 3 },
|
||||
},
|
||||
current_company: 3,
|
||||
},
|
||||
});
|
||||
registry.category("services").add("company", companyService, { force: true });
|
||||
|
||||
patchWithCleanup(AutoComplete, {
|
||||
timeout: 0,
|
||||
});
|
||||
|
||||
await makeView({
|
||||
type: "form",
|
||||
resId: 2,
|
||||
resModel: "partner",
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="p">
|
||||
<tree editable="top">
|
||||
<field name="timmy" widget="many2many_tags" context="{ 'default_partner_id': active_id, 'ids': active_ids, 'model': active_model, 'company_id': current_company_id}"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "onchange") {
|
||||
assert.strictEqual(args.kwargs.context.default_partner_id, 1);
|
||||
assert.strictEqual(args.kwargs.context.model, "partner");
|
||||
assert.deepEqual(args.kwargs.context.ids, [1]);
|
||||
assert.strictEqual(args.kwargs.context.company_id, 3);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
await clickEdit(target);
|
||||
await click(target, ".o_data_cell");
|
||||
await editInput(target, ".o_field_many2many_selection input", "indianapolis");
|
||||
await clickOpenedDropdownItem(target, "timmy", "Create and edit...");
|
||||
assert.containsOnce(target, ".modal .o_field_many2one");
|
||||
assert.strictEqual(
|
||||
target.querySelector(".modal .o_field_many2one input").value,
|
||||
"first record"
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from "../search/helpers";
|
||||
import { addLegacyMockEnvironment } from "../webclient/helpers";
|
||||
import {
|
||||
fakeCompanyService,
|
||||
makeFakeLocalizationService,
|
||||
makeFakeRouterService,
|
||||
makeFakeUserService,
|
||||
@@ -105,4 +106,5 @@ export function setupViewRegistries() {
|
||||
serviceRegistry.add("localization", makeFakeLocalizationService()), { force: true };
|
||||
serviceRegistry.add("dialog", dialogService), { force: true };
|
||||
serviceRegistry.add("popover", popoverService), { force: true };
|
||||
serviceRegistry.add("company", fakeCompanyService);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,8 @@ import {
|
||||
triggerEvent,
|
||||
mockTimeout,
|
||||
} from "@web/../tests/helpers/utils";
|
||||
import { makeView } from "@web/../tests/views/helpers";
|
||||
import { dialogService } from "@web/core/dialog/dialog_service";
|
||||
import { makeView, setupViewRegistries } from "@web/../tests/views/helpers";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { setupControlPanelServiceRegistry } from "@web/../tests/search/helpers";
|
||||
import { makeFakeUserService } from "../../helpers/mock_services";
|
||||
|
||||
const serviceRegistry = registry.category("services");
|
||||
@@ -97,13 +95,12 @@ QUnit.module("ViewDialogs", (hooks) => {
|
||||
},
|
||||
};
|
||||
target = getFixture();
|
||||
setupControlPanelServiceRegistry();
|
||||
setupViewRegistries();
|
||||
|
||||
function hasGroup(group) {
|
||||
return group === "base.group_allow_export";
|
||||
}
|
||||
serviceRegistry.add("user", makeFakeUserService(hasGroup), { force: true });
|
||||
serviceRegistry.add("dialog", dialogService);
|
||||
|
||||
fetchedFields = {
|
||||
root: [
|
||||
@@ -595,7 +592,7 @@ QUnit.module("ViewDialogs", (hooks) => {
|
||||
|
||||
QUnit.test("Export dialog: display on small screen after resize", async function (assert) {
|
||||
const { execRegisteredTimeouts } = mockTimeout();
|
||||
let ui = {
|
||||
const ui = {
|
||||
activateElement: () => {},
|
||||
deactivateElement: () => {},
|
||||
size: 4,
|
||||
|
||||
@@ -33,6 +33,7 @@ import { registerCleanup } from "../helpers/cleanup";
|
||||
import { makeTestEnv } from "../helpers/mock_env";
|
||||
import {
|
||||
fakeTitleService,
|
||||
fakeCompanyService,
|
||||
makeFakeLocalizationService,
|
||||
makeFakeRouterService,
|
||||
makeFakeHTTPService,
|
||||
@@ -74,7 +75,7 @@ export function setupWebClientRegistries() {
|
||||
options: { sequence: 0 },
|
||||
},
|
||||
};
|
||||
for (let [key, { value, options }] of Object.entries(favoriveMenuItems)) {
|
||||
for (const [key, { value, options }] of Object.entries(favoriveMenuItems)) {
|
||||
if (!favoriteMenuRegistry.contains(key)) {
|
||||
favoriteMenuRegistry.add(key, value, options);
|
||||
}
|
||||
@@ -97,8 +98,9 @@ export function setupWebClientRegistries() {
|
||||
ui: () => uiService,
|
||||
user: () => makeFakeUserService(),
|
||||
view: () => viewService,
|
||||
company: () => fakeCompanyService,
|
||||
};
|
||||
for (let serviceName in services) {
|
||||
for (const serviceName in services) {
|
||||
if (!serviceRegistry.contains(serviceName)) {
|
||||
serviceRegistry.add(serviceName, services[serviceName]());
|
||||
}
|
||||
@@ -195,7 +197,7 @@ export async function addLegacyMockEnvironment(env, legacyParams = {}) {
|
||||
serviceRegistry.add("legacy_action_manager", legacyActionManagerService);
|
||||
serviceRegistry.add("legacy_notification", makeLegacyNotificationService(legacyEnv));
|
||||
// deploy wowl services into the legacy env.
|
||||
const wowlToLegacyServiceMappers = registry.category('wowlToLegacyServiceMappers').getEntries();
|
||||
const wowlToLegacyServiceMappers = registry.category("wowlToLegacyServiceMappers").getEntries();
|
||||
for (const [legacyServiceName, wowlToLegacyServiceMapper] of wowlToLegacyServiceMappers) {
|
||||
serviceRegistry.add(legacyServiceName, wowlToLegacyServiceMapper(legacyEnv));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user