[REF] web,mail: adapt code to new load_views API

Part-of: odoo/odoo#87522
This commit is contained in:
Aaron Bohy
2022-04-29 09:57:44 +02:00
committed by Denis Ledoux
parent b03c227e88
commit f573e93bb2
38 changed files with 636 additions and 444 deletions
+4 -4
View File
@@ -365,7 +365,7 @@ odoo.define("board.dashboard_tests", function (require) {
],
});
}
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.deepEqual(
args.kwargs.context,
{ a: 1, b: 2 },
@@ -656,8 +656,8 @@ odoo.define("board.dashboard_tests", function (require) {
"</board>" +
"</form>",
intercepts: {
load_views: function () {
throw new Error("load_views should not be called");
get_views: function () {
throw new Error("get_views should not be called");
},
},
mockRPC: function (route) {
@@ -1048,7 +1048,7 @@ odoo.define("board.dashboard_tests", function (require) {
"</board>" +
"</form>",
mockRPC: function (route, args) {
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.deepEqual(
pyUtils.eval("context", args.kwargs.context),
{ lang: "fr_FR" },
+1
View File
@@ -99,6 +99,7 @@
'web/static/src/legacy/js/services/data_manager.js',
'web/static/src/legacy/js/services/session.js',
'web/static/src/legacy/js/widgets/date_picker.js',
'web/static/src/legacy/legacy_load_views.js',
'web/static/src/legacy/legacy_promise_error_handler.js',
'web/static/src/legacy/legacy_rpc_error_handler.js',
'web/static/src/legacy/utils.js',
@@ -562,7 +562,7 @@ QUnit.test("Schedule activity dialog uses the same search view as activity view"
});
function mockRPC(route, args) {
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.step(JSON.stringify(args.kwargs.views));
}
}
+1
View File
@@ -145,6 +145,7 @@ This module provides the core of the Odoo Web Client.
'web/static/src/legacy/legacy_service_provider.js',
'web/static/src/legacy/legacy_client_actions.js',
'web/static/src/legacy/legacy_dialog.js',
'web/static/src/legacy/legacy_load_views.js',
'web/static/src/legacy/legacy_views.js',
'web/static/src/legacy/legacy_promise_error_handler.js',
'web/static/src/legacy/legacy_rpc_error_handler.js',
@@ -5,7 +5,7 @@ var config = require('web.config');
var core = require('web.core');
var rpc = require('web.rpc');
var session = require('web.session');
var utils = require('web.utils');
const { generateLegacyLoadViewsResult } = require("@web/legacy/legacy_load_views");
return core.Class.extend({
init: function () {
@@ -96,11 +96,13 @@ return core.Class.extend({
args: [],
kwargs: { context, options, views: views_descr },
model,
method: 'load_views',
method: 'get_views',
}).then(result => {
const { models, views } = result;
result = generateLegacyLoadViewsResult(model, views, models);
// Freeze the fields dict as it will be shared between views and
// no one should edit it
utils.deepFreeze(result.fields);
// utils.deepFreeze(result.fields); // OWL issue regarding proxies and frozen objects https://github.com/odoo/owl/issues/1158
for (const [ /* viewId */ , viewType] of views_descr) {
const fvg = result.fields_views[viewType];
fvg.viewFields = fvg.fields;
@@ -0,0 +1,116 @@
/** @odoo-module **/
const xmlSerializer = new XMLSerializer();
const domParser = new DOMParser();
function traverse(tree, f) {
if (f(tree)) {
_.each(tree.children, function (c) {
traverse(c, f);
});
}
}
/**
* Given the result of a get_views, produces the equivalent of what was previously
* returned, such that legacy views continue working as before without any adaptations.
*
* @param {string} resModel
* @param {Object} views "views" key of the result of a get_views
* @param {Object} models maps of model name -> fields_get
* @returns {Object} similar to what was previously returned by load_views
*/
export function generateLegacyLoadViewsResult(resModel, views, models) {
const legacyFieldViews = {
fields: models[resModel],
fields_views: {},
};
if (views.search && views.search.filters) {
legacyFieldViews.filters = views.search.filters;
}
for (const viewType in views) {
const { arch, toolbar, id } = views[viewType];
const { arch: processedArch, viewFields } = processArch(arch, viewType, resModel, models);
legacyFieldViews.fields_views[viewType] = {
arch: processedArch,
fields: viewFields,
model: resModel,
toolbar,
view_id: id,
};
}
return legacyFieldViews;
}
/**
* Given an arch with inline x2many subviews, generates the object of fields
* appearing in the arch, with a "views" key that contains the descriptions of
* their subviews (whose archs are removed from the main arch).
*
* @param {string} arch
* @param {string} viewType
* @param {string} resModel
* @param {Object} models
* @returns {Object} the processed arch and the (view) fields description
*/
export function processArch(arch, viewType, resModel, models) {
const viewFields = {};
const archDoc = domParser.parseFromString(arch, "text/xml").documentElement;
traverse(archDoc, function (node) {
if (node.nodeType === 3) {
return false;
}
if (node.tagName === "field") {
const fieldName = node.getAttribute("name");
viewFields[fieldName] = viewFields[fieldName] || {
...models[resModel][fieldName],
views: {},
};
// extract subviews
// note: this should only be done for form views, because x2many subviews doesn't make
// any sense in any other view type, but sometimes in the codebase, people inline
// subviews in list views (e.g. sale.order.form.inherit.sale.product.configurator) so
// we remove them from the main arch anyway, to mock the previous behavior
if (viewType === "form" || viewType === "list") {
const coModel = models[resModel][fieldName].relation;
const views = {};
for (const childNode of [...node.children]) {
const viewType = childNode.tagName === "tree" ? "list" : childNode.tagName;
const { arch: subViewArch, viewFields: subViewFields } = processArch(
xmlSerializer.serializeToString(childNode),
viewType,
coModel,
models
);
views[viewType] = { arch: subViewArch, fields: subViewFields };
node.removeChild(childNode);
}
viewFields[fieldName].views = views;
}
return false;
}
// list: extract groupby informations
if (viewType === "list" && node.tagName === "groupby" && node !== archDoc) {
const fieldName = node.getAttribute("name");
const coModel = models[resModel][fieldName].relation;
const { arch: subViewArch, viewFields: subViewFields } = processArch(
xmlSerializer.serializeToString(node),
"groupby",
coModel,
models
);
const groupbyView = { arch: subViewArch, fields: subViewFields };
viewFields[fieldName] = viewFields[fieldName] || {
...models[resModel][fieldName],
views: {},
};
viewFields[fieldName].views.groupby = groupbyView;
for (const child of [...node.children]) {
node.removeChild(child);
}
return false;
}
return true;
});
return { arch: xmlSerializer.serializeToString(archDoc), viewFields };
}
@@ -116,6 +116,7 @@ function registerView(name, LegacyView) {
this.viewInfo = Object.assign({}, fieldsInfo, {
fields: result.fields,
viewFields: fieldsInfo.fields,
type: this.props.type,
});
let controlPanelFieldsView;
if (result.fields_views.search) {
@@ -36,10 +36,10 @@ function reduceType(type) {
}
export class SearchArchParser extends XMLParser {
constructor(searchViewDescription, searchDefaults = {}, searchPanelDefaults = {}) {
constructor(searchViewDescription, fields, searchDefaults = {}, searchPanelDefaults = {}) {
super();
const { fields, irFilters, arch } = searchViewDescription;
const { irFilters, arch } = searchViewDescription;
this.fields = fields || {};
this.irFilters = irFilters || [];
+9 -10
View File
@@ -229,7 +229,7 @@ export class SearchModel extends EventBus {
const searchViewDescription = {};
if (loadSearchView) {
const viewDescriptions = await this.viewService.loadViews(
const result = await this.viewService.loadViews(
{
context: this.globalContext,
resModel,
@@ -240,23 +240,20 @@ export class SearchModel extends EventBus {
loadIrFilters: loadIrFilters || false,
}
);
Object.assign(searchViewDescription, viewDescriptions.search);
Object.assign(searchViewDescription, result.views.search);
searchViewFields = searchViewFields || result.fields;
}
if (searchViewArch) {
searchViewDescription.arch = searchViewArch;
}
if (searchViewFields) {
searchViewDescription.fields = searchViewFields;
}
if (irFilters) {
searchViewDescription.irFilters = irFilters;
}
if (searchViewId !== undefined) {
searchViewDescription.viewId = searchViewId;
}
this.searchViewArch = searchViewDescription.arch || "<search/>";
this.searchViewFields = searchViewDescription.fields || {};
this.searchViewFields = searchViewFields || {};
if (searchViewDescription.irFilters) {
this.irFilters = searchViewDescription.irFilters;
}
@@ -266,7 +263,7 @@ export class SearchModel extends EventBus {
if (config.state) {
this._importState(config.state);
this.__legacyParseSearchPanelArchAnyway(searchViewDescription);
this.__legacyParseSearchPanelArchAnyway(searchViewDescription, searchViewFields);
this.domainParts = {};
this.display = this._getDisplay(config.display);
@@ -307,6 +304,7 @@ export class SearchModel extends EventBus {
const parser = new SearchArchParser(
searchViewDescription,
searchViewFields,
searchDefaults,
searchPanelDefaults
);
@@ -2084,13 +2082,14 @@ export class SearchModel extends EventBus {
* extension doesn't include the arch information, i.e. the class name and
* view types. We have to extract those if they are not given.
* @param {Object} searchViewDescription
* @param {Object} searchViewFields
*/
__legacyParseSearchPanelArchAnyway(searchViewDescription) {
__legacyParseSearchPanelArchAnyway(searchViewDescription, searchViewFields) {
if (this.searchPanelInfo) {
return;
}
const parser = new SearchArchParser(searchViewDescription);
const parser = new SearchArchParser(searchViewDescription, searchViewFields);
const { searchPanelInfo } = parser.parse();
this.searchPanelInfo = { ...searchPanelInfo, shouldReload: false };
+7 -8
View File
@@ -199,32 +199,31 @@ export class View extends Component {
if (loadView || loadSearchView) {
// view description (or search view description if required) is incomplete
// a loadViews is done to complete the missing information
const viewDescriptions = await this.viewService.loadViews(
const result = await this.viewService.loadViews(
{ context, resModel, views },
{ actionId: this.env.config.actionId, loadActionMenus, loadIrFilters }
);
// Note: if this.props.views is different from views, the cached descriptions
// will certainly not be reused! (but for the standard flow this will work as
// before)
viewDescription = viewDescriptions[type];
view[0] = viewDescription.viewId;
searchViewDescription = viewDescriptions.search;
viewDescription = result.views[type];
searchViewDescription = result.views.search;
if (loadSearchView) {
searchViewId = searchViewId || searchViewDescription.viewId;
searchViewId = searchViewId || searchViewDescription.id;
if (!searchViewArch) {
searchViewArch = searchViewDescription.arch;
searchViewFields = searchViewDescription.fields;
searchViewFields = result.fields;
}
if (!irFilters) {
irFilters = searchViewDescription.irFilters;
}
}
this.env.config.views = views;
fields = fields || result.fields;
}
if (!arch) {
arch = viewDescription.arch;
fields = viewDescription.fields;
}
if (!actionMenus) {
actionMenus = viewDescription.actionMenus;
@@ -244,7 +243,7 @@ export class View extends Component {
}
Object.assign(this.env.config, {
viewId: viewDescription.viewId,
viewId: viewDescription.id,
viewType: type,
viewSubType: subType,
bannerRoute,
+38 -36
View File
@@ -1,7 +1,8 @@
/** @odoo-module **/
import { deepCopy } from "@web/core/utils/objects";
import { registry } from "@web/core/registry";
import { device } from 'web.config';
import { generateLegacyLoadViewsResult } from "@web/legacy/legacy_load_views";
/**
* @typedef {Object} IrFilter
@@ -64,47 +65,48 @@ export const viewService = {
* @returns {Promise<ViewDescriptions>}
*/
async function loadViews(params, options) {
const key = JSON.stringify([params.resModel, params.views, params.context, options]);
const loadViewsOptions = {
action_id: options.actionId || false,
load_filters: options.loadIrFilters || false,
toolbar: options.loadActionMenus || false,
};
if (env.isSmall) {
loadViewsOptions.mobile = true;
}
const { context, resModel, views } = params;
const key = JSON.stringify([resModel, views, context, loadViewsOptions]);
if (!cache[key]) {
var load_views_options = {
action_id: options.actionId || false,
load_filters: options.loadIrFilters || false,
toolbar: options.loadActionMenus || false,
}
if (device.isMobile){
load_views_options.mobile = device.isMobile;
}
cache[key] = orm
.call(params.resModel, "load_views", [], {
views: params.views,
options: load_views_options,
context: params.context,
})
.call(resModel, "get_views", [], { context, views, options: loadViewsOptions })
.then((result) => {
const { models, views } = result;
const modelsCopy = deepCopy(models); // for legacy views
const fields = models[resModel];
// add relatedFields for relational fields in view
function setRelatedFields(fields, models) {
for (const field of Object.values(fields)) {
if (field.relation && !field.relatedFields) {
field.relatedFields = models[field.relation] || {};
setRelatedFields(field.relatedFields, models);
}
}
}
setRelatedFields(models[resModel], models);
const viewDescriptions = {
__legacy__: result,
}; // for legacy purpose, keys in result are left in viewDescriptions
for (const [, viewType] of params.views) {
const viewDescription = JSON.parse(
JSON.stringify(result.fields_views[viewType])
);
viewDescription.viewId = viewDescription.view_id;
delete viewDescription.view_id;
if (viewDescription.toolbar) {
viewDescription.actionMenus = viewDescription.toolbar;
delete viewDescription.toolbar;
__legacy__: generateLegacyLoadViewsResult(resModel, views, modelsCopy),
fields,
views: {},
};
for (const viewType in views) {
const { arch, toolbar, id, filters } = views[viewType];
const viewDescription = { arch, id };
if (toolbar) {
viewDescription.actionMenus = toolbar;
}
viewDescription.fields = Object.assign(
{},
result.fields,
viewDescription.fields
); // before a deep freeze was done.
delete viewDescription.base_model; // unused
delete viewDescription.field_parent; // unused
if (viewType === "search" && options.loadIrFilters) {
viewDescription.irFilters = result.filters;
if (filters) {
viewDescription.irFilters = filters;
}
viewDescriptions[viewType] = viewDescription;
viewDescriptions.views[viewType] = viewDescription;
}
return viewDescriptions;
})
+243 -210
View File
@@ -3,6 +3,8 @@
import { browser } from "@web/core/browser/browser";
import { Domain } from "@web/core/domain";
import { evaluateExpr } from "@web/core/py_js/py";
import { deepCopy } from "@web/core/utils/objects";
import { intersection } from "@web/core/utils/arrays";
import { registry } from "@web/core/registry";
import { makeFakeRPCService, makeMockFetch } from "./mock_services";
import { patchWithCleanup } from "./utils";
@@ -11,6 +13,9 @@ import { parseDateTime } from "@web/core/l10n/dates";
const { DateTime } = luxon;
const serviceRegistry = registry.category("services");
const domParser = new DOMParser();
const xmlSerializer = new XMLSerializer();
// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------
@@ -21,197 +26,6 @@ function traverseElementTree(tree, cb) {
}
}
export function _fieldsViewGet(params) {
let processedNodes = params.processedNodes || [];
const { arch, context, fields, modelName } = params;
function isNodeProcessed(node) {
return processedNodes.findIndex((n) => n.isSameNode(node)) > -1;
}
const modifiersNames = ["invisible", "readonly", "required"];
const onchanges = params.models[modelName].onchanges || {};
const fieldNodes = {};
const groupbyNodes = {};
let doc;
if (typeof arch === "string") {
const domParser = new DOMParser();
doc = domParser.parseFromString(arch, "text/xml").documentElement;
} else {
doc = arch;
}
const inTreeView = doc.tagName === "tree";
// mock _postprocess_access_rights
const isBaseModel = !context.base_model_name || modelName === context.base_model_name;
const views = ["kanban", "tree", "form", "gantt", "activity"];
if (isBaseModel && views.indexOf(doc.tagName) !== -1) {
for (const action of ["create", "delete", "edit", "write"]) {
if (!doc.getAttribute(action) && action in context && !context[action]) {
doc.setAttribute(action, "false");
}
}
}
traverseElementTree(doc, (node) => {
if (node.nodeType === Node.TEXT_NODE) {
return false;
}
const modifiers = {};
const isField = node.tagName === "field";
const isGroupby = node.tagName === "groupby";
if (isField) {
const fieldName = node.getAttribute("name");
fieldNodes[fieldName] = node;
// 'transfer_field_to_modifiers' simulation
const field = fields[fieldName];
if (!field) {
throw new Error("Field " + fieldName + " does not exist");
}
const defaultValues = {};
const stateExceptions = {}; // what is this ?
modifiersNames.forEach((attr) => {
stateExceptions[attr] = [];
defaultValues[attr] = !!field[attr];
});
// LPE: what is this ?
/* _.each(field.states || {}, function (modifs, state) {
_.each(modifs, function (modif) {
if (defaultValues[modif[0]] !== modif[1]) {
stateExceptions[modif[0]].append(state);
}
});
});*/
Object.entries(defaultValues).forEach(([attr, defaultValue]) => {
if (stateExceptions[attr].length) {
modifiers[attr] = [
["state", defaultValue ? "not in" : "in", stateExceptions[attr]],
];
} else {
modifiers[attr] = defaultValue;
}
});
} else if (isGroupby && !isNodeProcessed(node)) {
const groupbyName = node.getAttribute("name");
fieldNodes[groupbyName] = node;
groupbyNodes[groupbyName] = node;
}
// 'transfer_node_to_modifiers' simulation
let attrs = node.getAttribute("attrs");
if (attrs) {
attrs = evaluateExpr(attrs);
Object.assign(modifiers, attrs);
}
const states = node.getAttribute("states");
if (states) {
if (!modifiers.invisible) {
modifiers.invisible = [];
}
modifiers.invisible.push(["state", "not in", states.split(",")]);
}
const inListHeader = inTreeView && node.closest("header");
modifiersNames.forEach((attr) => {
const mod = node.getAttribute(attr);
if (mod) {
// TODO
// const pyevalContext = window.py.dict.fromJSON(context || {});
// var v = pyUtils.py_eval(mod, {context: pyevalContext}) ? true : false;
console.info(
"MockServer: naive parse of modifier value in",
QUnit.config.current.testName
);
const v = JSON.parse(mod);
if (inTreeView && !inListHeader && attr === "invisible") {
modifiers.column_invisible = v;
} else if (v || !(attr in modifiers) || !Array.isArray(modifiers[attr])) {
modifiers[attr] = v;
}
}
});
modifiersNames.forEach((attr) => {
if (
attr in modifiers &&
(!!modifiers[attr] === false ||
(Array.isArray(modifiers[attr]) && !modifiers[attr].length))
) {
delete modifiers[attr];
}
});
if (Object.keys(modifiers).length) {
node.setAttribute("modifiers", JSON.stringify(modifiers));
}
if (isGroupby && !isNodeProcessed(node)) {
return false;
}
return !isField;
});
let relModel, relFields;
Object.entries(fieldNodes).forEach(([name, node]) => {
const field = fields[name];
if (field.type === "many2one" || field.type === "many2many") {
const canCreate = node.getAttribute("can_create");
node.setAttribute("can_create", canCreate || "true");
const canWrite = node.getAttribute("can_write");
node.setAttribute("can_write", canWrite || "true");
}
if (field.type === "one2many" || field.type === "many2many") {
field.views = {};
Array.from(node.children).forEach((children) => {
if (children.tagName) {
// skip text nodes
relModel = field.relation;
relFields = Object.assign({}, params.models[relModel].fields);
field.views[children.tagName] = _fieldsViewGet({
models: params.models,
arch: children,
modelName: relModel,
fields: relFields,
context: Object.assign({}, context, { base_model_name: modelName }),
processedNodes,
});
}
});
}
// add onchanges
if (name in onchanges) {
node.setAttribute("on_change", "1");
}
});
Object.entries(groupbyNodes).forEach(([name, node]) => {
const field = fields[name];
if (field.type !== "many2one") {
throw new Error("groupby can only target many2one");
}
field.views = {};
relModel = field.relation;
relFields = Object.assign({}, params.models[relModel].fields);
processedNodes.push(node);
// postprocess simulation
field.views.groupby = _fieldsViewGet({
models: params.models,
arch: node,
modelName: relModel,
fields: relFields,
context,
processedNodes,
});
while (node.firstChild) {
node.removeChild(node.firstChild);
}
});
const xmlSerializer = new XMLSerializer();
const processedArch = xmlSerializer.serializeToString(doc);
const fieldsInView = {};
Object.entries(fields).forEach(([fname, field]) => {
if (fname in fieldNodes) {
fieldsInView[fname] = field;
}
});
return {
arch: processedArch,
fields: fieldsInView,
model: modelName,
type: doc.tagName === "tree" ? "list" : doc.tagName,
};
}
// -----------------------------------------------------------------------------
// MockServer
// -----------------------------------------------------------------------------
@@ -298,7 +112,7 @@ export class MockServer {
// def.abort = abort;
}
fieldsViewGet(modelName, args, kwargs) {
getView(modelName, args, kwargs) {
if (!(modelName in this.models)) {
throw new Error(`Model ${modelName} was not defined in mock server data`);
}
@@ -331,7 +145,7 @@ export class MockServer {
fields[fieldName].name = fieldName;
}
// var viewOptions = params.viewOptions || {};
const fvg = _fieldsViewGet({
const view = this._getView({
arch,
modelName,
fields,
@@ -339,13 +153,231 @@ export class MockServer {
models: this.models,
});
if (kwargs.options.toolbar) {
fvg.toolbar = this.models[modelName].toolbar || {};
view.toolbar = this.models[modelName].toolbar || {};
}
if (viewId !== undefined) {
fvg.view_id = viewId;
fvg.name = key;
view.id = viewId;
}
return fvg;
return view;
}
_getView(params) {
let processedNodes = params.processedNodes || [];
const { arch, context, modelName } = params;
const level = params.level || 0;
const fields = deepCopy(params.fields);
function isNodeProcessed(node) {
return processedNodes.findIndex((n) => n.isSameNode(node)) > -1;
}
const modifiersNames = ["invisible", "readonly", "required"];
const onchanges = params.models[modelName].onchanges || {};
const fieldNodes = {};
const groupbyNodes = {};
const relatedModels = new Set([modelName]);
let doc;
if (typeof arch === "string") {
doc = domParser.parseFromString(arch, "text/xml").documentElement;
} else {
doc = arch;
}
const inTreeView = doc.tagName === "tree";
const inFormView = doc.tagName === "form";
// mock _postprocess_access_rights
const isBaseModel = !context.base_model_name || modelName === context.base_model_name;
const views = ["kanban", "tree", "form", "gantt", "activity"];
if (isBaseModel && views.indexOf(doc.tagName) !== -1) {
for (const action of ["create", "delete", "edit", "write"]) {
if (!doc.getAttribute(action) && action in context && !context[action]) {
doc.setAttribute(action, "false");
}
}
}
traverseElementTree(doc, (node) => {
if (node.nodeType === Node.TEXT_NODE) {
return false;
}
const modifiers = {};
const isField = node.tagName === "field";
const isGroupby = node.tagName === "groupby";
if (isField) {
const fieldName = node.getAttribute("name");
fieldNodes[fieldName] = node;
// 'transfer_field_to_modifiers' simulation
const field = fields[fieldName];
if (!field) {
throw new Error("Field " + fieldName + " does not exist");
}
const defaultValues = {};
const stateExceptions = {}; // what is this ?
modifiersNames.forEach((attr) => {
stateExceptions[attr] = [];
defaultValues[attr] = !!field[attr];
});
// LPE: what is this ?
/* _.each(field.states || {}, function (modifs, state) {
_.each(modifs, function (modif) {
if (defaultValues[modif[0]] !== modif[1]) {
stateExceptions[modif[0]].append(state);
}
});
});*/
Object.entries(defaultValues).forEach(([attr, defaultValue]) => {
if (stateExceptions[attr].length) {
modifiers[attr] = [
["state", defaultValue ? "not in" : "in", stateExceptions[attr]],
];
} else {
modifiers[attr] = defaultValue;
}
});
} else if (isGroupby && !isNodeProcessed(node)) {
const groupbyName = node.getAttribute("name");
fieldNodes[groupbyName] = node;
groupbyNodes[groupbyName] = node;
}
// 'transfer_node_to_modifiers' simulation
let attrs = node.getAttribute("attrs");
if (attrs) {
attrs = evaluateExpr(attrs);
Object.assign(modifiers, attrs);
}
const states = node.getAttribute("states");
if (states) {
if (!modifiers.invisible) {
modifiers.invisible = [];
}
modifiers.invisible.push(["state", "not in", states.split(",")]);
}
const inListHeader = inTreeView && node.closest("header");
modifiersNames.forEach((attr) => {
const mod = node.getAttribute(attr);
if (mod) {
const v = evaluateExpr(mod, { context }) ? true : false;
if (inTreeView && !inListHeader && attr === "invisible") {
modifiers.column_invisible = v;
} else if (v || !(attr in modifiers) || !Array.isArray(modifiers[attr])) {
modifiers[attr] = v;
}
}
});
modifiersNames.forEach((attr) => {
if (
attr in modifiers &&
(!!modifiers[attr] === false ||
(Array.isArray(modifiers[attr]) && !modifiers[attr].length))
) {
delete modifiers[attr];
}
});
if (Object.keys(modifiers).length) {
node.setAttribute("modifiers", JSON.stringify(modifiers));
}
if (isGroupby && !isNodeProcessed(node)) {
return false;
}
return !isField;
});
let relModel, relFields;
Object.entries(fieldNodes).forEach(([name, node]) => {
const field = fields[name];
if (field.type === "many2one" || field.type === "many2many") {
const canCreate = node.getAttribute("can_create");
node.setAttribute("can_create", canCreate || "true");
const canWrite = node.getAttribute("can_write");
node.setAttribute("can_write", canWrite || "true");
}
if (field.type === "one2many" || field.type === "many2many") {
relModel = field.relation;
relatedModels.add(relModel);
// inline subviews: in forms if field is visible and has no widget (1st level only)
if (
inFormView &&
level === 0 &&
!node.getAttribute("widget") &&
!node.getAttribute("invisible")
) {
const inlineViewTypes = Array.from(node.children).map((c) => c.tagName);
const missingViewtypes = inlineViewTypes.includes("form") ? [] : ["form"];
const mode = node.getAttribute("mode") || "kanban,tree";
if (!intersection(inlineViewTypes, mode.split(",")).length) {
// TODO: use a kanban view by default in mobile
missingViewtypes.push((node.getAttribute("mode") || "list").split(",")[0]);
}
for (let type of missingViewtypes) {
type = type === "tree" ? "list" : type;
let key = `${field.relation},false,${type}`;
if (!this.archs[key]) {
const regexp = new RegExp(`${field.relation},[a-z._0-9]+,${type}`);
key = Object.keys(this.archs).find((k) => regexp.test(k));
}
// in a lot of tests, we don't need the form view, so it doesn't even exist
const arch = this.archs[key] || (type === "form" ? "<form/>" : null);
if (!arch) {
throw new Error(`Can't find ${type} view to inline`);
}
node.appendChild(
domParser.parseFromString(arch, "text/xml").documentElement
);
}
}
Array.from(node.children).forEach((childNode) => {
if (childNode.tagName) {
relFields = Object.assign({}, params.models[relModel].fields);
// this is hackhish, but _getView modifies the subview document in place,
// especially to generate the "modifiers" attribute
const { models } = this._getView({
models: params.models,
arch: childNode,
modelName: relModel,
fields: relFields,
context,
processedNodes,
level: level + 1,
});
[...models].forEach((modelName) => relatedModels.add(modelName));
}
});
}
// add onchanges
if (name in onchanges) {
node.setAttribute("on_change", "1");
}
});
Object.entries(groupbyNodes).forEach(([name, node]) => {
const field = fields[name];
if (field.type !== "many2one") {
throw new Error("groupby can only target many2one");
}
field.views = {};
relModel = field.relation;
relatedModels.add(relModel);
relFields = Object.assign({}, params.models[relModel].fields);
processedNodes.push(node);
// postprocess simulation
const { models } = this._getView({
models: params.models,
arch: node,
modelName: relModel,
fields: relFields,
context,
processedNodes,
});
[...models].forEach((modelName) => relatedModels.add(modelName));
});
const processedArch = xmlSerializer.serializeToString(doc);
const fieldsInView = {};
Object.entries(fields).forEach(([fname, field]) => {
if (fname in fieldNodes) {
fieldsInView[fname] = field;
}
});
return {
arch: processedArch,
model: modelName,
type: doc.tagName === "tree" ? "list" : doc.tagName,
models: relatedModels,
};
}
/**
@@ -419,8 +451,8 @@ export class MockServer {
return this.mockCreate(args.model, args.args[0]);
case "fields_get":
return this.mockFieldsGet(args.model);
case "load_views":
return this.mockLoadViews(args.model, args.kwargs);
case "get_views":
return this.mockGetViews(args.model, args.kwargs);
case "name_create":
return this.mockNameCreate(args.model, args.args[0]);
case "name_get":
@@ -536,19 +568,20 @@ export class MockServer {
return menus;
}
mockLoadViews(modelName, kwargs) {
const fieldsViews = {};
mockGetViews(modelName, kwargs) {
const views = {};
const models = {};
models[modelName] = this.mockFieldsGet(modelName);
kwargs.views.forEach(([viewId, viewType]) => {
fieldsViews[viewType] = this.fieldsViewGet(modelName, [viewId, viewType], kwargs);
views[viewType] = this.getView(modelName, [viewId, viewType], kwargs);
if (kwargs.options.load_filters && viewType === "search") {
views[viewType].filters = this.models[modelName].filters || [];
}
for (const modelName of views[viewType].models) {
models[modelName] = models[modelName] || this.mockFieldsGet(modelName);
}
});
const result = {
fields: this.mockFieldsGet(modelName),
fields_views: fieldsViews,
};
if (kwargs.options.load_filters) {
result.filters = this.models[modelName].filters || [];
}
return result;
return { models, views };
}
/**
@@ -446,7 +446,7 @@ QUnit.module('fields', {}, function () {
},
res_id: 1,
mockRPC: function (route, args) {
if (args.method !== 'load_views') {
if (args.method !== 'get_views') {
assert.step(_.last(route.split('/')));
}
if (args.method === 'write' && args.model === 'partner') {
@@ -550,7 +550,7 @@ QUnit.module('fields', {}, function () {
'partner_type,false,search': '<search><field name="display_name"/></search>',
},
mockRPC: function (route, args) {
if (args.method !== 'load_views') {
if (args.method !== 'get_views') {
assert.step(_.last(route.split('/')));
}
if (args.method === 'write') {
@@ -1139,7 +1139,7 @@ QUnit.module('fields', {}, function () {
'</search>',
},
mockRPC: function (route, args) {
if (args.method !== 'load_views') {
if (args.method !== 'get_views') {
assert.step(_.last(route.split('/')) + ' on ' + args.model);
}
if (args.model === 'turtle') {
@@ -1264,7 +1264,7 @@ QUnit.module('fields', {}, function () {
assert.verifySteps([
'read', // read initial record (on partner)
'read', // read many2many turtles
'load_views', // load arch of turtles form view
'get_views', // load arch of turtles form view
'read', // read missing field when opening record in modal form view
'write', // when saving the modal
'onchange', // onchange should be triggered on partner
@@ -629,7 +629,7 @@ QUnit.module('fields', {}, function () {
// save the modal and make sure an onchange is triggered
await testUtils.dom.click($('.modal .modal-footer .btn-primary').first());
assert.verifySteps(['read', 'get_formview_id', 'load_views', 'read', 'write', 'read', 'onchange']);
assert.verifySteps(['read', 'get_formview_id', 'get_views', 'read', 'write', 'read', 'onchange']);
// save the main record, and check that no extra rpcs are done (record
// is not dirty, only a related record was modified)
@@ -3117,7 +3117,7 @@ QUnit.module('fields', {}, function () {
assert.verifySteps([
'onchange',
'name_search', // to display results in the dropdown
'load_views', // list view in dialog
'get_views', // list view in dialog
'/web/dataset/search_read', // to display results in the dialog
]);
@@ -3173,7 +3173,7 @@ QUnit.module('fields', {}, function () {
'name_search', // empty search, triggered when the user clicks in the input
'name_search', // to display results in the dropdown
'name_search', // to get preselected ids matching the search
'load_views', // list view in dialog
'get_views', // list view in dialog
'/web/dataset/search_read', // to display results in the dialog
'/web/dataset/search_read', // after removal of dynamic filter
]);
@@ -3339,7 +3339,7 @@ QUnit.module('fields', {}, function () {
assert.verifySteps([
'onchange',
'name_search', // to display results in the dropdown
'load_views', // list view in dialog
'get_views', // list view in dialog
'/web/dataset/search_read', // to display results in the dialog
'/web/dataset/resequence', // resequencing lines
'read',
@@ -6233,7 +6233,7 @@ QUnit.module('fields', {}, function () {
await testUtils.owlCompatibilityExtraNextTick();
assert.strictEqual(form.$('.o_field_widget[name="int_field"]').val(), "0",
"int_field should still be 0 (no onchange should have been done yet)");
assert.verifySteps(['load_views', 'read', 'onchange']);
assert.verifySteps(['get_views', 'read', 'onchange']);
// fill turtle_foo field
await testUtils.fields.editInput(form.$('.o_field_widget[name="turtle_foo"]'), "some text");
@@ -7964,7 +7964,7 @@ QUnit.module('fields', {}, function () {
// swap 2 lines in the one2many
await testUtils.dom.dragAndDrop(form.$('.ui-sortable-handle:eq(1)'), form.$('tbody tr').first(),
{ position: 'top' });
assert.verifySteps(['load_views', 'read', 'read', 'onchange', 'onchange']);
assert.verifySteps(['get_views', 'read', 'read', 'onchange', 'onchange']);
form.destroy();
});
@@ -8004,7 +8004,7 @@ QUnit.module('fields', {}, function () {
"should have triggered the onchange");
assert.verifySteps([
'load_views', // load sub list
'get_views', // load sub list
'onchange', // main record
'onchange', // sub record
'onchange', // edition of display_name of sub record
@@ -79,7 +79,7 @@ var MockServer = Class.extend({
},
/**
* helper: read a string describing an arch, and returns a simulated
* 'field_view_get' call to the server. Calls processViews() of data_manager
* 'view_get' call to the server. Calls processViews() of data_manager
* to mimick the real behavior of a call to loadViews().
*
* @param {Object} params
@@ -90,7 +90,7 @@ var MockServer = Class.extend({
* @param {Object} [params.viewOptions] the view options set in the test (optional)
* @returns {Object} an object with 2 keys: arch and fields
*/
fieldsViewGet: function (params) {
getView: function (params) {
var model = params.model;
var toolbar = params.toolbar;
var viewId = params.view_id;
@@ -99,14 +99,14 @@ var MockServer = Class.extend({
throw new Error('Model ' + model + ' was not defined in mock server data');
}
var fields = $.extend(true, {}, this.data[model].fields);
var fvg = this._fieldsViewGet(params.arch, model, fields, viewOptions.context || {});
var view = this._getView(params.arch, model, fields, viewOptions.context || {});
if (toolbar) {
fvg.toolbar = toolbar;
view.toolbar = toolbar;
}
if (viewId) {
fvg.view_id = viewId;
view.id = viewId;
}
return fvg;
return view;
},
/**
* Simulates a complete fetch call.
@@ -266,7 +266,7 @@ var MockServer = Class.extend({
},
/**
* helper: read a string describing an arch, and returns a simulated
* 'fields_view_get' call to the server.
* 'get_view' call to the server.
*
* @private
* @param {string} arch a string OR a parsed xml document
@@ -276,12 +276,13 @@ var MockServer = Class.extend({
* @returns {Object} an object with 2 keys: arch and fields (the fields
* appearing in the views)
*/
_fieldsViewGet: function (arch, model, fields, context) {
_getView: function (arch, model, fields, context) {
var self = this;
var modifiersNames = ['invisible', 'readonly', 'required'];
var onchanges = this.data[model].onchanges || {};
var fieldNodes = {};
var groupbyNodes = {};
const relatedModels = new Set([model]);
var doc;
if (typeof arch === 'string') {
@@ -394,7 +395,7 @@ var MockServer = Class.extend({
return !isField;
});
var relModel, relFields;
let relModel, relFields;
_.each(fieldNodes, function (node, name) {
var field = fields[name];
if (field.type === "many2one" || field.type === "many2many") {
@@ -404,13 +405,16 @@ var MockServer = Class.extend({
node.setAttribute('can_write', canWrite || "true");
}
if (field.type === "one2many" || field.type === "many2many") {
field.views = {};
_.each(node.childNodes, function (children) {
if (children.tagName) { // skip text nodes
relModel = field.relation;
relModel = field.relation;
relatedModels.add(relModel);
_.each(node.childNodes, function (childNode) {
if (childNode.tagName) { // skip text nodes
relFields = $.extend(true, {}, self.data[relModel].fields);
field.views[children.tagName] = self._fieldsViewGet(children, relModel,
// this is hackhish, but _getView modifies the subview document in place,
// especially to generate the "modifiers" attribute
const { models } = self._getView(childNode, relModel,
relFields, _.extend({}, context, {base_model_name: model}));
[...models].forEach((modelName) => relatedModels.add(modelName));
}
});
}
@@ -427,22 +431,21 @@ var MockServer = Class.extend({
}
field.views = {};
relModel = field.relation;
relatedModels.add(relModel);
relFields = $.extend(true, {}, self.data[relModel].fields);
node._isProcessed = true;
// postprocess simulation
field.views.groupby = self._fieldsViewGet(node, relModel, relFields, context);
while (node.firstChild) {
node.removeChild(node.firstChild);
}
const { models } = self._getView(node, relModel, relFields, context);
[...models].forEach((modelName) => relatedModels.add(modelName));
});
var xmlSerializer = new XMLSerializer();
var processedArch = xmlSerializer.serializeToString(doc);
return {
arch: processedArch,
fields: _.pick(fields, _.keys(fieldNodes)),
model: model,
type: doc.tagName === 'tree' ? 'list' : doc.tagName,
models: relatedModels,
};
},
/**
@@ -1180,7 +1183,7 @@ var MockServer = Class.extend({
return action || false;
},
/**
* Simulate a 'load_views' operation
* Simulate a 'get_views' operation
*
* @param {string} model
* @param {Array} args
@@ -1190,7 +1193,7 @@ var MockServer = Class.extend({
* @param {Object} kwargs.context
* @returns {Object}
*/
_mockLoadViews: function (model, kwargs) {
_mockGetViews: function (model, kwargs) {
var self = this;
var views = {};
_.each(kwargs.views, function (view_descr) {
@@ -1199,7 +1202,7 @@ var MockServer = Class.extend({
if (!viewID) {
var contextKey = (viewType === 'list' ? 'tree' : viewType) + '_view_ref';
if (contextKey in kwargs.context) {
viewID = kwargs.context[contextKey];
viewID = parseInt(kwargs.context[contextKey]);
}
}
var key = [model, viewID, viewType].join(',');
@@ -1989,8 +1992,8 @@ var MockServer = Class.extend({
case 'search_panel_select_multi_range':
return this._mockSearchPanelSelectMultiRange(args.model, args.args, args.kwargs);
case 'load_views':
return this._mockLoadViews(args.model, args.kwargs);
case 'get_views':
return this._mockGetViews(args.model, args.kwargs);
case 'name_get':
return this._mockNameGet(args.model, args.args);
@@ -141,7 +141,7 @@ odoo.define('web.test_utils', async function (require) {
patch: testUtilsMock.patch,
patchDate: testUtilsMock.patchDate,
unpatch: testUtilsMock.unpatch,
fieldsViewGet: testUtilsMock.fieldsViewGet,
getView: testUtilsMock.getView,
patchSetTimeout: testUtilsMock.patchSetTimeout,
},
controlPanel: {
@@ -269,7 +269,7 @@ odoo.define('web.test_utils', async function (require) {
// backward-compatibility
addMockEnvironment: deprecated(testUtilsMock.addMockEnvironment, 'mock'),
dragAndDrop: deprecated(testUtilsDom.dragAndDrop, 'dom'),
fieldsViewGet: deprecated(testUtilsMock.fieldsViewGet, 'mock'),
getView: deprecated(testUtilsMock.getView, 'mock'),
intercept: deprecated(testUtilsMock.intercept, 'mock'),
openDatepicker: deprecated(testUtilsDom.openDatepicker, 'dom'),
patch: deprecated(testUtilsMock.patch, 'mock'),
@@ -142,13 +142,13 @@ odoo.define('web.test_utils_create', function (require) {
new Widget(),
serverParams,
);
const { arch, fields } = testUtilsMock.fieldsViewGet(mockServer, {
const { arch } = testUtilsMock.getView(mockServer, {
arch: globalConfig.arch,
fields: globalConfig.fields,
model,
viewOptions: { context: globalConfig.context },
});
Object.assign(globalConfig, { arch, fields });
Object.assign(globalConfig, { arch });
}
globalConfig.env = env;
@@ -283,7 +283,7 @@ odoo.define('web.test_utils_create', function (require) {
// add mock environment: mock server, session, fieldviewget, ...
const mockServer = await testUtilsMock.addMockEnvironment(widget, params);
const viewInfo = testUtilsMock.fieldsViewGet(mockServer, params);
const viewInfo = testUtilsMock.getView(mockServer, params);
params.server = mockServer;
@@ -297,7 +297,7 @@ odoo.define('web.test_utils_create', function (require) {
};
const viewOptions = Object.assign({
action: Object.assign(defaultAction, params.action),
view: viewInfo,
view: { ...viewInfo, fields: mockServer.fieldsGet(params.model) },
modelName: modelName,
ids: 'res_id' in params ? [params.res_id] : undefined,
currentId: 'res_id' in params ? params.res_id : undefined,
@@ -323,7 +323,7 @@ odoo.define('web.test_utils_create', function (require) {
// TODO: probably needs to create an helper just for that
view = new params.View({ viewInfo, modelName });
} else {
viewOptions.controlPanelFieldsView = Object.assign(testUtilsMock.fieldsViewGet(mockServer, {
viewOptions.controlPanelFieldsView = Object.assign(testUtilsMock.getView(mockServer, {
arch: params.archs && params.archs[params.model + ',false,search'] || '<search/>',
fields: viewInfo.fields,
model: params.model,
@@ -23,6 +23,7 @@ const MockServer = require('web.MockServer');
const RamStorage = require('web.RamStorage');
const session = require('web.session');
const { patchDate } = require("@web/../tests/helpers/utils");
const { processArch } = require("@web/legacy/legacy_load_views");
const { Component } = owl;
const DebouncedField = basic_fields.DebouncedField;
@@ -80,11 +81,11 @@ async function _getMockedOwlEnv(params, mockServer) {
options: options,
views: params.views_descr,
},
method: 'load_views',
method: 'get_views',
model: params.model,
}).then(function (views) {
views = _.mapObject(views, viewParams => {
return fieldsViewGet(mockServer, viewParams);
return getView(mockServer, viewParams);
});
if (favoriteFilters && 'search' in views) {
views.search.favoriteFilters = favoriteFilters;
@@ -247,7 +248,7 @@ function _observe(widget) {
//------------------------------------------------------------------------------
/**
* performs a fields_view_get, and mocks the postprocessing done by the
* performs a get_view, and mocks the postprocessing done by the
* data_manager to return an equivalent structure.
*
* @param {MockServer} server
@@ -255,12 +256,24 @@ function _observe(widget) {
* @param {string} params.model
* @returns {Object} an object with 3 keys: arch, fields and viewFields
*/
function fieldsViewGet(server, params) {
var fieldsView = server.fieldsViewGet(params);
function getView(server, params) {
var view = server.getView(params);
const fields = server.fieldsGet(params.model);
// mock the structure produced by the DataManager
fieldsView.viewFields = fieldsView.fields;
fieldsView.fields = server.fieldsGet(params.model);
return fieldsView;
const models = { [params.model]: fields };
for (const modelName of view.models) {
models[modelName] = models[modelName] || server.fieldsGet(modelName);
}
const { arch, viewFields } = processArch(view.arch, view.type, params.model, models);
return {
arch,
fields,
model: view.model,
toolbar: view.toolbar,
type: view.type,
viewFields,
view_id: view.id,
};
}
/**
@@ -681,7 +694,7 @@ function patchSetTimeout() {
return {
addMockEnvironment: addMockEnvironment,
fieldsViewGet: fieldsViewGet,
getView: getView,
addMockEnvironmentOwl: addMockEnvironmentOwl,
intercept: intercept,
patchDate: legacyPatchDate,
@@ -23,17 +23,20 @@ odoo.define('web.data_manager_tests', function (require) {
const server = new MockServer(data, { archs });
const serverMethods = {
async load_views({ kwargs, model }) {
const { options, views } = kwargs;
const fields = server.fieldsGet(model);
const fields_views = {};
for (const [viewId, viewType] of views) {
async get_views({ kwargs, model }) {
const models = {};
models[model] = server.fieldsGet(model);
const views = {};
for (const [viewId, viewType] of kwargs.views) {
const arch = archs[[model, viewId || false, viewType].join()];
fields_views[viewType] = server.fieldsViewGet({ arch, model, viewId });
views[viewType] = server.getView({ arch, model, viewId });
for (const modelName of views[viewType].models) {
models[modelName] = server.fieldsGet(modelName);
}
}
const result = { fields, fields_views };
if (options.load_filters) {
result.filters = data['ir.filters'].records.filter(r => r.model_id === model);
const result = { models, views };
if (kwargs.options.load_filters && views.search) {
views.search.filters = data['ir.filters'].records.filter(r => r.model_id === model);
}
return result;
},
@@ -133,7 +136,7 @@ odoo.define('web.data_manager_tests', function (require) {
"query with same params and options should yield the same results");
assert.deepEqual(firstLoad.search.favoriteFilters, filters,
"load filters should yield the same result as the first load_views' filters");
assert.verifySteps(['oui.load_views'],
assert.verifySteps(['oui.get_views'],
"only load once when not in assets debugging");
});
@@ -165,8 +168,8 @@ odoo.define('web.data_manager_tests', function (require) {
assert.deepEqual(firstLoad.search.favoriteFilters, filters,
"load filters should yield the same result as the first load_views' filters");
assert.verifySteps([
'oui.load_views',
'oui.load_views',
'oui.get_views',
'oui.get_views',
'ir.filters.get_filters',
], "reload each time when in assets debugging");
});
@@ -228,7 +231,7 @@ odoo.define('web.data_manager_tests', function (require) {
"Filters loaded by the load_views should be equal to the result of a load_filters");
assert.verifySteps([
'oui.load_views',
'oui.get_views',
'ir.filters.create_or_replace',
'ir.filters.get_filters',
'ir.filters.unlink',
@@ -154,7 +154,7 @@ odoo.define('web.basic_model_tests', function (require) {
});
assert.verifySteps([
'load_views',
'get_views',
'onchange',
]);
assert.containsOnce(form, '.o_field_x2many_list', 'should have rendered a x2many list');
@@ -3295,7 +3295,7 @@ QUnit.module('Views', {
initialDate: initialDate,
},
mockRPC: function (route, args) {
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.strictEqual(args.kwargs.views[0][0], 1,
"should load view with id 1");
}
@@ -521,10 +521,14 @@ QUnit.module('Views', {
serverData.models.product.records = [{id: 1, name: 'Tromblon', partner_type_ids: [12,14]}];
serverData.models.partner.records[0].product_id = 1;
// This is an old test, written before "get_views" (formerly "load_views") automatically
// inlines x2many subviews. As the purpose of this test is to assert that the js fetches
// the correct sub view when it is not inline (which can still happen in nested form views),
// we bypass the inline mecanism of "get_views" by setting widget="one2many" on the field.
serverData.views = {
'product,false,form': '<form>'+
'<field name="name"/>'+
'<field name="partner_type_ids" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' +
'<field name="partner_type_ids" widget="one2many" context="{\'tree_view_ref\': \'some_other_tree_view\'}"/>' +
'</form>',
'partner_type,false,list': '<tree>'+
@@ -539,7 +543,7 @@ QUnit.module('Views', {
};
const mockRPC = (route, args) => {
if (args.method === 'load_views') {
if (args.method === 'get_views') {
var context = args.kwargs.context;
if (args.model === 'product') {
assert.strictEqual(context.tree_view_ref, 'some_tree_view',
@@ -6192,7 +6196,7 @@ QUnit.module('Views', {
"the row should contains the 2 fields defined in the form view");
assert.strictEqual($(modal_row).text(), "gold2",
"the value of the fields should be fetched and displayed");
assert.verifySteps(['read', 'read', 'load_views', 'read', 'read'],
assert.verifySteps(['read', 'read', 'get_views', 'read', 'read'],
"there should be 4 read rpcs");
form.destroy();
});
@@ -8222,10 +8226,10 @@ QUnit.module('Views', {
assert.verifySteps([
"read", // main record
"get_formview_id", // id of first form view opened in a dialog
"load_views", // arch of first form view opened in a dialog
"get_views", // arch of first form view opened in a dialog
"read", // first dialog
"get_formview_id", // id of second form view opened in a dialog
"load_views", // arch of second form view opened in a dialog
"get_views", // arch of second form view opened in a dialog
"read", // second dialog
"write", // save second dialog
"read", // reload first dialog
@@ -8799,7 +8803,7 @@ QUnit.module('Views', {
}
});
assert.verifySteps(['partner_type:load_views', 'partner:read', 'partner_type:read']);
assert.verifySteps(['partner_type:get_views', 'partner:read', 'partner_type:read']);
form.destroy();
});
@@ -745,11 +745,11 @@ QUnit.module('Views', {
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'load_views', // form view in quick create
'get_views', // form view in quick create
'onchange', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'load_views', // form view in quick create (is actually in cache)
'get_views', // form view in quick create (is actually in cache)
'onchange', // reopen the quick create automatically
]);
@@ -870,11 +870,11 @@ QUnit.module('Views', {
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'load_views', // form view in quick create
'get_views', // form view in quick create
'onchange', // quick create
'create', // should perform a create to create the record
'read', // read the created record
'load_views', // form view in quick create (is actually in cache)
'get_views', // form view in quick create (is actually in cache)
'onchange', // reopen the quick create automatically
]);
@@ -934,7 +934,7 @@ QUnit.module('Views', {
'web_read_group', // initial read_group
'/web/dataset/search_read', // initial search_read (first column)
'/web/dataset/search_read', // initial search_read (second column)
'load_views', // form view in quick create
'get_views', // form view in quick create
'onchange', // quick create
'onchange', // onchange due to 'foo' field change
]);
@@ -2600,7 +2600,7 @@ QUnit.module('Views', {
groupBy: ['product_id'],
async mockRPC(route, args) {
const result = this._super(...arguments);
if (args.method === 'load_views') {
if (args.method === 'get_views') {
await def;
}
return result;
@@ -1060,7 +1060,7 @@ QUnit.module("Search", (hooks) => {
promise.resolve();
await compPromise;
assert.verifySteps(["load_views", "search_panel_select_range"]);
assert.verifySteps(["get_views", "search_panel_select_range"]);
// Case 2: search domain changed so we wait for the search panel once again
promise = makeDeferred();
@@ -1123,7 +1123,7 @@ QUnit.module("Search", (hooks) => {
await compPromise;
assert.verifySteps([
"load_views",
"get_views",
"search_panel_select_range",
"search_panel_select_multi_range",
]);
@@ -1160,7 +1160,7 @@ QUnit.module("Search", (hooks) => {
await compPromise;
assert.verifySteps([
"load_views",
"get_views",
"search_panel_select_range",
"search_panel_select_multi_range",
]);
@@ -137,8 +137,8 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
mockRPC: function (_, args) {
if (args.method === "load_views") {
throw new Error("No load_views should be done");
if (args.method === "get_views") {
throw new Error("No get_views should be done");
}
},
resModel: "animal",
@@ -159,7 +159,7 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
mockRPC: function (_, args) {
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.deepEqual(args.kwargs, {
context: {
lang: "en",
@@ -193,8 +193,8 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
mockRPC: function (_, args) {
if (args.method === "load_views") {
throw new Error("No load_views should be done");
if (args.method === "get_views") {
throw new Error("No get_views should be done");
}
},
resModel: "animal",
@@ -218,7 +218,7 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
mockRPC: function (_, args) {
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.deepEqual(args.kwargs.options, {
action_id: false,
load_filters: true,
@@ -253,7 +253,7 @@ QUnit.module("Search", (hooks) => {
await makeWithSearch({
serverData,
mockRPC: function (_, args) {
if (args.method === "load_views") {
if (args.method === "get_views") {
assert.deepEqual(args.kwargs.views, [[1, "search"]]);
}
},
+3 -4
View File
@@ -3,7 +3,7 @@
import { makeTestEnv } from "@web/../tests/helpers/mock_env";
import { getFixture, mount } from "@web/../tests/helpers/utils";
import { getDefaultConfig, View } from "@web/views/view";
import { _fieldsViewGet } from "../helpers/mock_server";
import { _getView } from "../helpers/mock_server";
import { addLegacyMockEnvironment } from "../webclient/helpers";
/**
@@ -46,15 +46,14 @@ export const makeView = async (params) => {
props.fields[fieldName].name = fieldName;
}
}
const fvg = _fieldsViewGet({
const view = _getView({
arch: props.arch,
modelName: props.resModel,
fields: props.fields,
context: props.context || {},
models: serverData.models,
});
props.arch = fvg.arch;
props.fields = Object.assign({}, props.fields, fvg.fields);
props.arch = view.arch;
props.searchViewArch = props.searchViewArch || "<search/>";
props.searchViewFields = props.searchViewFields || Object.assign({}, props.fields);
}
@@ -4780,7 +4780,7 @@ QUnit.module("Views", (hooks) => {
assert.containsOnce(target, ".o_pivot_view");
assert.strictEqual(getCurrentValues(target), ["4", "2", "2"].join(","));
assert.verifySteps(["/web/webclient/load_menus", "load_views", "read_group", "read_group"]);
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read_group", "read_group"]);
// switch to list view
await click(target.querySelector(".o_control_panel .o_switch_view.o_list"));
@@ -20,16 +20,29 @@ QUnit.module("View service", (hooks) => {
},
};
const fakeUiService = {
start(env) {
Object.defineProperty(env, "isSmall", {
get() {
return false;
},
});
},
};
serverData = { models, views };
registry.category("services").add("views", viewService).add("orm", ormService);
registry
.category("services")
.add("views", viewService)
.add("orm", ormService)
.add("ui", fakeUiService);
});
QUnit.test("stores calls in cache in success", async (assert) => {
assert.expect(2);
const mockRPC = (route, args) => {
if (route.includes("load_views")) {
assert.step("load_views");
if (route.includes("get_views")) {
assert.step("get_views");
}
};
@@ -51,15 +64,15 @@ QUnit.module("View service", (hooks) => {
{}
);
assert.verifySteps(["load_views"]);
assert.verifySteps(["get_views"]);
});
QUnit.test("stores calls in cache when failed", async (assert) => {
assert.expect(5);
const mockRPC = (route, args) => {
if (route.includes("load_views")) {
assert.step("load_views");
if (route.includes("get_views")) {
assert.step("get_views");
return Promise.reject("my little error");
}
};
@@ -91,6 +104,6 @@ QUnit.module("View service", (hooks) => {
assert.strictEqual(error, "my little error");
}
assert.verifySteps(["load_views", "load_views"]);
assert.verifySteps(["get_views", "get_views"]);
});
});
+2 -2
View File
@@ -110,7 +110,7 @@ QUnit.module("Views", (hooks) => {
QUnit.module("View component");
////////////////////////////////////////////////////////////////////////////
// load_views
// get_views
////////////////////////////////////////////////////////////////////////////
QUnit.test("simple rendering", async function (assert) {
@@ -132,7 +132,7 @@ QUnit.module("Views", (hooks) => {
serverData,
mockRPC: (_, args) => {
assert.strictEqual(args.model, "animal");
assert.strictEqual(args.method, "load_views");
assert.strictEqual(args.method, "get_views");
assert.deepEqual(args.kwargs.views, [[false, "toy"]]);
assert.deepEqual(args.kwargs.options, {
action_id: false,
@@ -49,7 +49,7 @@ QUnit.module("ActionManager", (hooks) => {
"/web/webclient/load_menus",
"/web/action/load",
"/web/action/load",
"/web/dataset/call_kw/pony/load_views",
"/web/dataset/call_kw/pony/get_views",
"/web/dataset/search_read",
]);
});
@@ -80,7 +80,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/search_read",
"/web/dataset/search_read",
"/web/dataset/search_read",
@@ -187,11 +187,11 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"read",
"/web/dataset/search_read",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
// unblock the switch to Kanban in action 4
@@ -238,12 +238,12 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"object",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
// unblock the call_button request
@@ -302,11 +302,11 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
// unblock the switch to the form view in action 3
@@ -332,12 +332,12 @@ QUnit.module("ActionManager", (hooks) => {
const def = testUtils.makeTestPromise();
const mockRPC = async function (route, args) {
assert.step((args && args.method) || route);
if (args && args.method === "load_views") {
if (args && args.method === "get_views") {
await def;
}
};
const webClient = await createWebClient({ serverData, mockRPC });
// execute a first action (its 'load_views' RPC is blocked)
// execute a first action (its 'get_views' RPC is blocked)
doAction(webClient, 3);
await testUtils.nextTick();
await legacyExtraNextTick();
@@ -358,9 +358,9 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -395,10 +395,10 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -499,7 +499,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"/web/action/load",
@@ -537,19 +537,19 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/action/load",
"/web/dataset/search_read",
]);
});
QUnit.test("switching when doing an action -- load_views slow", async function (assert) {
QUnit.test("switching when doing an action -- get_views slow", async function (assert) {
assert.expect(13);
let def;
const mockRPC = async (route, args) => {
assert.step((args && args.method) || route);
if (args && args.method === "load_views") {
if (args && args.method === "get_views") {
return Promise.resolve(def);
}
};
@@ -574,10 +574,10 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -611,10 +611,10 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/dataset/search_read",
]);
@@ -216,7 +216,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -239,7 +239,7 @@ QUnit.module("ActionManager", (hooks) => {
"Second record",
"should have opened the second record"
);
assert.verifySteps(["/web/webclient/load_menus", "load_views", "read"]);
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read"]);
});
QUnit.test("properly load records with existing first APP", async function (assert) {
@@ -266,7 +266,7 @@ QUnit.module("ActionManager", (hooks) => {
"should have opened the second record"
);
assert.containsNone(target, ".o_menu_brand");
assert.verifySteps(["/web/webclient/load_menus", "load_views", "read"]);
assert.verifySteps(["/web/webclient/load_menus", "get_views", "read"]);
});
QUnit.test("properly load default record", async function (assert) {
@@ -287,7 +287,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"onchange",
]);
});
@@ -309,7 +309,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -345,7 +345,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"read",
"/web/dataset/search_read",
]);
@@ -458,12 +458,12 @@ QUnit.module("ActionManager", (hooks) => {
"should have opened the requested record"
);
// verify steps to ensure that the whole action hasn't been re-executed
// (if it would have been, /web/action/load and load_views would appear
// (if it would have been, /web/action/load and get_views would appear
// several times)
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/dataset/search_read",
"read",
@@ -507,12 +507,12 @@ QUnit.module("ActionManager", (hooks) => {
"should have switched to the requested record"
);
// verify steps to ensure that the whole action hasn't been re-executed
// (if it would have been, /web/action/load and load_views would appear
// (if it would have been, /web/action/load and get_views would appear
// twice)
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"read",
@@ -717,7 +717,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"setItem session current_action",
"getItem session current_action",
@@ -749,7 +749,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/search_read",
]);
});
@@ -799,7 +799,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/search_read",
]);
});
@@ -830,7 +830,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
await loadState(webClient, {
@@ -311,7 +311,7 @@ QUnit.module("ActionManager", (hooks) => {
},
});
const mockRPC = async function (route, args) {
if (args && args.method === "load_views") {
if (args && args.method === "get_views") {
await Promise.resolve(def);
}
};
@@ -319,7 +319,7 @@ QUnit.module("ActionManager", (hooks) => {
// execute action 4 to know the number of widgets it instantiates
await doAction(webClient, 4);
const n = delta;
// execute a first action (its 'load_views' RPC is blocked)
// execute a first action (its 'get_views' RPC is blocked)
def = testUtils.makeTestPromise();
doAction(webClient, 3, { clearBreadcrumbs: true });
await testUtils.nextTick();
@@ -32,7 +32,7 @@ QUnit.module("ActionManager", (hooks) => {
"/web/action/load",
"/web/action/run",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -62,7 +62,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"onchange",
"/web/action/load",
"/web/action/run",
@@ -56,7 +56,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"onchange",
]);
});
@@ -153,7 +153,7 @@ QUnit.module("ActionManager", (hooks) => {
await doAction(webClient, 4);
assert.verifySteps([
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/onchange",
]);
await testUtils.dom.click(`button[name="5"]`);
@@ -161,7 +161,7 @@ QUnit.module("ActionManager", (hooks) => {
"/web/dataset/call_kw/partner/create",
"/web/dataset/call_kw/partner/read",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/onchange",
]);
await legacyExtraNextTick();
@@ -471,7 +471,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"read",
]);
}
@@ -55,7 +55,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -66,7 +66,7 @@ QUnit.module("ActionManager", (hooks) => {
print: [{ name: "Print that record" }],
};
const mockRPC = async (route, args) => {
if (args && args.method === "load_views") {
if (args && args.method === "get_views") {
assert.strictEqual(
args.kwargs.options.toolbar,
true,
@@ -122,7 +122,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"/web/dataset/search_read",
"/web/dataset/search_read",
@@ -176,7 +176,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"web_read_group",
"/web/dataset/search_read",
"/web/dataset/search_read",
@@ -679,7 +679,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"onchange",
"/web/dataset/search_read",
@@ -737,7 +737,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"object",
@@ -884,11 +884,11 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -935,10 +935,10 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/search_read",
]);
});
@@ -1050,7 +1050,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"read",
"/web/dataset/search_read",
@@ -1191,7 +1191,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/search_read",
"/web/dataset/call_kw/partner/read",
"/web/dataset/call_kw/partner/get_formview_id",
@@ -1416,7 +1416,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
]);
});
@@ -1449,7 +1449,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"onchange",
]);
});
@@ -1702,12 +1702,12 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"load_views",
"get_views",
"/web/dataset/search_read",
"onchange",
"get_formview_action",
"create", // FIXME: to check with mcm
"load_views",
"get_views",
"read",
"read",
]);
@@ -1722,7 +1722,7 @@ QUnit.module("ActionManager", (hooks) => {
print: [],
};
const mockRPC = async (route, args) => {
if (args && args.method === "load_views" && args.model === "partner") {
if (args && args.method === "get_views" && args.model === "partner") {
assert.strictEqual(
args.kwargs.options.toolbar,
true,
@@ -1992,7 +1992,7 @@ QUnit.module("ActionManager", (hooks) => {
assert.verifySteps([
"/web/webclient/load_menus",
"/web/action/load",
"/web/dataset/call_kw/partner/load_views",
"/web/dataset/call_kw/partner/get_views",
"/web/dataset/call_kw/partner/read",
]);
assert.containsN(target, ".o_form_buttons_view button:not([disabled])", 2);
@@ -2365,7 +2365,7 @@ QUnit.module("ActionManager", (hooks) => {
);
});
QUnit.test("action and load_views rpcs are cached", async function (assert) {
QUnit.test("action and get_views rpcs are cached", async function (assert) {
const mockRPC = async (route, args) => {
assert.step(args.method || route);
};
@@ -2374,7 +2374,7 @@ QUnit.module("ActionManager", (hooks) => {
await doAction(webClient, 1);
assert.containsOnce(target, ".o_kanban_view");
assert.verifySteps(["/web/action/load", "load_views", "/web/dataset/search_read"]);
assert.verifySteps(["/web/action/load", "get_views", "/web/dataset/search_read"]);
await doAction(webClient, 1);
assert.containsOnce(target, ".o_kanban_view");
+5 -2
View File
@@ -12,6 +12,7 @@ import {
makeLegacySessionService,
} from "@web/legacy/utils";
import { makeLegacyActionManagerService } from "@web/legacy/backend_utils";
import { generateLegacyLoadViewsResult } from "@web/legacy/legacy_load_views";
import { viewService } from "@web/views/view_service";
import { actionService } from "@web/webclient/actions/action_service";
import { effectService } from "@web/core/effects/effect_service";
@@ -118,16 +119,18 @@ export function addLegacyMockEnvironment(env, legacyParams = {}) {
});
},
load_views: async (params, options) => {
const result = await env.services.rpc(`/web/dataset/call_kw/${params.model}`, {
let result = await env.services.rpc(`/web/dataset/call_kw/${params.model}`, {
args: [],
kwargs: {
context: params.context,
options: options,
views: params.views_descr,
},
method: "load_views",
method: "get_views",
model: params.model,
});
const { models, views: _views } = result;
result = generateLegacyLoadViewsResult(params.model, _views, models);
const views = result.fields_views;
for (const [, viewType] of params.views_descr) {
const fvg = views[viewType];
@@ -206,11 +206,11 @@ QUnit.module('base_settings_tests', {
await legacyExtraNextTick();
assert.hasClass($(target).find(".o_form_view"), "o_form_editable");
assert.verifySteps([
"load_views", // initial setting action
"get_views", // initial setting action
"onchange", // this is a setting view => create new record
"create", // when we click on action button => save
"read", // with save, we have a reload... (not necessary actually)
"load_views", // for other action in breadcrumb,
"get_views", // for other action in breadcrumb,
// with a searchread (not shown here since it is a route)
"onchange", // when we come back, we want to restart from scratch
]);