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 ]);