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