diff --git a/addons/board/static/tests/dashboard_tests.js b/addons/board/static/tests/dashboard_tests.js
index 4e62f5adab7..43a0d72e9d8 100644
--- a/addons/board/static/tests/dashboard_tests.js
+++ b/addons/board/static/tests/dashboard_tests.js
@@ -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) {
"" +
"",
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) {
"" +
"",
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" },
diff --git a/addons/mail/__manifest__.py b/addons/mail/__manifest__.py
index cab0cf777ef..c3d31734f01 100644
--- a/addons/mail/__manifest__.py
+++ b/addons/mail/__manifest__.py
@@ -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',
diff --git a/addons/test_mail/static/tests/activity_tests.js b/addons/test_mail/static/tests/activity_tests.js
index 02fb895f1f2..5bad8e284f1 100644
--- a/addons/test_mail/static/tests/activity_tests.js
+++ b/addons/test_mail/static/tests/activity_tests.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));
}
}
diff --git a/addons/web/__manifest__.py b/addons/web/__manifest__.py
index 9e87df8b27b..342045a3f85 100644
--- a/addons/web/__manifest__.py
+++ b/addons/web/__manifest__.py
@@ -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',
diff --git a/addons/web/static/src/legacy/js/services/data_manager.js b/addons/web/static/src/legacy/js/services/data_manager.js
index 2a8fc5b425b..bf7c2b82a56 100644
--- a/addons/web/static/src/legacy/js/services/data_manager.js
+++ b/addons/web/static/src/legacy/js/services/data_manager.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;
diff --git a/addons/web/static/src/legacy/legacy_load_views.js b/addons/web/static/src/legacy/legacy_load_views.js
new file mode 100644
index 00000000000..fa50c822799
--- /dev/null
+++ b/addons/web/static/src/legacy/legacy_load_views.js
@@ -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 };
+}
diff --git a/addons/web/static/src/legacy/legacy_views.js b/addons/web/static/src/legacy/legacy_views.js
index e2f0e4d5994..b8c167c87e6 100644
--- a/addons/web/static/src/legacy/legacy_views.js
+++ b/addons/web/static/src/legacy/legacy_views.js
@@ -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) {
diff --git a/addons/web/static/src/search/search_arch_parser.js b/addons/web/static/src/search/search_arch_parser.js
index 9e71ba747f4..09bdb370079 100644
--- a/addons/web/static/src/search/search_arch_parser.js
+++ b/addons/web/static/src/search/search_arch_parser.js
@@ -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 || [];
diff --git a/addons/web/static/src/search/search_model.js b/addons/web/static/src/search/search_model.js
index f03724152f4..b8995f199c2 100644
--- a/addons/web/static/src/search/search_model.js
+++ b/addons/web/static/src/search/search_model.js
@@ -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 || "";
- 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 };
diff --git a/addons/web/static/src/views/view.js b/addons/web/static/src/views/view.js
index 453e75385a2..f860389e43f 100644
--- a/addons/web/static/src/views/view.js
+++ b/addons/web/static/src/views/view.js
@@ -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,
diff --git a/addons/web/static/src/views/view_service.js b/addons/web/static/src/views/view_service.js
index 423bc8b317c..b25e26bb7f5 100644
--- a/addons/web/static/src/views/view_service.js
+++ b/addons/web/static/src/views/view_service.js
@@ -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}
*/
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;
})
diff --git a/addons/web/static/tests/helpers/mock_server.js b/addons/web/static/tests/helpers/mock_server.js
index f3689d437e9..15be09d7c87 100644
--- a/addons/web/static/tests/helpers/mock_server.js
+++ b/addons/web/static/tests/helpers/mock_server.js
@@ -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" ? "" : 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 };
}
/**
diff --git a/addons/web/static/tests/legacy/fields/relational_fields/field_many2many_tests.js b/addons/web/static/tests/legacy/fields/relational_fields/field_many2many_tests.js
index ff6ce352664..4dbb411b0d4 100644
--- a/addons/web/static/tests/legacy/fields/relational_fields/field_many2many_tests.js
+++ b/addons/web/static/tests/legacy/fields/relational_fields/field_many2many_tests.js
@@ -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': '',
},
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 () {
'',
},
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
diff --git a/addons/web/static/tests/legacy/fields/relational_fields/field_many2one_tests.js b/addons/web/static/tests/legacy/fields/relational_fields/field_many2one_tests.js
index 03bfa5ee421..7f99381c812 100644
--- a/addons/web/static/tests/legacy/fields/relational_fields/field_many2one_tests.js
+++ b/addons/web/static/tests/legacy/fields/relational_fields/field_many2one_tests.js
@@ -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',
diff --git a/addons/web/static/tests/legacy/fields/relational_fields/field_one2many_tests.js b/addons/web/static/tests/legacy/fields/relational_fields/field_one2many_tests.js
index aada8b45683..06593a76727 100644
--- a/addons/web/static/tests/legacy/fields/relational_fields/field_one2many_tests.js
+++ b/addons/web/static/tests/legacy/fields/relational_fields/field_one2many_tests.js
@@ -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
diff --git a/addons/web/static/tests/legacy/helpers/mock_server.js b/addons/web/static/tests/legacy/helpers/mock_server.js
index d9cbf4c756a..055e6646067 100644
--- a/addons/web/static/tests/legacy/helpers/mock_server.js
+++ b/addons/web/static/tests/legacy/helpers/mock_server.js
@@ -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);
diff --git a/addons/web/static/tests/legacy/helpers/test_utils.js b/addons/web/static/tests/legacy/helpers/test_utils.js
index e8b5d23250c..cd021609026 100644
--- a/addons/web/static/tests/legacy/helpers/test_utils.js
+++ b/addons/web/static/tests/legacy/helpers/test_utils.js
@@ -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'),
diff --git a/addons/web/static/tests/legacy/helpers/test_utils_create.js b/addons/web/static/tests/legacy/helpers/test_utils_create.js
index f1b31c26ccb..e93e2161c4a 100644
--- a/addons/web/static/tests/legacy/helpers/test_utils_create.js
+++ b/addons/web/static/tests/legacy/helpers/test_utils_create.js
@@ -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'] || '',
fields: viewInfo.fields,
model: params.model,
diff --git a/addons/web/static/tests/legacy/helpers/test_utils_mock.js b/addons/web/static/tests/legacy/helpers/test_utils_mock.js
index 9e7b941ffc3..d866c8e3e9d 100644
--- a/addons/web/static/tests/legacy/helpers/test_utils_mock.js
+++ b/addons/web/static/tests/legacy/helpers/test_utils_mock.js
@@ -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,
diff --git a/addons/web/static/tests/legacy/services/data_manager_tests.js b/addons/web/static/tests/legacy/services/data_manager_tests.js
index 38ee8d71970..5e36b1a093c 100644
--- a/addons/web/static/tests/legacy/services/data_manager_tests.js
+++ b/addons/web/static/tests/legacy/services/data_manager_tests.js
@@ -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',
diff --git a/addons/web/static/tests/legacy/views/basic_model_tests.js b/addons/web/static/tests/legacy/views/basic_model_tests.js
index 3bf60f59333..6678c47f7af 100644
--- a/addons/web/static/tests/legacy/views/basic_model_tests.js
+++ b/addons/web/static/tests/legacy/views/basic_model_tests.js
@@ -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');
diff --git a/addons/web/static/tests/legacy/views/calendar_tests.js b/addons/web/static/tests/legacy/views/calendar_tests.js
index 778bbc65715..8775881af10 100644
--- a/addons/web/static/tests/legacy/views/calendar_tests.js
+++ b/addons/web/static/tests/legacy/views/calendar_tests.js
@@ -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");
}
diff --git a/addons/web/static/tests/legacy/views/form_tests.js b/addons/web/static/tests/legacy/views/form_tests.js
index 654e932099b..e3725bd817a 100644
--- a/addons/web/static/tests/legacy/views/form_tests.js
+++ b/addons/web/static/tests/legacy/views/form_tests.js
@@ -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': '',
'partner_type,false,list': ''+
@@ -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();
});
diff --git a/addons/web/static/tests/legacy/views/kanban_tests.js b/addons/web/static/tests/legacy/views/kanban_tests.js
index 54e19fcb9b7..a79e008acef 100644
--- a/addons/web/static/tests/legacy/views/kanban_tests.js
+++ b/addons/web/static/tests/legacy/views/kanban_tests.js
@@ -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;
diff --git a/addons/web/static/tests/search/search_panel_tests.js b/addons/web/static/tests/search/search_panel_tests.js
index 3699f340ae2..b354e7f3c81 100644
--- a/addons/web/static/tests/search/search_panel_tests.js
+++ b/addons/web/static/tests/search/search_panel_tests.js
@@ -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",
]);
diff --git a/addons/web/static/tests/search/with_search_tests.js b/addons/web/static/tests/search/with_search_tests.js
index 2224c9314ce..a881ee97b7e 100644
--- a/addons/web/static/tests/search/with_search_tests.js
+++ b/addons/web/static/tests/search/with_search_tests.js
@@ -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"]]);
}
},
diff --git a/addons/web/static/tests/views/helpers.js b/addons/web/static/tests/views/helpers.js
index fa8d0a4271a..d8e2bb60ede 100644
--- a/addons/web/static/tests/views/helpers.js
+++ b/addons/web/static/tests/views/helpers.js
@@ -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 || "";
props.searchViewFields = props.searchViewFields || Object.assign({}, props.fields);
}
diff --git a/addons/web/static/tests/views/pivot_view_tests.js b/addons/web/static/tests/views/pivot_view_tests.js
index 8e3dee7299a..d6aafe09ce8 100644
--- a/addons/web/static/tests/views/pivot_view_tests.js
+++ b/addons/web/static/tests/views/pivot_view_tests.js
@@ -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"));
diff --git a/addons/web/static/tests/views/view_service_tests.js b/addons/web/static/tests/views/view_service_tests.js
index 904c037c77d..f4e78e36d01 100644
--- a/addons/web/static/tests/views/view_service_tests.js
+++ b/addons/web/static/tests/views/view_service_tests.js
@@ -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"]);
});
});
diff --git a/addons/web/static/tests/views/view_tests.js b/addons/web/static/tests/views/view_tests.js
index 22f3e57e57e..a7bb4063e96 100644
--- a/addons/web/static/tests/views/view_tests.js
+++ b/addons/web/static/tests/views/view_tests.js
@@ -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,
diff --git a/addons/web/static/tests/webclient/actions/concurrency_tests.js b/addons/web/static/tests/webclient/actions/concurrency_tests.js
index 6fdaf48b963..31292228366 100644
--- a/addons/web/static/tests/webclient/actions/concurrency_tests.js
+++ b/addons/web/static/tests/webclient/actions/concurrency_tests.js
@@ -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",
]);
diff --git a/addons/web/static/tests/webclient/actions/load_state_tests.js b/addons/web/static/tests/webclient/actions/load_state_tests.js
index fc9233372ae..afcfd375e1a 100644
--- a/addons/web/static/tests/webclient/actions/load_state_tests.js
+++ b/addons/web/static/tests/webclient/actions/load_state_tests.js
@@ -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, {
diff --git a/addons/web/static/tests/webclient/actions/misc_tests.js b/addons/web/static/tests/webclient/actions/misc_tests.js
index 0cf598d2e7e..86e936665f1 100644
--- a/addons/web/static/tests/webclient/actions/misc_tests.js
+++ b/addons/web/static/tests/webclient/actions/misc_tests.js
@@ -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();
diff --git a/addons/web/static/tests/webclient/actions/server_action_tests.js b/addons/web/static/tests/webclient/actions/server_action_tests.js
index d08bff74d79..d1322301bd1 100644
--- a/addons/web/static/tests/webclient/actions/server_action_tests.js
+++ b/addons/web/static/tests/webclient/actions/server_action_tests.js
@@ -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",
diff --git a/addons/web/static/tests/webclient/actions/target_tests.js b/addons/web/static/tests/webclient/actions/target_tests.js
index 6fdbc735ddf..e30850d8b98 100644
--- a/addons/web/static/tests/webclient/actions/target_tests.js
+++ b/addons/web/static/tests/webclient/actions/target_tests.js
@@ -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",
]);
}
diff --git a/addons/web/static/tests/webclient/actions/window_action_tests.js b/addons/web/static/tests/webclient/actions/window_action_tests.js
index eeeabc0248b..a786baa9890 100644
--- a/addons/web/static/tests/webclient/actions/window_action_tests.js
+++ b/addons/web/static/tests/webclient/actions/window_action_tests.js
@@ -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");
diff --git a/addons/web/static/tests/webclient/helpers.js b/addons/web/static/tests/webclient/helpers.js
index 9fd42d88e68..99c226b8354 100644
--- a/addons/web/static/tests/webclient/helpers.js
+++ b/addons/web/static/tests/webclient/helpers.js
@@ -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];
diff --git a/odoo/addons/base/static/tests/base_settings_tests.js b/odoo/addons/base/static/tests/base_settings_tests.js
index b278b54b59b..1ee83575a37 100644
--- a/odoo/addons/base/static/tests/base_settings_tests.js
+++ b/odoo/addons/base/static/tests/base_settings_tests.js
@@ -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
]);