[REF] base,all: Update modifier syntax: adapt JavaScript
closes odoo/odoo#104741 Related: odoo/enterprise#35255 Related: odoo/documentation#3523 Related: odoo/upgrade#4884 Signed-off-by: Rémy Voet (ryv) <ryv@odoo.com>
This commit is contained in:
@@ -73,7 +73,7 @@ QUnit.module('section_and_note', (hooks) => {
|
||||
<field name="invoice_line_ids" widget="section_and_note_one2many">
|
||||
<tree editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="display_type" invisible="1"/>
|
||||
<field name="display_type" column_invisible="1"/>
|
||||
<field name="name" widget="section_and_note_text"/>
|
||||
<field name="price"/>
|
||||
</tree>
|
||||
|
||||
@@ -96,7 +96,7 @@ QUnit.test(
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="description" onchange_on_keydown="True" attrs="{'invisible': [('display_name','=','yop')]}"/>
|
||||
<field name="description" onchange_on_keydown="True" invisible="display_name == 'yop'"/>
|
||||
<field name="display_name"/>
|
||||
</form>`,
|
||||
mockRPC(route, params) {
|
||||
|
||||
@@ -459,7 +459,7 @@ QUnit.module('favorite filter widget', (hooks) => {
|
||||
<field name="display_name"/>
|
||||
<field name="subject"/>
|
||||
<field name="mailing_model_name" invisible="1"/>
|
||||
<field name="mailing_model_id" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
|
||||
<field name="mailing_model_id" readonly="state != 'draft'"/>
|
||||
<field name="mailing_filter_count" />
|
||||
<field name="mailing_filter_id" widget="mailing_filter" options="{'no_create': '1', 'no_open': '1', 'domain_field': 'mailing_domain', 'model': 'mailing_model_id'}"/>
|
||||
<field name="state" widget="statusbar" options="{'clickable' : '1'}"/>
|
||||
|
||||
@@ -109,7 +109,7 @@ QUnit.module('field html', (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="body_arch" widget="mass_mailing_html" attrs="{'invisible': [['display_name', '=', 'hide']]}"/>
|
||||
<field name="body_arch" widget="mass_mailing_html" invisible="display_name == 'hide'"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { ListRenderer } from "@web/views/list/list_renderer";
|
||||
|
||||
const { onWillUpdateProps } = owl;
|
||||
@@ -19,14 +19,8 @@ export class ProjectSharingListRenderer extends ListRenderer {
|
||||
const allColumns = [];
|
||||
const firstRecord = this.props.list.records[0];
|
||||
for (const column of columns) {
|
||||
if (
|
||||
column.modifiers.column_invisible &&
|
||||
column.modifiers.column_invisible instanceof Array
|
||||
) {
|
||||
const result = evalDomain(column.modifiers.column_invisible, firstRecord.evalContext);
|
||||
if (result) {
|
||||
continue;
|
||||
}
|
||||
if (evaluateBooleanExpr(column.column_invisible, firstRecord.evalContext)) {
|
||||
continue;
|
||||
}
|
||||
allColumns.push(column);
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ export class ProjectTaskKanbanRenderer extends KanbanRenderer {
|
||||
let canMoveRecords = super.canMoveRecords;
|
||||
if (!canMoveRecords && this.canResequenceRecords && this.props.list.isGroupedByPersonalStages) {
|
||||
const { groupByField } = this.props.list;
|
||||
const { modifiers } = groupByField;
|
||||
canMoveRecords = !(modifiers && modifiers.readonly);
|
||||
canMoveRecords = groupByField.readonly !== "True";
|
||||
}
|
||||
return canMoveRecords;
|
||||
}
|
||||
@@ -25,9 +24,8 @@ export class ProjectTaskKanbanRenderer extends KanbanRenderer {
|
||||
get canResequenceGroups() {
|
||||
let canResequenceGroups = super.canResequenceGroups;
|
||||
if (!canResequenceGroups && this.props.list.isGroupedByPersonalStages) {
|
||||
const { modifiers } = this.props.list.groupByField;
|
||||
const { groupsDraggable } = this.props.archInfo;
|
||||
canResequenceGroups = groupsDraggable && !(modifiers && modifiers.readonly);
|
||||
canResequenceGroups = groupsDraggable && groupsDraggable.readonly !== "True";
|
||||
}
|
||||
return canResequenceGroups;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ QUnit.module("SectionOneToManyField", (hooks) => {
|
||||
<form>
|
||||
<field name="lines" widget="section_one2many">
|
||||
<tree>
|
||||
<field name="display_type" invisible="1" />
|
||||
<field name="display_type" column_invisible="1" />
|
||||
<field name="title" />
|
||||
<field name="int" />
|
||||
</tree>
|
||||
|
||||
@@ -93,6 +93,10 @@ registry.category("web_tour.tours").add('sale_timesheet_tour', {
|
||||
}, {
|
||||
trigger: 'div[name="timesheet_ids"] td.o_field_x2many_list_row_add a[role="button"]',
|
||||
content: 'Click on Add a line to create a new timesheet into the task.',
|
||||
}, {
|
||||
trigger: '.o_field_x2many div[name="name"] input',
|
||||
content: 'Enter a description for this timesheet',
|
||||
run: 'text work',
|
||||
}, {
|
||||
trigger: 'div[name="unit_amount"] input',
|
||||
content: 'Enter one hour for this timesheet',
|
||||
|
||||
@@ -76,7 +76,7 @@ QUnit.module("QuestionPageListRenderer", (hooks) => {
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="title" widget="survey_description_page"/>
|
||||
<field name="question_type" />
|
||||
<field name="is_page" invisible="1"/>
|
||||
<field name="is_page" column_invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>
|
||||
@@ -108,7 +108,7 @@ QUnit.module("QuestionPageListRenderer", (hooks) => {
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="title" widget="survey_description_page"/>
|
||||
<field name="question_type" />
|
||||
<field name="is_page" invisible="1"/>
|
||||
<field name="is_page" column_invisible="1"/>
|
||||
<field name="random_questions_count"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -147,7 +147,7 @@ QUnit.module("QuestionPageListRenderer", (hooks) => {
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="question_type" />
|
||||
<field name="title" widget="survey_description_page"/>
|
||||
<field name="is_page" invisible="1"/>
|
||||
<field name="is_page" column_invisible="1"/>
|
||||
<field name="random_questions_count"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -184,7 +184,7 @@ QUnit.module("QuestionPageListRenderer", (hooks) => {
|
||||
<field name="random_questions_count"/>
|
||||
<field name="question_type" />
|
||||
<field name="title" widget="survey_description_page"/>
|
||||
<field name="is_page" invisible="1"/>
|
||||
<field name="is_page" column_invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>
|
||||
|
||||
@@ -33,6 +33,32 @@ export function parseExpr(expr) {
|
||||
* @returns {any}
|
||||
*/
|
||||
export function evaluateExpr(expr, context = {}) {
|
||||
const ast = parseExpr(expr);
|
||||
return evaluate(ast, context);
|
||||
let ast;
|
||||
try {
|
||||
ast = parseExpr(expr);
|
||||
} catch (error) {
|
||||
throw new EvalError(`Can not parse python expression: (${expr})\nError: ${error.message}`);
|
||||
}
|
||||
try {
|
||||
return evaluate(ast, context);
|
||||
} catch (error) {
|
||||
throw new EvalError(`Can not evaluate python expression: (${expr})\nError: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a python expression to return a boolean.
|
||||
*
|
||||
* @param {string} expr
|
||||
* @param {Object} [context]
|
||||
* @returns {any}
|
||||
*/
|
||||
export function evaluateBooleanExpr(expr, context = {}) {
|
||||
if (!expr || expr === 'False' || expr === '0') {
|
||||
return false;
|
||||
}
|
||||
if (expr === 'True' || expr === '1') {
|
||||
return true;
|
||||
}
|
||||
return evaluateExpr(`bool(${expr})`, context);
|
||||
}
|
||||
|
||||
@@ -16,12 +16,15 @@ export const BUILTINS = {
|
||||
case "boolean":
|
||||
return value;
|
||||
case "object":
|
||||
if (value === null) {
|
||||
if (value === null || value === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (value.isTrue) {
|
||||
return value.isTrue();
|
||||
}
|
||||
if (value instanceof Array) {
|
||||
return !!value.length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -136,6 +136,9 @@ function isIn(left, right) {
|
||||
if (typeof right === "string" && typeof left === "string") {
|
||||
return right.includes(left);
|
||||
}
|
||||
if (typeof right === "object") {
|
||||
return left in right;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -261,6 +264,15 @@ const DICT = {
|
||||
},
|
||||
};
|
||||
|
||||
const STRING = {
|
||||
lower(str) {
|
||||
return () => str.toLowerCase();
|
||||
},
|
||||
upper(str) {
|
||||
return () => str.toUpperCase();
|
||||
},
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Evaluate function
|
||||
// -----------------------------------------------------------------------------
|
||||
@@ -274,14 +286,16 @@ export function evaluate(ast, context = {}) {
|
||||
const dicts = new Set();
|
||||
let pyContext;
|
||||
const evalContext = Object.create(context);
|
||||
Object.defineProperty(evalContext, "context", {
|
||||
get() {
|
||||
if (!pyContext) {
|
||||
pyContext = toPyDict(context);
|
||||
}
|
||||
return pyContext;
|
||||
},
|
||||
});
|
||||
if (!evalContext.context) {
|
||||
Object.defineProperty(evalContext, "context", {
|
||||
get() {
|
||||
if (!pyContext) {
|
||||
pyContext = toPyDict(context);
|
||||
}
|
||||
return pyContext;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AST} ast
|
||||
@@ -362,6 +376,12 @@ export function evaluate(ast, context = {}) {
|
||||
// this is a dictionary => need to apply dict methods
|
||||
return DICT[ast.key](left);
|
||||
}
|
||||
if (typeof left === 'string') {
|
||||
return STRING[ast.key](left);
|
||||
}
|
||||
if (ast.key == 'get' && typeof left === 'object') {
|
||||
return DICT[ast.key](toPyDict(left));
|
||||
}
|
||||
const result = left[ast.key];
|
||||
if (typeof result === "function" && !isConstructor(result)) {
|
||||
return result.bind(left);
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { markRaw, markup, toRaw } from "@odoo/owl";
|
||||
import { AlertDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { Domain, evalDomain } from "@web/core/domain";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { serializeDate, serializeDateTime } from "@web/core/l10n/dates";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { x2ManyCommands } from "@web/core/orm_service";
|
||||
@@ -541,17 +542,17 @@ export class Record extends DataPoint {
|
||||
|
||||
_isInvisible(fieldName) {
|
||||
const invisible = this.activeFields[fieldName].invisible;
|
||||
return invisible ? evalDomain(invisible, this.evalContext) : false;
|
||||
return invisible ? evaluateBooleanExpr(invisible, this.evalContext) : false;
|
||||
}
|
||||
|
||||
_isReadonly(fieldName) {
|
||||
const readonly = this.activeFields[fieldName].readonly;
|
||||
return readonly ? evalDomain(readonly, this.evalContext) : false;
|
||||
return readonly ? evaluateBooleanExpr(readonly, this.evalContext) : false;
|
||||
}
|
||||
|
||||
_isRequired(fieldName) {
|
||||
const required = this.activeFields[fieldName].required;
|
||||
return required ? evalDomain(required, this.evalContext) : false;
|
||||
return required ? evaluateBooleanExpr(required, this.evalContext) : false;
|
||||
}
|
||||
|
||||
async _load(nextConfig = {}) {
|
||||
|
||||
@@ -502,7 +502,8 @@ export class StaticList extends DataPoint {
|
||||
const changes = {};
|
||||
for (const fieldName in command[2]) {
|
||||
if (["one2many", "many2many"].includes(this.fields[fieldName].type)) {
|
||||
if (record.activeFields[fieldName].invisible) {
|
||||
const invisible = record.activeFields[fieldName].invisible;
|
||||
if (invisible === "True" || invisible === "1") {
|
||||
if (!(command[1] in this._unknownRecordCommands)) {
|
||||
this._unknownRecordCommands[command[1]] = [];
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import { markup, onWillDestroy, onWillStart, onWillUpdateProps, useComponent } from "@odoo/owl";
|
||||
import { evalPartialContext, makeContext } from "@web/core/context";
|
||||
import { deserializeDate, deserializeDateTime } from "@web/core/l10n/dates";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { x2ManyCommands } from "@web/core/orm_service";
|
||||
import { Deferred } from "@web/core/utils/concurrency";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
@@ -22,9 +21,9 @@ export function makeActiveField({
|
||||
} = {}) {
|
||||
return {
|
||||
context: context || "{}",
|
||||
invisible: invisible || false,
|
||||
readonly: readonly || false,
|
||||
required: required || false,
|
||||
invisible: invisible || "False",
|
||||
readonly: readonly || "False",
|
||||
required: required || "False",
|
||||
onChange: onChange || false,
|
||||
forceSave: forceSave || false,
|
||||
isHandle: isHandle || false,
|
||||
@@ -74,7 +73,7 @@ export function completeActiveFields(activeFields, extraActiveFields) {
|
||||
for (const fieldName in extraActiveFields) {
|
||||
const extraActiveField = {
|
||||
...extraActiveFields[fieldName],
|
||||
invisible: true,
|
||||
invisible: 'True',
|
||||
};
|
||||
if (fieldName in activeFields) {
|
||||
completeActiveField(activeFields[fieldName], extraActiveField);
|
||||
@@ -103,29 +102,29 @@ export function createPropertyActiveField(property) {
|
||||
return activeField;
|
||||
}
|
||||
|
||||
function combineModifiers(mod1, mod2, operator) {
|
||||
export function combineModifiers(mod1, mod2, operator) {
|
||||
if (operator === "AND") {
|
||||
if (mod1 === false || mod2 === false) {
|
||||
return false;
|
||||
if (!mod1 || mod1 === "False" || !mod2 || mod2 === "False") {
|
||||
return "False";
|
||||
}
|
||||
if (mod1 === true) {
|
||||
if (mod1 === "True") {
|
||||
return mod2;
|
||||
}
|
||||
if (mod2 === true) {
|
||||
if (mod2 === "True") {
|
||||
return mod1;
|
||||
}
|
||||
return Domain.and([mod1, mod2]).toString();
|
||||
return "(" + mod1 + ") and (" + mod2 + ")";
|
||||
} else if (operator === "OR") {
|
||||
if (mod1 === true || mod2 === true) {
|
||||
return true;
|
||||
if (mod1 === "True" || mod2 === "True") {
|
||||
return "True";
|
||||
}
|
||||
if (mod1 === false) {
|
||||
if (!mod1 || mod1 === "False") {
|
||||
return mod2;
|
||||
}
|
||||
if (mod2 === false) {
|
||||
if (!mod2 || mod2 === "False") {
|
||||
return mod1;
|
||||
}
|
||||
return Domain.or([mod1, mod2]).toString();
|
||||
return "(" + mod1 + ") or (" + mod2 + ")";
|
||||
}
|
||||
throw new Error(
|
||||
`Operator provided to "combineModifiers" must be "AND" or "OR", received ${operator}`
|
||||
@@ -166,12 +165,11 @@ export function extractFieldsFromArchInfo({ fieldNodes, widgetNodes }, fields) {
|
||||
const activeFields = {};
|
||||
for (const fieldNode of Object.values(fieldNodes)) {
|
||||
const fieldName = fieldNode.name;
|
||||
const modifiers = fieldNode.modifiers || {};
|
||||
const activeField = makeActiveField({
|
||||
context: fieldNode.context,
|
||||
invisible: modifiers.invisible || modifiers.column_invisible,
|
||||
readonly: modifiers.readonly,
|
||||
required: modifiers.required,
|
||||
invisible: combineModifiers(fieldNode.invisible, fieldNode.column_invisible, "OR"),
|
||||
readonly: fieldNode.readonly,
|
||||
required: fieldNode.required,
|
||||
onChange: fieldNode.onChange,
|
||||
forceSave: fieldNode.forceSave,
|
||||
isHandle: fieldNode.isHandle,
|
||||
@@ -205,7 +203,7 @@ export function extractFieldsFromArchInfo({ fieldNodes, widgetNodes }, fields) {
|
||||
}
|
||||
}
|
||||
if (fieldNode.field?.useSubView) {
|
||||
activeField.required = false;
|
||||
activeField.required = 'False';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +270,7 @@ export function getFieldsSpec(
|
||||
const { related, limit, defaultOrderBy, invisible } = activeFields[fieldName];
|
||||
fieldsSpec[fieldName] = {};
|
||||
// X2M
|
||||
if (related && (invisible !== true || withInvisible)) {
|
||||
if (related && ((invisible !== "True" && invisible !== "1") || withInvisible)) {
|
||||
fieldsSpec[fieldName].fields = getFieldsSpec(
|
||||
related.activeFields,
|
||||
related.fields,
|
||||
@@ -291,7 +289,7 @@ export function getFieldsSpec(
|
||||
// M2O
|
||||
if (fields[fieldName].type === "many2one") {
|
||||
fieldsSpec[fieldName].fields = {};
|
||||
if (invisible !== true) {
|
||||
if (invisible !== "True" && invisible !== "1") {
|
||||
fieldsSpec[fieldName].fields.display_name = {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { XMLParser } from "@web/core/utils/xml";
|
||||
import { DEFAULT_INTERVAL, DEFAULT_PERIOD } from "@web/search/utils/dates";
|
||||
|
||||
@@ -109,9 +109,8 @@ export class SearchArchParser extends XMLParser {
|
||||
visitField(node) {
|
||||
this.pushGroup("field");
|
||||
const preField = { type: "field" };
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
if (modifiers.invisible === true) {
|
||||
preField.invisible = true;
|
||||
if (node.getAttribute("invisible") === "True" || node.getAttribute("invisible") === "1") {
|
||||
preField.invisible = "True";
|
||||
}
|
||||
if (node.hasAttribute("domain")) {
|
||||
preField.domain = node.getAttribute("domain");
|
||||
@@ -221,9 +220,8 @@ export class SearchArchParser extends XMLParser {
|
||||
preSearchItem.domain = stringRepr;
|
||||
}
|
||||
}
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
if (modifiers.invisible === true) {
|
||||
preSearchItem.invisible = true;
|
||||
if (node.getAttribute("invisible") === "True" || node.getAttribute("invisible") === "1") {
|
||||
preSearchItem.invisible = "True";
|
||||
const fieldName = preSearchItem.fieldName;
|
||||
if (fieldName && !this.fields[fieldName]) {
|
||||
// In some case when a field is limited to specific groups
|
||||
@@ -290,8 +288,7 @@ export class SearchArchParser extends XMLParser {
|
||||
if (node.nodeType !== 1 || node.tagName !== "field") {
|
||||
continue;
|
||||
}
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
if (modifiers.invisible === true) {
|
||||
if (node.getAttribute("invisible") === "True" || node.getAttribute("invisible") === "1") {
|
||||
continue;
|
||||
}
|
||||
const attrs = {};
|
||||
@@ -303,8 +300,8 @@ export class SearchArchParser extends XMLParser {
|
||||
const section = {
|
||||
color: attrs.color || null,
|
||||
description: attrs.string || this.fields[attrs.name].string,
|
||||
enableCounters: Boolean(evaluateExpr(attrs.enable_counters || "0")),
|
||||
expand: Boolean(evaluateExpr(attrs.expand || "0")),
|
||||
enableCounters: evaluateBooleanExpr(attrs.enable_counters),
|
||||
expand: evaluateBooleanExpr(attrs.expand),
|
||||
fieldName: attrs.name,
|
||||
icon: attrs.icon || null,
|
||||
id: nextSectionId++,
|
||||
@@ -315,7 +312,7 @@ export class SearchArchParser extends XMLParser {
|
||||
if (type === "category") {
|
||||
section.activeValueId = this.searchPanelDefaults[attrs.name];
|
||||
section.icon = section.icon || "fa-folder";
|
||||
section.hierarchize = Boolean(evaluateExpr(attrs.hierarchize || "1"));
|
||||
section.hierarchize = evaluateBooleanExpr(attrs.hierarchize || "1");
|
||||
section.values.set(false, {
|
||||
childrenIds: [],
|
||||
display_name: ALL.toString(),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { is24HourFormat } from "@web/core/l10n/dates";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { Record } from "@web/views/record";
|
||||
@@ -33,7 +33,7 @@ export class CalendarCommonPopover extends Component {
|
||||
}
|
||||
|
||||
isInvisible(fieldNode, record) {
|
||||
return evalDomain(fieldNode.modifiers.invisible, record.evalContext);
|
||||
return evaluateBooleanExpr(fieldNode.invisible, record.evalContext);
|
||||
}
|
||||
|
||||
computeDateTimeAndDuration() {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { editModelDebug } from "@web/core/debug/debug_utils";
|
||||
import { formatDateTime, deserializeDateTime } from "@web/core/l10n/dates";
|
||||
import { registry } from "@web/core/registry";
|
||||
@@ -69,6 +69,9 @@ export function editView({ accessRights, component, env }) {
|
||||
type = component.props.viewInfo.type;
|
||||
type = type === "tree" ? "list" : type;
|
||||
}
|
||||
if (!type) {
|
||||
return;
|
||||
}
|
||||
const displayName = type[0].toUpperCase() + type.slice(1);
|
||||
const description = env._t("Edit View: ") + displayName;
|
||||
return {
|
||||
@@ -221,8 +224,8 @@ class SetDefaultDialog extends Component {
|
||||
// ignore fields which are empty, invisible, readonly, o2m or m2m
|
||||
if (
|
||||
!value ||
|
||||
evalDomain(this.activeFields[fieldName].invisible, evalContext) ||
|
||||
evalDomain(this.activeFields[fieldName].readonly, evalContext) ||
|
||||
evaluateBooleanExpr(this.activeFields[fieldName].invisible, evalContext) ||
|
||||
evaluateBooleanExpr(this.activeFields[fieldName].readonly, evalContext) ||
|
||||
fieldInfo.type === "one2many" ||
|
||||
fieldInfo.type === "many2many" ||
|
||||
fieldInfo.type === "binary" ||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { standardFieldProps } from "@web/views/fields/standard_field_props";
|
||||
|
||||
@@ -28,7 +28,7 @@ export class BadgeField extends Component {
|
||||
get classFromDecoration() {
|
||||
const evalContext = this.props.record.evalContext;
|
||||
for (const decorationName in this.props.decorations) {
|
||||
if (evaluateExpr(this.props.decorations[decorationName], evalContext)) {
|
||||
if (evaluateBooleanExpr(this.props.decorations[decorationName], evalContext)) {
|
||||
return `text-bg-${decorationName}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
|
||||
@@ -37,12 +36,7 @@ class CopyClipboardField extends Component {
|
||||
return this.props.record.fields[this.props.name].type;
|
||||
}
|
||||
get disabled() {
|
||||
const context = this.props.record.evalContext;
|
||||
const evaluated = this.props.disabledExpr ? evaluateExpr(this.props.disabledExpr) : false;
|
||||
if (evaluated instanceof Array) {
|
||||
return evalDomain(evaluated, context);
|
||||
}
|
||||
return Boolean(evaluated);
|
||||
return this.props.disabledExpr ? evaluateBooleanExpr(this.props.disabledExpr, this.props.record.evalContext) : false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { standardFieldProps } from "../standard_field_props";
|
||||
* maxDate?: string;
|
||||
* minDate?: string;
|
||||
* placeholder?: string;
|
||||
* required?: boolean;
|
||||
* required?: string | boolean;
|
||||
* rounding?: number;
|
||||
* startDateField?: string;
|
||||
* warnFuture?: boolean;
|
||||
@@ -41,7 +41,7 @@ export class DateTimeField extends Component {
|
||||
maxDate: { type: String, optional: true },
|
||||
minDate: { type: String, optional: true },
|
||||
placeholder: { type: String, optional: true },
|
||||
required: { type: Boolean, optional: true },
|
||||
required: { type: [Boolean, String], optional: true },
|
||||
rounding: { type: Number, optional: true },
|
||||
startDateField: { type: String, optional: true },
|
||||
warnFuture: { type: Boolean, optional: true },
|
||||
@@ -267,27 +267,27 @@ export const dateField = {
|
||||
},
|
||||
],
|
||||
supportedTypes: ["date"],
|
||||
extractProps: ({ attrs, modifiers, options }) => ({
|
||||
extractProps: ({ attrs, options }, dynamicInfo) => ({
|
||||
endDateField: options[END_DATE_FIELD_OPTION],
|
||||
maxDate: options.max_date,
|
||||
minDate: options.min_date,
|
||||
placeholder: attrs.placeholder,
|
||||
required: Boolean(modifiers.required),
|
||||
required: 'required' in attrs ? attrs.required : dynamicInfo.required,
|
||||
rounding: options.rounding && parseInt(options.rounding, 10),
|
||||
startDateField: options[START_DATE_FIELD_OPTION],
|
||||
warnFuture: archParseBoolean(options.warn_future),
|
||||
}),
|
||||
fieldDependencies: ({ type, modifiers, options }) => {
|
||||
fieldDependencies: ({ type, attrs, options }) => {
|
||||
const deps = [];
|
||||
if (options[START_DATE_FIELD_OPTION]) {
|
||||
deps.push({ name: options[START_DATE_FIELD_OPTION], type, modifiers });
|
||||
deps.push({ name: options[START_DATE_FIELD_OPTION], type, attrs });
|
||||
if (options[END_DATE_FIELD_OPTION]) {
|
||||
console.warn(
|
||||
`A field cannot have both ${START_DATE_FIELD_OPTION} and ${END_DATE_FIELD_OPTION} options at the same time`
|
||||
);
|
||||
}
|
||||
} else if (options[END_DATE_FIELD_OPTION]) {
|
||||
deps.push({ name: options[END_DATE_FIELD_OPTION], type, modifiers });
|
||||
deps.push({ name: options[END_DATE_FIELD_OPTION], type, attrs });
|
||||
}
|
||||
return deps;
|
||||
},
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { Domain, evalDomain } from "@web/core/domain";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { utils } from "@web/core/ui/ui_service";
|
||||
import { getFieldContext } from "@web/model/relational_model/utils";
|
||||
import { archParseBoolean, getClassNameFromDecoration, X2M_TYPES } from "@web/views/utils";
|
||||
import {
|
||||
archParseBoolean,
|
||||
getClassNameFromDecoration,
|
||||
X2M_TYPES,
|
||||
} from "@web/views/utils";
|
||||
import { getTooltipInfo } from "./field_tooltip";
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
@@ -39,8 +43,8 @@ export function getFieldFromRegistry(fieldType, widget, viewType, jsClass) {
|
||||
}
|
||||
|
||||
export function fieldVisualFeedback(field, record, fieldName, fieldInfo) {
|
||||
const modifiers = fieldInfo.modifiers || {};
|
||||
const readonly = evalDomain(modifiers.readonly, record.evalContext);
|
||||
const readonly = evaluateBooleanExpr(fieldInfo.readonly, record.evalContext);
|
||||
const required = evaluateBooleanExpr(fieldInfo.required, record.evalContext);
|
||||
const inEdit = record.isInEdition;
|
||||
|
||||
let empty = !record.isNew;
|
||||
@@ -52,7 +56,7 @@ export function fieldVisualFeedback(field, record, fieldName, fieldInfo) {
|
||||
empty = inEdit ? empty && readonly : empty;
|
||||
return {
|
||||
readonly,
|
||||
required: evalDomain(modifiers.required, record.evalContext),
|
||||
required,
|
||||
invalid: record.isFieldInvalid(fieldName),
|
||||
empty,
|
||||
};
|
||||
@@ -67,7 +71,10 @@ export function getPropertyFieldInfo(propertyField) {
|
||||
type,
|
||||
widget: type,
|
||||
options: {},
|
||||
modifiers: {},
|
||||
column_invisible: "False",
|
||||
invisible: "False",
|
||||
readonly: "False",
|
||||
required: "False",
|
||||
attrs: {},
|
||||
relatedPropertyField,
|
||||
|
||||
@@ -76,7 +83,6 @@ export function getPropertyFieldInfo(propertyField) {
|
||||
help: undefined,
|
||||
onChange: false,
|
||||
forceSave: false,
|
||||
alwaysInvisible: false,
|
||||
decorations: {},
|
||||
// ???
|
||||
};
|
||||
@@ -150,7 +156,7 @@ export class Field extends Component {
|
||||
const { decorations } = fieldInfo;
|
||||
const evalContext = record.evalContext;
|
||||
for (const decoName in decorations) {
|
||||
const value = evaluateExpr(decorations[decoName], evalContext);
|
||||
const value = evaluateBooleanExpr(decorations[decoName], evalContext);
|
||||
classNames[getClassNameFromDecoration(decoName)] = value;
|
||||
}
|
||||
}
|
||||
@@ -164,16 +170,13 @@ export class Field extends Component {
|
||||
|
||||
get fieldComponentProps() {
|
||||
const record = this.props.record;
|
||||
const evalContext = record.evalContext;
|
||||
const readonly = this.props.readonly === true;
|
||||
let readonly = this.props.readonly || false;
|
||||
|
||||
let readonlyFromModifiers = false;
|
||||
let propsFromNode = {};
|
||||
if (this.props.fieldInfo) {
|
||||
const evalContext = record.getEvalContext?.(false) || record.evalContext;
|
||||
let fieldInfo = this.props.fieldInfo;
|
||||
|
||||
const modifiers = fieldInfo.modifiers || {};
|
||||
readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
|
||||
readonly = readonly || evaluateBooleanExpr(fieldInfo.readonly, evalContext);
|
||||
|
||||
if (this.field.extractProps) {
|
||||
if (this.props.attrs) {
|
||||
@@ -182,14 +185,13 @@ export class Field extends Component {
|
||||
attrs: { ...fieldInfo.attrs, ...this.props.attrs },
|
||||
};
|
||||
}
|
||||
|
||||
const dynamicInfo = {
|
||||
get context() {
|
||||
return getFieldContext(record, fieldInfo.name, fieldInfo.context);
|
||||
},
|
||||
domain() {
|
||||
const evalContext = record.getEvalContext
|
||||
? record.getEvalContext(true)
|
||||
: record.evalContext;
|
||||
const evalContext = record.getEvalContext?.(true) || record.evalContext;
|
||||
if (fieldInfo.domain) {
|
||||
return new Domain(evaluateExpr(fieldInfo.domain, evalContext)).toList();
|
||||
}
|
||||
@@ -198,14 +200,8 @@ export class Field extends Component {
|
||||
? new Domain(evaluateExpr(domain, evalContext)).toList()
|
||||
: domain || [];
|
||||
},
|
||||
readonly: readonly || readonlyFromModifiers,
|
||||
get required() {
|
||||
return (
|
||||
fieldInfo.modifiers &&
|
||||
fieldInfo.modifiers.required &&
|
||||
evalDomain(fieldInfo.modifiers.required, record.evalContext)
|
||||
);
|
||||
},
|
||||
required: evaluateBooleanExpr(fieldInfo.required, evalContext),
|
||||
readonly: readonly,
|
||||
};
|
||||
propsFromNode = this.field.extractProps(fieldInfo, dynamicInfo);
|
||||
}
|
||||
@@ -221,7 +217,7 @@ export class Field extends Component {
|
||||
delete props.readonly;
|
||||
|
||||
return {
|
||||
readonly: readonly || !record.isInEdition || readonlyFromModifiers || false,
|
||||
readonly: readonly || !record.isInEdition || false,
|
||||
...propsFromNode,
|
||||
...props,
|
||||
};
|
||||
@@ -246,13 +242,15 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
const name = node.getAttribute("name");
|
||||
const widget = node.getAttribute("widget");
|
||||
const fields = models[modelName];
|
||||
if (!fields[name]) {
|
||||
throw new Error(`"${modelName}"."${name}" field is undefined.`);
|
||||
}
|
||||
const field = getFieldFromRegistry(fields[name].type, widget, viewType, jsClass);
|
||||
const fieldInfo = {
|
||||
name,
|
||||
type: fields[name].type,
|
||||
viewType,
|
||||
widget,
|
||||
modifiers: {},
|
||||
field,
|
||||
context: "{}",
|
||||
string: fields[name].string,
|
||||
@@ -260,12 +258,22 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
onChange: false,
|
||||
forceSave: false,
|
||||
options: {},
|
||||
alwaysInvisible: false,
|
||||
decorations: {},
|
||||
attrs: {},
|
||||
domain: undefined,
|
||||
};
|
||||
|
||||
for (const attr of ["invisible", "column_invisible", "readonly", "required"]) {
|
||||
fieldInfo[attr] = node.getAttribute(attr);
|
||||
if (fieldInfo[attr] === "True") {
|
||||
if (attr === "column_invisible") {
|
||||
fieldInfo.invisible = "True";
|
||||
}
|
||||
} else if (fieldInfo[attr] === null && fields[name][attr]) {
|
||||
fieldInfo[attr] = "True";
|
||||
}
|
||||
}
|
||||
|
||||
for (const { name, value } of node.attributes) {
|
||||
if (["name", "widget"].includes(name)) {
|
||||
// avoid adding name and widget to attrs
|
||||
@@ -273,11 +281,6 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
}
|
||||
if (["context", "string", "help", "domain"].includes(name)) {
|
||||
fieldInfo[name] = value;
|
||||
} else if (name === "modifiers") {
|
||||
fieldInfo.modifiers = JSON.parse(value);
|
||||
fieldInfo.alwaysInvisible =
|
||||
fieldInfo.modifiers.invisible === true ||
|
||||
fieldInfo.modifiers.column_invisible === true;
|
||||
} else if (name === "on_change") {
|
||||
fieldInfo.onChange = archParseBoolean(value);
|
||||
} else if (name === "options") {
|
||||
@@ -293,7 +296,7 @@ Field.parseFieldNode = function (node, models, modelName, viewType, jsClass) {
|
||||
}
|
||||
}
|
||||
if (name === "id") {
|
||||
fieldInfo.modifiers.readonly = true;
|
||||
fieldInfo.readonly = 'True';
|
||||
}
|
||||
|
||||
if (widget === "handle") {
|
||||
|
||||
@@ -18,7 +18,10 @@ export function getTooltipInfo(params) {
|
||||
widgetDescription,
|
||||
context: params.fieldInfo.context,
|
||||
domain: params.field.domain,
|
||||
modifiers: JSON.stringify(params.fieldInfo.modifiers),
|
||||
invisible: params.fieldInfo.invisible,
|
||||
column_invisible: params.fieldInfo.column_invisible,
|
||||
readonly: params.fieldInfo.readonly,
|
||||
required: params.fieldInfo.required,
|
||||
changeDefault: params.field.change_default,
|
||||
relation: params.field.relation,
|
||||
selection: params.field.selection,
|
||||
|
||||
@@ -40,9 +40,21 @@
|
||||
<span class="o-tooltip--technical--title">Domain:</span>
|
||||
<t t-esc="field.domain"/>
|
||||
</li>
|
||||
<li t-if="field.modifiers" data-item="modifiers">
|
||||
<span class="o-tooltip--technical--title">Modifiers:</span>
|
||||
<t t-esc="field.modifiers"/>
|
||||
<li t-if="'invisible' in field" data-item="invisible">
|
||||
<span class="o-tooltip--technical--title">Invisible:</span>
|
||||
<t t-esc="field.invisible"/>
|
||||
</li>
|
||||
<li t-if="'column_invisible' in field" data-item="column_invisible">
|
||||
<span class="o-tooltip--technical--title">Column invisible:</span>
|
||||
<t t-esc="field.column_invisible"/>
|
||||
</li>
|
||||
<li t-if="'required' in field" data-item="required">
|
||||
<span class="o-tooltip--technical--title">Required:</span>
|
||||
<t t-esc="field.required"/>
|
||||
</li>
|
||||
<li t-if="'readonly' in field" data-item="readonly">
|
||||
<span class="o-tooltip--technical--title">Readonly:</span>
|
||||
<t t-esc="field.readonly"/>
|
||||
</li>
|
||||
<li t-if="field.default" data-item="default">
|
||||
<span class="o-tooltip--technical--title">Default:</span>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { isMobileOS } from "@web/core/browser/feature_detection";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useChildRef, useOwnedDialogs, useService } from "@web/core/utils/hooks";
|
||||
import { Many2XAutocomplete, useOpenMany2XRecord } from "@web/views/fields/relational_utils";
|
||||
@@ -168,7 +168,7 @@ export class Many2OneField extends Component {
|
||||
get classFromDecoration() {
|
||||
const evalContext = this.props.record.evalContext;
|
||||
for (const decorationName in this.props.decorations) {
|
||||
if (evaluateExpr(this.props.decorations[decorationName], evalContext)) {
|
||||
if (evaluateBooleanExpr(this.props.decorations[decorationName], evalContext)) {
|
||||
return `text-${decorationName}`;
|
||||
}
|
||||
}
|
||||
@@ -217,8 +217,11 @@ export class Many2OneField extends Component {
|
||||
};
|
||||
}
|
||||
getDomain() {
|
||||
const domain = this.props.domain;
|
||||
return typeof domain === "function" ? domain() : domain;
|
||||
let domain = this.props.domain;
|
||||
if (typeof domain === "function") {
|
||||
domain = domain();
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
async openAction() {
|
||||
const action = await this.orm.call(this.relation, "get_formview_action", [[this.resId]], {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { AutoComplete } from "@web/core/autocomplete/autocomplete";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { Dialog } from "@web/core/dialog/dialog";
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { Domain } from "@web/core/domain";
|
||||
import { RPCError } from "@web/core/network/rpc_service";
|
||||
import { Cache } from "@web/core/utils/cache";
|
||||
import {
|
||||
@@ -121,7 +121,7 @@ export function useActiveActions({
|
||||
let evalFn = () => actionName !== "write";
|
||||
if (!isNull(crudOptions[actionName])) {
|
||||
const action = crudOptions[actionName];
|
||||
evalFn = (evalContext) => evalDomain(action, evalContext);
|
||||
evalFn = (evalContext) => Boolean(action && new Domain(action).contains(evalContext));
|
||||
}
|
||||
|
||||
if (actionName in subViewActiveActions) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { Pager } from "@web/core/pager/pager";
|
||||
import { registry } from "@web/core/registry";
|
||||
@@ -191,6 +191,9 @@ export class X2ManyField extends Component {
|
||||
archInfo,
|
||||
list: this.list,
|
||||
openRecord: this.openRecord.bind(this),
|
||||
evalViewModifier: (modifier) => {
|
||||
return evaluateBooleanExpr(modifier, this.list.evalContext);
|
||||
},
|
||||
};
|
||||
|
||||
if (this.props.viewMode === "kanban") {
|
||||
@@ -207,32 +210,9 @@ export class X2ManyField extends Component {
|
||||
return props;
|
||||
}
|
||||
|
||||
// handle column_invisible modifiers
|
||||
const columns = archInfo.columns
|
||||
.map((col) => {
|
||||
// first remove (column_)invisible buttons in button_groups
|
||||
if (col.type === "button_group") {
|
||||
const buttons = col.buttons.filter((button) => {
|
||||
return !this.evalColumnInvisibleModifier(button.modifiers);
|
||||
});
|
||||
return { ...col, buttons };
|
||||
}
|
||||
return col;
|
||||
})
|
||||
.filter((col) => {
|
||||
// filter out (column_)invisible fields and empty button_groups
|
||||
if (col.type === "field") {
|
||||
return !this.evalColumnInvisibleModifier(col.modifiers);
|
||||
} else if (col.type === "button_group") {
|
||||
return col.buttons.length > 0;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const editable =
|
||||
(this.archInfo.activeActions.edit && archInfo.editable) || this.props.editable;
|
||||
props.activeActions = this.activeActions;
|
||||
props.archInfo = { ...archInfo, columns };
|
||||
props.cycleOnTab = false;
|
||||
props.editable = !this.props.readonly && editable;
|
||||
props.nestedKeyOptionalFieldsData = this.nestedKeyOptionalFieldsData;
|
||||
@@ -244,13 +224,6 @@ export class X2ManyField extends Component {
|
||||
return props;
|
||||
}
|
||||
|
||||
evalColumnInvisibleModifier(modifiers) {
|
||||
if ("column_invisible" in modifiers) {
|
||||
return evalDomain(modifiers.column_invisible, this.list.evalContext);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async onAdd({ context, editable } = {}) {
|
||||
const domain =
|
||||
typeof this.props.domain === "function" ? this.props.domain() : this.props.domain;
|
||||
|
||||
@@ -134,17 +134,19 @@ export class FormCompiler extends ViewCompiler {
|
||||
let hasContent = false;
|
||||
for (const child of el.children) {
|
||||
const invisible = getModifier(child, "invisible");
|
||||
if (this.isAlwaysInvisible(invisible, params)) {
|
||||
if (!params.compileInvisibleNodes && (invisible === "True" || invisible === "1")) {
|
||||
continue;
|
||||
}
|
||||
hasContent = true;
|
||||
let isVisibleExpr;
|
||||
if (typeof invisible === "boolean") {
|
||||
isVisibleExpr = `${invisible ? false : true}`;
|
||||
if (!invisible || invisible === "False" || invisible === "0") {
|
||||
isVisibleExpr = "true";
|
||||
} else if (invisible === "True" || invisible === "1") {
|
||||
isVisibleExpr = "false";
|
||||
} else {
|
||||
isVisibleExpr = `!__comp__.evalDomainFromRecord(__comp__.props.record,${JSON.stringify(
|
||||
isVisibleExpr = `!__comp__.evaluateBooleanExpr(${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
)},__comp__.props.record.evalContext)`;
|
||||
}
|
||||
const mainSlot = createElement("t", {
|
||||
"t-set-slot": `slot_${slotId++}`,
|
||||
@@ -277,7 +279,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
}
|
||||
|
||||
const invisible = getModifier(child, "invisible");
|
||||
if (this.isAlwaysInvisible(invisible, params)) {
|
||||
if (!params.compileInvisibleNodes && (invisible === "True" || invisible === "1")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -340,12 +342,14 @@ export class FormCompiler extends ViewCompiler {
|
||||
|
||||
if (slotContent && !isTextNode(slotContent)) {
|
||||
let isVisibleExpr;
|
||||
if (typeof invisible === "boolean") {
|
||||
isVisibleExpr = `${invisible ? false : true}`;
|
||||
if (!invisible || invisible === "False" || invisible === "0") {
|
||||
isVisibleExpr = "true";
|
||||
} else if (invisible === "True" || invisible === "1") {
|
||||
isVisibleExpr = "false";
|
||||
} else {
|
||||
isVisibleExpr = `!__comp__.evalDomainFromRecord(__comp__.props.record,${JSON.stringify(
|
||||
isVisibleExpr = `!__comp__.evaluateBooleanExpr(${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
)},__comp__.props.record.evalContext)`;
|
||||
}
|
||||
mainSlot.setAttribute("isVisible", isVisibleExpr);
|
||||
if (itemSpan > 0) {
|
||||
@@ -499,7 +503,7 @@ export class FormCompiler extends ViewCompiler {
|
||||
continue;
|
||||
}
|
||||
const invisible = getModifier(child, "invisible");
|
||||
if (this.isAlwaysInvisible(invisible, params)) {
|
||||
if (!params.compileInvisibleNodes && (invisible === "True" || invisible === "1")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -537,15 +541,17 @@ export class FormCompiler extends ViewCompiler {
|
||||
};
|
||||
}
|
||||
|
||||
let isVisible;
|
||||
if (typeof invisible === "boolean") {
|
||||
isVisible = `${!invisible}`;
|
||||
let isVisibleExpr;
|
||||
if (!invisible || invisible === "False" || invisible === "0") {
|
||||
isVisibleExpr = "true";
|
||||
} else if (invisible === "True" || invisible === "1") {
|
||||
isVisibleExpr = "false";
|
||||
} else {
|
||||
isVisible = `!__comp__.evalDomainFromRecord(__comp__.props.record,${JSON.stringify(
|
||||
isVisibleExpr = `!__comp__.evaluateBooleanExpr(${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
)},__comp__.props.record.evalContext)`;
|
||||
}
|
||||
pageSlot.setAttribute("isVisible", isVisible);
|
||||
pageSlot.setAttribute("isVisible", isVisibleExpr);
|
||||
|
||||
for (const contents of child.children) {
|
||||
append(pageSlot, this.compileNode(contents, { ...params, currentSlot: pageSlot }));
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
import { hasTouch } from "@web/core/browser/feature_detection";
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { makeContext } from "@web/core/context";
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { useDebugCategory } from "@web/core/debug/debug_context";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { SIZES } from "@web/core/ui/ui_service";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
import { createElement } from "@web/core/utils/xml";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { standardViewProps } from "@web/views/standard_view_props";
|
||||
@@ -48,7 +48,7 @@ export async function loadSubViews(
|
||||
if (!isX2Many(field)) {
|
||||
continue; // what follows only concerns x2many fields
|
||||
}
|
||||
if (fieldInfo.alwaysInvisible) {
|
||||
if (fieldInfo.invisible === "True" || fieldInfo.invisible === "1") {
|
||||
continue; // no need to fetch the sub view if the field is always invisible
|
||||
}
|
||||
if (!fieldInfo.field.useSubView) {
|
||||
@@ -110,6 +110,7 @@ export async function loadSubViews(
|
||||
|
||||
export class FormController extends Component {
|
||||
setup() {
|
||||
this.evaluateBooleanExpr = evaluateBooleanExpr;
|
||||
this.dialogService = useService("dialog");
|
||||
this.router = useService("router");
|
||||
this.user = useService("user");
|
||||
@@ -559,10 +560,6 @@ export class FormController extends Component {
|
||||
result["o_field_highlight"] = size < SIZES.SM || hasTouch();
|
||||
return result;
|
||||
}
|
||||
|
||||
evalDomainFromRecord(record, expr) {
|
||||
return evalDomain(expr, record.evalContext);
|
||||
}
|
||||
}
|
||||
|
||||
FormController.template = `web.FormView`;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { Notebook } from "@web/core/notebook/notebook";
|
||||
import { Setting } from "./setting/setting";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
|
||||
export class FormRenderer extends Component {
|
||||
setup() {
|
||||
this.evaluateBooleanExpr = evaluateBooleanExpr;
|
||||
const { archInfo, Compiler, record } = this.props;
|
||||
const { arch, xmlDoc } = archInfo;
|
||||
const templates = { FormRenderer: xmlDoc };
|
||||
@@ -79,10 +80,6 @@ export class FormRenderer extends Component {
|
||||
get shouldAutoFocus() {
|
||||
return !this.props.archInfo.disableAutofocus;
|
||||
}
|
||||
|
||||
evalDomainFromRecord(record, expr) {
|
||||
return evalDomain(expr, record.evalContext);
|
||||
}
|
||||
}
|
||||
|
||||
FormRenderer.template = xml`<t t-call="{{ templates.FormRenderer }}" t-call-context="{ __comp__: Object.assign(Object.create(this), { this: this }) }" />`;
|
||||
|
||||
@@ -9,10 +9,7 @@ export class Setting extends Component {
|
||||
setup() {
|
||||
if (this.props.fieldName) {
|
||||
this.fieldType = this.props.record.fields[this.props.fieldName].type;
|
||||
if (
|
||||
typeof this.props.fieldInfo.modifiers.readonly === "boolean" &&
|
||||
this.props.fieldInfo.modifiers.readonly === true
|
||||
) {
|
||||
if (this.props.fieldInfo.readonly === "True") {
|
||||
this.notMuttedLabel = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ export class GraphArchParser extends XMLParser {
|
||||
}
|
||||
archInfo.fieldAttrs[fieldName].string = string;
|
||||
}
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
if (modifiers.invisible === true) {
|
||||
if (node.getAttribute("invisible") === "True" || node.getAttribute("invisible") === "1") {
|
||||
if (!archInfo.fieldAttrs[fieldName]) {
|
||||
archInfo.fieldAttrs[fieldName] = {};
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ export class KanbanArchParser extends XMLParser {
|
||||
type: "button",
|
||||
id: button_id++,
|
||||
}))
|
||||
.filter((button) => button.modifiers.invisible !== true);
|
||||
.filter((button) => button.invisible !== "True" && button.invisible !== "1");
|
||||
return false;
|
||||
} else if (node.tagName === "control") {
|
||||
for (const childNode of node.children) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_d
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
import { CogMenu } from "@web/search/cog_menu/cog_menu";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { Layout } from "@web/search/layout";
|
||||
import { usePager } from "@web/search/pager_hook";
|
||||
import { SearchBar } from "@web/search/search_bar/search_bar";
|
||||
@@ -206,6 +207,10 @@ export class KanbanController extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
evalViewModifier(modifier) {
|
||||
return evaluateBooleanExpr(modifier, {context: this.props.context});
|
||||
};
|
||||
|
||||
async openRecord(record, mode) {
|
||||
const activeIds = this.model.root.records.map((datapoint) => datapoint.resId);
|
||||
this.props.selectRecord(record.resId, { activeIds, mode });
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
canQuickCreate="canQuickCreate"
|
||||
quickCreateState="quickCreateState"
|
||||
progressBarState="progressBarState"
|
||||
evalViewModifier.bind="evalViewModifier"
|
||||
/>
|
||||
</Layout>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { ColorList } from "@web/core/colorlist/colorlist";
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { registry } from "@web/core/registry";
|
||||
@@ -179,6 +179,7 @@ export function isHtmlEmpty(innerHTML = "") {
|
||||
|
||||
export class KanbanRecord extends Component {
|
||||
setup() {
|
||||
this.evaluateBooleanExpr = evaluateBooleanExpr;
|
||||
this.action = useService("action");
|
||||
this.dialog = useService("dialog");
|
||||
this.notification = useService("notification");
|
||||
@@ -243,10 +244,6 @@ export class KanbanRecord extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
evalDomainFromRecord(record, expr) {
|
||||
return evalDomain(expr, record.evalContext);
|
||||
}
|
||||
|
||||
getRecordClasses() {
|
||||
const { archInfo, canResequence, forceGlobalClick, record, progressBarState } = this.props;
|
||||
const classes = ["o_kanban_record d-flex"];
|
||||
|
||||
@@ -21,7 +21,7 @@ const DEFAULT_QUICK_CREATE_VIEW = {
|
||||
// note: the required modifier is written in the format returned by the server
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="display_name" placeholder="Title" modifiers='{"required": true}' />
|
||||
<field name="display_name" placeholder="Title" required="True" />
|
||||
</form>`,
|
||||
};
|
||||
const DEFAULT_QUICK_CREATE_FIELDS = {
|
||||
|
||||
@@ -51,6 +51,7 @@ export class KanbanRenderer extends Component {
|
||||
"deleteRecord",
|
||||
"openRecord",
|
||||
"readonly",
|
||||
"evalViewModifier",
|
||||
"forceGlobalClick?",
|
||||
"noContentHelp?",
|
||||
"scrollTop?",
|
||||
@@ -221,14 +222,13 @@ export class KanbanRenderer extends Component {
|
||||
const fieldNodes = Object.values(this.props.archInfo.fieldNodes).filter(
|
||||
(fieldNode) => fieldNode.name === groupByField.name
|
||||
);
|
||||
let isReadonly;
|
||||
let isReadonly = this.props.list.fields[groupByField.name].readonly;
|
||||
if (
|
||||
!isReadonly &&
|
||||
fieldNodes.length &&
|
||||
fieldNodes.some((fieldNode) => "readonly" in fieldNode.modifiers)
|
||||
fieldNodes.some((fieldNode) => "readonly" in fieldNode)
|
||||
) {
|
||||
isReadonly = fieldNodes.every((fieldNode) => fieldNode.modifiers.readonly === true);
|
||||
} else {
|
||||
isReadonly = this.props.list.fields[groupByField.name].readonly;
|
||||
isReadonly = fieldNodes.every((fieldNode) => fieldNode.readonly === 'True');
|
||||
}
|
||||
return !isReadonly && this.isMovableField(groupByField);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
/** @odoo-module */
|
||||
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { XMLParser } from "@web/core/utils/xml";
|
||||
import { stringToOrderBy } from "@web/search/utils/order_by";
|
||||
import { Field } from "@web/views/fields/field";
|
||||
import { archParseBoolean, getActiveActions, getDecoration, processButton } from "@web/views/utils";
|
||||
import { encodeObjectForTemplate } from "@web/views/view_compiler";
|
||||
import { combineModifiers } from "@web/model/relational_model/utils";
|
||||
import { Widget } from "@web/views/widgets/widget";
|
||||
|
||||
export class GroupListArchParser extends XMLParser {
|
||||
@@ -36,10 +37,6 @@ export class GroupListArchParser extends XMLParser {
|
||||
}
|
||||
|
||||
export class ListArchParser extends XMLParser {
|
||||
isColumnVisible(columnInvisibleModifier) {
|
||||
return columnInvisibleModifier !== true;
|
||||
}
|
||||
|
||||
parseFieldNode(node, models, modelName) {
|
||||
return Field.parseFieldNode(node, models, modelName, "list");
|
||||
}
|
||||
@@ -77,25 +74,24 @@ export class ListArchParser extends XMLParser {
|
||||
buttonGroup = undefined;
|
||||
}
|
||||
if (node.tagName === "button") {
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
if (this.isColumnVisible(modifiers.column_invisible)) {
|
||||
const button = {
|
||||
...this.processButton(node),
|
||||
defaultRank: "btn-link",
|
||||
type: "button",
|
||||
id: buttonId++,
|
||||
const button = {
|
||||
...this.processButton(node),
|
||||
defaultRank: "btn-link",
|
||||
type: "button",
|
||||
id: buttonId++,
|
||||
};
|
||||
if (buttonGroup) {
|
||||
buttonGroup.buttons.push(button);
|
||||
buttonGroup.column_invisible = combineModifiers(buttonGroup.column_invisible, node.getAttribute('column_invisible'), "AND");
|
||||
} else {
|
||||
buttonGroup = {
|
||||
id: `column_${nextId++}`,
|
||||
type: "button_group",
|
||||
buttons: [button],
|
||||
hasLabel: false,
|
||||
column_invisible: node.getAttribute('column_invisible'),
|
||||
};
|
||||
if (buttonGroup) {
|
||||
buttonGroup.buttons.push(button);
|
||||
} else {
|
||||
buttonGroup = {
|
||||
id: `column_${nextId++}`,
|
||||
type: "button_group",
|
||||
buttons: [button],
|
||||
hasLabel: false,
|
||||
};
|
||||
columns.push(buttonGroup);
|
||||
}
|
||||
columns.push(buttonGroup);
|
||||
}
|
||||
} else if (node.tagName === "field") {
|
||||
const fieldInfo = this.parseFieldNode(node, models, modelName);
|
||||
@@ -108,20 +104,18 @@ export class ListArchParser extends XMLParser {
|
||||
if (fieldInfo.isHandle) {
|
||||
handleField = fieldInfo.name;
|
||||
}
|
||||
if (this.isColumnVisible(fieldInfo.modifiers.column_invisible)) {
|
||||
const label = fieldInfo.field.label;
|
||||
columns.push({
|
||||
...fieldInfo,
|
||||
id: `column_${nextId++}`,
|
||||
className: node.getAttribute("class"), // for oe_edit_only and oe_read_only
|
||||
optional: node.getAttribute("optional") || false,
|
||||
type: "field",
|
||||
hasLabel: !(
|
||||
archParseBoolean(fieldInfo.attrs.nolabel) || fieldInfo.field.noLabel
|
||||
),
|
||||
label: (fieldInfo.widget && label && label.toString()) || fieldInfo.string,
|
||||
});
|
||||
}
|
||||
const label = fieldInfo.field.label;
|
||||
columns.push({
|
||||
...fieldInfo,
|
||||
id: `column_${nextId++}`,
|
||||
className: node.getAttribute("class"), // for oe_edit_only and oe_read_only
|
||||
optional: node.getAttribute("optional") || false,
|
||||
type: "field",
|
||||
hasLabel: !(
|
||||
archParseBoolean(fieldInfo.attrs.nolabel) || fieldInfo.field.noLabel
|
||||
),
|
||||
label: (fieldInfo.widget && label && label.toString()) || fieldInfo.string,
|
||||
});
|
||||
return false;
|
||||
} else if (node.tagName === "widget") {
|
||||
const widgetInfo = this.parseWidgetNode(node);
|
||||
@@ -155,7 +149,7 @@ export class ListArchParser extends XMLParser {
|
||||
};
|
||||
return false;
|
||||
} else if (node.tagName === "header") {
|
||||
// AAB: not sure we need to handle invisible="1" button as the usecase seems way
|
||||
// AAB: not sure we need to handle invisible="True" button as the usecase seems way
|
||||
// less relevant than for fields (so for buttons, relying on the modifiers logic
|
||||
// that applies later on could be enough, even if the value is always true)
|
||||
headerButtons = [...node.children]
|
||||
@@ -163,8 +157,7 @@ export class ListArchParser extends XMLParser {
|
||||
...this.processButton(node),
|
||||
type: "button",
|
||||
id: buttonId++,
|
||||
}))
|
||||
.filter((button) => button.modifiers.invisible !== true);
|
||||
}));
|
||||
return false;
|
||||
} else if (node.tagName === "control") {
|
||||
for (const childNode of node.children) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { ConfirmationDialog } from "@web/core/confirmation_dialog/confirmation_dialog";
|
||||
import { download } from "@web/core/network/download";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { unique } from "@web/core/utils/arrays";
|
||||
import { useService, useBus } from "@web/core/utils/hooks";
|
||||
import { omit } from "@web/core/utils/objects";
|
||||
@@ -380,6 +380,10 @@ export class ListController extends Component {
|
||||
this.model.root.selectDomain(false);
|
||||
}
|
||||
|
||||
evalViewModifier(modifier) {
|
||||
return evaluateBooleanExpr(modifier, this.model.root.evalContext);
|
||||
}
|
||||
|
||||
get className() {
|
||||
return this.props.className;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<t t-set-slot="control-panel-selection-actions">
|
||||
<div t-if="nbSelected" class="d-flex gap-1">
|
||||
<t t-call="web.ListView.Selection"/>
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
|
||||
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id" t-if="!evalViewModifier(button.invisible)">
|
||||
<MultiRecordViewButton
|
||||
t-if="button.display !== 'always'"
|
||||
list="model.root"
|
||||
@@ -69,7 +69,18 @@
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-component="props.Renderer" list="model.root" activeActions="activeActions" archInfo="archInfo" allowSelectors="props.allowSelectors" editable="editable" openRecord.bind="openRecord" noContentHelp="props.info.noContentHelp" onAdd.bind="createRecord" onOptionalFieldsChanged.bind="onOptionalFieldsChanged"/>
|
||||
<t t-component="props.Renderer"
|
||||
list="model.root"
|
||||
activeActions="activeActions"
|
||||
archInfo="archInfo"
|
||||
allowSelectors="props.allowSelectors"
|
||||
editable="editable"
|
||||
openRecord.bind="openRecord"
|
||||
noContentHelp="props.info.noContentHelp"
|
||||
onAdd.bind="createRecord"
|
||||
onOptionalFieldsChanged.bind="onOptionalFieldsChanged"
|
||||
evalViewModifier.bind="evalViewModifier"
|
||||
/>
|
||||
</Layout>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
|
||||
import { browser } from "@web/core/browser/browser";
|
||||
import { CheckBox } from "@web/core/checkbox/checkbox";
|
||||
import { Domain, evalDomain } from "@web/core/domain";
|
||||
import { Dropdown } from "@web/core/dropdown/dropdown";
|
||||
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
||||
import { getActiveHotkey } from "@web/core/hotkeys/hotkey_service";
|
||||
import { Pager } from "@web/core/pager/pager";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { useBus, useService } from "@web/core/utils/hooks";
|
||||
import { useSortable } from "@web/core/utils/sortable";
|
||||
@@ -15,6 +14,7 @@ import { getTabableElements } from "@web/core/utils/ui";
|
||||
import { Field, getPropertyFieldInfo } from "@web/views/fields/field";
|
||||
import { getTooltipInfo } from "@web/views/fields/field_tooltip";
|
||||
import { getClassNameFromDecoration } from "@web/views/utils";
|
||||
import { combineModifiers } from "@web/model/relational_model/utils";
|
||||
import { ViewButton } from "@web/views/view_button/view_button";
|
||||
import { useBounceButton } from "@web/views/view_hook";
|
||||
import { Widget } from "@web/views/widgets/widget";
|
||||
@@ -228,7 +228,13 @@ export class ListRenderer extends Component {
|
||||
if (list.isGrouped && col.widget === "handle") {
|
||||
return false; // no handle column if the list is grouped
|
||||
}
|
||||
return !col.optional || this.optionalActiveFields[col.name];
|
||||
if (col.optional && !this.optionalActiveFields[col.name]) {
|
||||
return false;
|
||||
}
|
||||
if (this.evalColumnInvisible(col.column_invisible)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -263,6 +269,7 @@ export class ListRenderer extends Component {
|
||||
return {
|
||||
...getPropertyFieldInfo(propertyField),
|
||||
id: `${column.id}_${propertyField.name}`,
|
||||
column_invisible: combineModifiers(propertyField.column_invisible, column.column_invisible, 'OR'),
|
||||
classNames: column.classNames,
|
||||
optional: "hide",
|
||||
type: "field",
|
||||
@@ -274,12 +281,9 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
getFieldProps(record, column) {
|
||||
if (this.isCellReadonly(column, record) || this.isRecordReadonly(record)) {
|
||||
return {
|
||||
readonly: true,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
return {
|
||||
readonly: this.isCellReadonly(column, record) || this.isRecordReadonly(record),
|
||||
};
|
||||
}
|
||||
|
||||
// The following code manipulates the DOM directly to avoid having to wait for a
|
||||
@@ -565,7 +569,9 @@ export class ListRenderer extends Component {
|
||||
get optionalFieldGroups() {
|
||||
const propertyGroups = {};
|
||||
const optionalFields = [];
|
||||
for (const col of this.allColumns.filter((col) => col.optional)) {
|
||||
const optionalColumns = this.allColumns.filter((col) =>
|
||||
col.optional && !this.evalColumnInvisible(col.column_invisible));
|
||||
for (const col of optionalColumns) {
|
||||
const optionalField = {
|
||||
label: col.label,
|
||||
name: col.name,
|
||||
@@ -589,7 +595,8 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
get hasOptionalFields() {
|
||||
return this.allColumns.some((c) => c.optional);
|
||||
return this.allColumns.some((col) =>
|
||||
col.optional && !this.evalColumnInvisible(col.column_invisible));
|
||||
}
|
||||
|
||||
get displayOptionalFields() {
|
||||
@@ -796,7 +803,7 @@ export class ListRenderer extends Component {
|
||||
getRowClass(record) {
|
||||
// classnames coming from decorations
|
||||
const classNames = this.props.archInfo.decorations
|
||||
.filter((decoration) => evaluateExpr(decoration.condition, record.evalContext))
|
||||
.filter((decoration) => evaluateBooleanExpr(decoration.condition, record.evalContext))
|
||||
.map((decoration) => decoration.class);
|
||||
if (record.selected) {
|
||||
classNames.push("table-info");
|
||||
@@ -840,8 +847,7 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
const classNames = [...this.cellClassByColumn[column.id]];
|
||||
if (column.type === "field") {
|
||||
const { required } = column.modifiers;
|
||||
if (required && evalDomain(required, record.evalContext)) {
|
||||
if (evaluateBooleanExpr(column.required, record.evalContext)) {
|
||||
classNames.push("o_required_modifier");
|
||||
}
|
||||
if (record.isFieldInvalid(column.name)) {
|
||||
@@ -856,7 +862,7 @@ export class ListRenderer extends Component {
|
||||
// only handle the text-decoration.
|
||||
const { decorations } = column;
|
||||
for (const decoName in decorations) {
|
||||
if (evaluateExpr(decorations[decoName], record.evalContext)) {
|
||||
if (evaluateBooleanExpr(decorations[decoName], record.evalContext)) {
|
||||
classNames.push(getClassNameFromDecoration(decoName));
|
||||
}
|
||||
}
|
||||
@@ -875,10 +881,10 @@ export class ListRenderer extends Component {
|
||||
}
|
||||
|
||||
isCellReadonly(column, record) {
|
||||
return (
|
||||
return !!(
|
||||
this.isRecordReadonly(record) ||
|
||||
(column.relatedPropertyField && record.selected && record.model.multiEdit) ||
|
||||
evalDomain(column.modifiers.readonly, record.evalContext)
|
||||
evaluateBooleanExpr(column.readonly, record.evalContext)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -910,8 +916,12 @@ export class ListRenderer extends Component {
|
||||
return getFormattedValue(record, fieldName, column.attrs);
|
||||
}
|
||||
|
||||
evalModifier(modifier, record) {
|
||||
return !!(modifier && new Domain(modifier).contains(record.evalContext));
|
||||
evalInvisible(invisible, record) {
|
||||
return evaluateBooleanExpr(invisible, record.evalContext);
|
||||
}
|
||||
|
||||
evalColumnInvisible(columnInvisible) {
|
||||
return this.props.evalViewModifier(columnInvisible);
|
||||
}
|
||||
|
||||
getGroupDisplayName(group) {
|
||||
@@ -2106,6 +2116,7 @@ ListRenderer.props = [
|
||||
"list",
|
||||
"archInfo",
|
||||
"openRecord",
|
||||
"evalViewModifier",
|
||||
"onAdd?",
|
||||
"cycleOnTab?",
|
||||
"allowSelectors?",
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<t t-esc="getGroupDisplayName(group)"/> (<t t-esc="group.count"/>)
|
||||
<div t-if="(groupByButtons[group.groupByField.name] and !group.isFolded)" class="o_group_buttons">
|
||||
<t t-foreach="groupByButtons[group.groupByField.name]" t-as="button" t-key="button.id">
|
||||
<t t-if="!evalModifier(button.modifiers.invisible, group.record)">
|
||||
<t t-if="!evalInvisible(button.invisible, group.record)">
|
||||
<t t-if="button.clickParams.type === 'edit'">
|
||||
<button t-att-title="button.title" class="btn" t-on-click="() => this.editGroupRecord(group)" tabindex="-1">
|
||||
<i t-attf-class="fa fa-fw {{button.icon}} o_button_icon"/>
|
||||
@@ -226,7 +226,7 @@
|
||||
</td>
|
||||
<t t-foreach="getColumns(record)" t-as="column" t-key="column.id">
|
||||
<t t-if="column.type === 'field'">
|
||||
<t t-set="isInvisible" t-value="evalModifier(column.modifiers.invisible, record) or !(column.name in record.data)"/>
|
||||
<t t-set="isInvisible" t-value="evalInvisible(column.invisible, record) or !(column.name in record.data)"/>
|
||||
<td t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev, group, record)"
|
||||
class="o_data_cell cursor-pointer"
|
||||
t-att-class="getCellClass(column, record)"
|
||||
@@ -244,7 +244,7 @@
|
||||
<t t-if="column.type === 'button_group'">
|
||||
<td t-on-keydown.synthetic="(ev) => this.onCellKeydown(ev, group, record)" class="o_data_cell cursor-pointer" t-att-class="getCellClass(column, record)" t-on-click="(ev) => this.onButtonCellClicked(record, column, ev)" tabindex="-1">
|
||||
<t t-foreach="column.buttons" t-as="button" t-key="button.id">
|
||||
<ViewButton t-if="!evalModifier(button.modifiers.invisible, record)"
|
||||
<ViewButton t-if="!evalInvisible(button.invisible, record)"
|
||||
className="button.className"
|
||||
clickParams="button.clickParams"
|
||||
defaultRank="button.defaultRank"
|
||||
|
||||
@@ -42,8 +42,7 @@ export class PivotArchParser extends XMLParser {
|
||||
if (node.hasAttribute("string")) {
|
||||
archInfo.fieldAttrs[fieldName].string = node.getAttribute("string");
|
||||
}
|
||||
const modifiers = JSON.parse(node.getAttribute("modifiers") || "{}");
|
||||
if (modifiers.invisible === true) {
|
||||
if (node.getAttribute("invisible") === "True" || node.getAttribute("invisible") === "1") {
|
||||
archInfo.fieldAttrs[fieldName].isInvisible = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { _t } from "@web/core/l10n/translation";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { combineModifiers } from "@web/model/relational_model/utils";
|
||||
|
||||
export const X2M_TYPES = ["one2many", "many2many"];
|
||||
const NUMERIC_TYPES = ["integer", "float", "monetary"];
|
||||
@@ -27,7 +28,6 @@ export const BUTTON_CLICK_PARAMS = [
|
||||
"special",
|
||||
"effect",
|
||||
"help",
|
||||
"modifiers",
|
||||
// WOWL SAD: is adding the support for debounce attribute here justified or should we
|
||||
// just override compileButton in kanban compiler to add the debounce?
|
||||
"debounce",
|
||||
@@ -61,7 +61,7 @@ export function addDependencies(deps, activeFields, fields) {
|
||||
attrs: {},
|
||||
options: {},
|
||||
...dependency,
|
||||
modifiers: { invisible: true, ...dependency.modifiers },
|
||||
invisible: "True",
|
||||
};
|
||||
}
|
||||
if (!(name in fields)) {
|
||||
@@ -268,9 +268,12 @@ export function processButton(node) {
|
||||
title: node.getAttribute("title") || undefined,
|
||||
string: node.getAttribute("string") || undefined,
|
||||
options: JSON.parse(node.getAttribute("options") || "{}"),
|
||||
modifiers: JSON.parse(node.getAttribute("modifiers") || "{}"),
|
||||
display: node.getAttribute("display") || "selection",
|
||||
clickParams,
|
||||
column_invisible: node.getAttribute("column_invisible"),
|
||||
invisible: combineModifiers(node.getAttribute("column_invisible"), node.getAttribute("invisible"), "OR"),
|
||||
readonly: node.getAttribute("readonly"),
|
||||
required: node.getAttribute("required"),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { KeepLast } from "@web/core/utils/concurrency";
|
||||
import { useService } from "@web/core/utils/hooks";
|
||||
@@ -334,7 +334,7 @@ export class View extends Component {
|
||||
if ("useSampleModel" in props) {
|
||||
viewProps.useSampleModel = props.useSampleModel;
|
||||
} else if (sample) {
|
||||
viewProps.useSampleModel = Boolean(evaluateExpr(sample));
|
||||
viewProps.useSampleModel = evaluateBooleanExpr(sample);
|
||||
}
|
||||
|
||||
for (const key in props) {
|
||||
|
||||
@@ -51,7 +51,10 @@ export class ViewButton extends Component {
|
||||
string: this.props.string,
|
||||
help: this.clickParams.help,
|
||||
context: this.clickParams.context,
|
||||
modifiers: this.clickParams.modifiers,
|
||||
invisible: this.props.attrs?.invisible,
|
||||
column_invisible: this.props.attrs?.column_invisible,
|
||||
readonly: this.props.attrs?.readonly,
|
||||
required: this.props.attrs?.required,
|
||||
special: this.clickParams.special,
|
||||
type: this.clickParams.type,
|
||||
name: this.clickParams.name,
|
||||
|
||||
@@ -48,9 +48,21 @@
|
||||
<span class="o-tooltip--technical--title">Context:</span>
|
||||
<t t-esc="button.context || context"/>
|
||||
</li>
|
||||
<li t-if="button.modifiers" data-item="modifiers">
|
||||
<span class="o-tooltip--technical--title">Modifiers:</span>
|
||||
<t t-esc="button.modifiers"/>
|
||||
<li t-if="'invisible' in button" data-item="invisible">
|
||||
<span class="o-tooltip--technical--title">Invisible:</span>
|
||||
<t t-esc="button.invisible"/>
|
||||
</li>
|
||||
<li t-if="'column_invisible' in button" data-item="column_invisible">
|
||||
<span class="o-tooltip--technical--title">Column invisible:</span>
|
||||
<t t-esc="button.column_invisible"/>
|
||||
</li>
|
||||
<li t-if="'required' in button" data-item="required">
|
||||
<span class="o-tooltip--technical--title">Required:</span>
|
||||
<t t-esc="button.required"/>
|
||||
</li>
|
||||
<li t-if="'readonly' in button" data-item="readonly">
|
||||
<span class="o-tooltip--technical--title">Readonly:</span>
|
||||
<t t-esc="button.readonly"/>
|
||||
</li>
|
||||
<li t-if="button.special" data-item="special">
|
||||
<span class="o-tooltip--technical--title">Special:</span>
|
||||
|
||||
@@ -140,11 +140,7 @@ export function encodeObjectForTemplate(obj) {
|
||||
* @returns {boolean | boolean[]}
|
||||
*/
|
||||
export function getModifier(el, modifierName) {
|
||||
// cf python side def transfer_node_to_modifiers
|
||||
// modifiers' string are evaluated to their boolean or array form
|
||||
const modifiers = JSON.parse(el.getAttribute("modifiers") || "{}");
|
||||
const mod = modifierName in modifiers ? modifiers[modifierName] : false;
|
||||
return typeof mod !== "boolean" ? mod : !!mod;
|
||||
return el.getAttribute(modifierName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -229,16 +225,16 @@ export class ViewCompiler {
|
||||
* @returns {Element}
|
||||
*/
|
||||
applyInvisible(invisible, compiled, params) {
|
||||
if (!invisible) {
|
||||
if (!invisible || invisible === "False") {
|
||||
return compiled;
|
||||
}
|
||||
if (typeof invisible === "boolean") {
|
||||
if (invisible === "True" || invisible === "1") {
|
||||
return;
|
||||
}
|
||||
const recordExpr = params.recordExpr || "__comp__.props.record";
|
||||
let isVisileExpr = `!__comp__.evalDomainFromRecord(${recordExpr},${JSON.stringify(
|
||||
let isVisileExpr = `!__comp__.evaluateBooleanExpr(${JSON.stringify(
|
||||
invisible
|
||||
)})`;
|
||||
)},${recordExpr}.evalContext)`;
|
||||
if (compiled.hasAttribute("t-if")) {
|
||||
const formerTif = compiled.getAttribute("t-if");
|
||||
isVisileExpr = `( ${formerTif} ) and ${isVisileExpr}`;
|
||||
@@ -275,7 +271,7 @@ export class ViewCompiler {
|
||||
let invisible;
|
||||
if (evalInvisible) {
|
||||
invisible = getModifier(node, "invisible");
|
||||
if (this.isAlwaysInvisible(invisible, params)) {
|
||||
if (!params.compileInvisibleNodes && (invisible === "True" || invisible === "1")) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -394,7 +390,7 @@ export class ViewCompiler {
|
||||
*/
|
||||
compileGenericNode(el, params) {
|
||||
const compiled = createElement(el.nodeName.toLowerCase());
|
||||
const metaAttrs = ["modifiers", "attrs", "invisible", "readonly"];
|
||||
const metaAttrs = ["column_invisible", "invisible", "readonly", "required"];
|
||||
for (const attr of el.attributes) {
|
||||
if (metaAttrs.includes(attr.name)) {
|
||||
continue;
|
||||
@@ -429,15 +425,6 @@ export class ViewCompiler {
|
||||
return assignOwlDirectives(widget, el);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {any} invisibleModifer
|
||||
* @param {{ enableInvisible?: boolean }} params
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAlwaysInvisible(invisibleModifer, params) {
|
||||
return !params.enableInvisible && typeof invisibleModifer === "boolean" && invisibleModifer;
|
||||
}
|
||||
|
||||
validateNode(node) {
|
||||
// detect attributes not in whitelist, starting with t-
|
||||
const attributes = Object.values(node.attributes).map((attr) => attr.name);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import { evalDomain } from "@web/core/domain";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
|
||||
import { Component, xml } from "@odoo/owl";
|
||||
@@ -45,8 +44,7 @@ export class Widget extends Component {
|
||||
let propsFromNode = {};
|
||||
if (this.props.widgetInfo) {
|
||||
const widgetInfo = this.props.widgetInfo;
|
||||
const modifiers = widgetInfo.modifiers || {};
|
||||
readonlyFromModifiers = evalDomain(modifiers.readonly, evalContext);
|
||||
readonlyFromModifiers = evaluateBooleanExpr(widgetInfo.attrs.readonly, evalContext);
|
||||
propsFromNode = this.widget.extractProps ? this.widget.extractProps(widgetInfo) : {};
|
||||
}
|
||||
|
||||
@@ -67,7 +65,6 @@ Widget.parseWidgetNode = function (node) {
|
||||
const widget = viewWidgetRegistry.get(name);
|
||||
const widgetInfo = {
|
||||
name,
|
||||
modifiers: {},
|
||||
widget,
|
||||
options: {},
|
||||
attrs: {},
|
||||
@@ -78,9 +75,7 @@ Widget.parseWidgetNode = function (node) {
|
||||
// avoid adding name and widget to attrs
|
||||
continue;
|
||||
}
|
||||
if (name === "modifiers") {
|
||||
widgetInfo.modifiers = JSON.parse(value);
|
||||
} else if (name === "options") {
|
||||
if (name === "options") {
|
||||
widgetInfo.options = evaluateExpr(value);
|
||||
} else if (!name.startsWith("t-att")) {
|
||||
// all other (non dynamic) attributes
|
||||
|
||||
@@ -333,7 +333,7 @@ QUnit.module("py", {}, () => {
|
||||
QUnit.test("throws when period negative", (assert) => {
|
||||
const matcher = (errorMessage) => {
|
||||
return function match(err) {
|
||||
return err.message === errorMessage;
|
||||
return err.message.includes(errorMessage);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { evaluateExpr, evaluateBooleanExpr } from "@web/core/py_js/py";
|
||||
|
||||
QUnit.module("py", {}, () => {
|
||||
QUnit.module("interpreter", () => {
|
||||
@@ -29,6 +29,8 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(evaluateExpr('""'), "");
|
||||
assert.strictEqual(evaluateExpr('"foo"'), "foo");
|
||||
assert.strictEqual(evaluateExpr("'foo'"), "foo");
|
||||
assert.strictEqual(evaluateExpr("'FOO'.lower()"), "foo");
|
||||
assert.strictEqual(evaluateExpr("'foo'.upper()"), "FOO");
|
||||
});
|
||||
|
||||
QUnit.test("boolean", (assert) => {
|
||||
@@ -96,6 +98,7 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(evaluateExpr("not False"), true);
|
||||
assert.strictEqual(evaluateExpr("not foo", { foo: false }), true);
|
||||
assert.strictEqual(evaluateExpr("not None"), true);
|
||||
assert.strictEqual(evaluateExpr("not []"), true);
|
||||
assert.strictEqual(evaluateExpr("True == False or True == True"), true);
|
||||
assert.strictEqual(evaluateExpr("False == True and False"), false);
|
||||
});
|
||||
@@ -123,6 +126,7 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(evaluateExpr('None or "bar"'), "bar");
|
||||
assert.strictEqual(evaluateExpr("False or None"), null);
|
||||
assert.strictEqual(evaluateExpr("0 or 1"), 1);
|
||||
assert.strictEqual(evaluateExpr("[] or False"), false);
|
||||
});
|
||||
|
||||
QUnit.module("values from context");
|
||||
@@ -321,6 +325,22 @@ QUnit.module("py", {}, () => {
|
||||
assert.strictEqual(evaluateExpr("{'a': 1}.get('b', 54)"), 54);
|
||||
});
|
||||
|
||||
QUnit.test("can get values from values 'context'", (assert) => {
|
||||
assert.strictEqual(evaluateExpr("context.get('a')", { context: { a: 123 } }), 123);
|
||||
const values = { context: { a: { b: { c: 321 } } } };
|
||||
assert.strictEqual(evaluateExpr("context.get('a').b.c", values), 321);
|
||||
assert.strictEqual(evaluateExpr("context.get('a', {'e': 5}).b.c", values), 321);
|
||||
assert.strictEqual(evaluateExpr("context.get('d', 3)", values), 3);
|
||||
assert.strictEqual(evaluateExpr("context.get('d', {'e': 5})['e']", values), 5);
|
||||
});
|
||||
|
||||
QUnit.test("can check if a key is in the 'context'", (assert) => {
|
||||
assert.strictEqual(evaluateExpr("'a' in context", { context: { a: 123 } }), true);
|
||||
assert.strictEqual(evaluateExpr("'a' in context", { context: { b: 123 } }), false);
|
||||
assert.strictEqual(evaluateExpr("'a' not in context", { context: { a: 123 } }), false);
|
||||
assert.strictEqual(evaluateExpr("'a' not in context", { context: { b: 123 } }), true);
|
||||
});
|
||||
|
||||
QUnit.module("objects");
|
||||
|
||||
QUnit.test("can read values from object", (assert) => {
|
||||
@@ -350,5 +370,42 @@ QUnit.module("py", {}, () => {
|
||||
QUnit.test("tuple in list", (assert) => {
|
||||
assert.deepEqual(evaluateExpr("[(1 + 2,'foo', True)]"), [[3, "foo", true]]);
|
||||
});
|
||||
|
||||
QUnit.module("evaluate to boolean");
|
||||
|
||||
QUnit.test("simple expression", (assert) => {
|
||||
assert.strictEqual(evaluateBooleanExpr("12"), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("0"), false);
|
||||
assert.strictEqual(evaluateBooleanExpr("0 + 3 - 1"), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("0 + 3 - 1 - 2"), false);
|
||||
assert.strictEqual(evaluateBooleanExpr('"foo"'), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("[1]"), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("[]"), false);
|
||||
});
|
||||
|
||||
QUnit.test("use contextual values", (assert) => {
|
||||
assert.strictEqual(evaluateBooleanExpr("a", {a: 12}), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("a", {a: 0}), false);
|
||||
assert.strictEqual(evaluateBooleanExpr("0 + 3 - a", {a: 1}), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("0 + 3 - a - 2", {a: 1}), false);
|
||||
assert.strictEqual(evaluateBooleanExpr("0 + 3 - a - b", {a: 1, b: 2}), false);
|
||||
assert.strictEqual(evaluateBooleanExpr('a', {a: "foo"}), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("a", {a: [1]}), true);
|
||||
assert.strictEqual(evaluateBooleanExpr("a", {a: []}), false);
|
||||
});
|
||||
|
||||
QUnit.test("throw if has missing value", (assert) => {
|
||||
assert.throws(() => evaluateBooleanExpr("a", {b: 0}));
|
||||
assert.strictEqual(evaluateBooleanExpr("1 or a"), true); // do not throw (lazy value)
|
||||
assert.throws(() => evaluateBooleanExpr("0 or a"));
|
||||
assert.throws(() => evaluateBooleanExpr("a or b", {b: true}));
|
||||
assert.throws(() => evaluateBooleanExpr("a and b", {b: true}));
|
||||
assert.throws(() => evaluateBooleanExpr("a()"));
|
||||
assert.throws(() => evaluateBooleanExpr("a[0]"));
|
||||
assert.throws(() => evaluateBooleanExpr("a.b"));
|
||||
assert.throws(() => evaluateBooleanExpr("0 + 3 - a", {b: 1}));
|
||||
assert.throws(() => evaluateBooleanExpr("0 + 3 - a - 2", {b: 1}));
|
||||
assert.throws(() => evaluateBooleanExpr("0 + 3 - a - b", {b: 2}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
serializeDate,
|
||||
serializeDateTime,
|
||||
} from "@web/core/l10n/dates";
|
||||
import { evaluateExpr } from "@web/core/py_js/py";
|
||||
import { registry } from "@web/core/registry";
|
||||
import { intersection, unique } from "@web/core/utils/arrays";
|
||||
import { deepCopy, pick } from "@web/core/utils/objects";
|
||||
@@ -319,7 +318,6 @@ export class MockServer {
|
||||
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 = {};
|
||||
@@ -332,8 +330,6 @@ export class MockServer {
|
||||
}
|
||||
const editableView = editable && this._editableNode(doc, modelName);
|
||||
const onchangeAbleView = this._onchangeAbleView(doc);
|
||||
const modifiersFromModel = this._modifiersFromModel(doc);
|
||||
const inTreeView = ["tree", "list"].includes(doc.tagName);
|
||||
const inFormView = doc.tagName === "form";
|
||||
// mock _postprocess_access_rights
|
||||
const isBaseModel = !context.base_model_name || modelName === context.base_model_name;
|
||||
@@ -350,90 +346,30 @@ export class MockServer {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
return false;
|
||||
}
|
||||
const modifiers = {};
|
||||
['required', 'readonly', 'invisible', 'column_invisible'].forEach((attr) => {
|
||||
const value = node.getAttribute(attr);
|
||||
if (value === '1' || value === 'true') {
|
||||
node.setAttribute(attr, 'True');
|
||||
}
|
||||
});
|
||||
const isField = node.tagName === "field";
|
||||
const isGroupby = node.tagName === "groupby";
|
||||
if (isField) {
|
||||
const fieldName = node.getAttribute("name");
|
||||
fieldNodes[fieldName] = {
|
||||
node,
|
||||
isInvisible: node.getAttribute("invisible"),
|
||||
isInvisible: node.getAttribute("invisible") === "True",
|
||||
isEditable: editableView && this._editableNode(node, modelName),
|
||||
};
|
||||
// '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 ?
|
||||
|
||||
modifiersFromModel.forEach((attr) => {
|
||||
stateExceptions[attr] = [];
|
||||
defaultValues[attr] = !!field[attr];
|
||||
});
|
||||
// LPE: what is this ?
|
||||
/* for (const [state, modifs] of Object.entries(field.states || {})) {
|
||||
modifs.forEach((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");
|
||||
node.removeAttribute("attrs");
|
||||
if (attrs) {
|
||||
attrs = evaluateExpr(attrs);
|
||||
Object.assign(modifiers, attrs);
|
||||
}
|
||||
const states = node.getAttribute("states");
|
||||
node.removeAttribute("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);
|
||||
node.removeAttribute(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;
|
||||
}
|
||||
@@ -480,8 +416,7 @@ export class MockServer {
|
||||
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
|
||||
// this is hackhish, but _getView modifies the subview document in place
|
||||
const { models } = this._getView({
|
||||
models: params.models,
|
||||
arch: childNode,
|
||||
@@ -553,16 +488,7 @@ export class MockServer {
|
||||
case "field": {
|
||||
const fname = node.getAttribute("name");
|
||||
const field = this.models[modelName].fields[fname];
|
||||
return (
|
||||
(!field.readonly ||
|
||||
(field.states &&
|
||||
Object.values(field.states).some((item) =>
|
||||
item.includes("readonly")
|
||||
))) &&
|
||||
(!["1", "True"].includes(node.getAttribute("readonly")) ||
|
||||
Object.keys(evaluateExpr(node.getAttribute("attrs") || "{}") || {}).length >
|
||||
0)
|
||||
);
|
||||
return !field.readonly && node.getAttribute("readonly") !== "True" && node.getAttribute("readonly") !== "1";
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
@@ -579,14 +505,6 @@ export class MockServer {
|
||||
}
|
||||
}
|
||||
|
||||
_modifiersFromModel(node) {
|
||||
const modifiersNames = ["invisible"];
|
||||
if (["kanban", "tree", "form"].includes(node.tagName)) {
|
||||
modifiersNames.push(...["readonly", "required"]);
|
||||
}
|
||||
return modifiersNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an Object representing a record to actual return Object of the
|
||||
* python `onchange` method.
|
||||
|
||||
@@ -104,8 +104,8 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<button string="Confirm" attrs="{'invisible': [['display_name', '=', 'first record']]}" />
|
||||
<button string="Do it" attrs="{'invisible': [['display_name', '=', 'first record']]}" />
|
||||
<button string="Confirm" invisible="display_name == 'first record'" />
|
||||
<button string="Do it" invisible="display_name == 'first record'" />
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
@@ -145,7 +145,7 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<button string="Hola" attrs="{'invisible': [['display_name', '=', 'first record']]}" />
|
||||
<button string="Hola" invisible="display_name == 'first record'" />
|
||||
<button string="Ciao" />
|
||||
</header>
|
||||
<sheet>
|
||||
@@ -197,7 +197,7 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
|
||||
<form>
|
||||
<header>
|
||||
<widget name="attach_document" string="Attach document" />
|
||||
<button string="Ciao" attrs="{'invisible': [['display_name', '=', 'first record']]}" />
|
||||
<button string="Ciao" invisible="display_name == 'first record'" />
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
@@ -251,8 +251,8 @@ QUnit.module("Mobile Views", ({ beforeEach }) => {
|
||||
<form>
|
||||
<header>
|
||||
<button string="Just more than one" />
|
||||
<button string="Confirm" attrs="{'invisible': [['display_name', '=', '']]}" />
|
||||
<button string="Do it" attrs="{'invisible': [['display_name', '!=', '']]}" />
|
||||
<button string="Confirm" invisible="display_name == ''" />
|
||||
<button string="Do it" invisible="display_name != ''" />
|
||||
</header>
|
||||
<sheet>
|
||||
<field name="display_name" />
|
||||
|
||||
@@ -1377,14 +1377,14 @@ QUnit.module("MockServer", (hooks) => {
|
||||
data.views = {
|
||||
"bar,10001,list": `
|
||||
<tree>
|
||||
<field name="bool" invisible="1"/>
|
||||
<field name="bool" column_invisible="1"/>
|
||||
<field name="foo"/>
|
||||
</tree>
|
||||
`,
|
||||
"bar,10001,search": `<search></search>`,
|
||||
};
|
||||
const expectedList = `<tree>
|
||||
<field name="bool" modifiers="{"column_invisible":true}"/>
|
||||
<field name="bool" column_invisible="True"/>
|
||||
<field name="foo"/>
|
||||
</tree>`;
|
||||
const mockServer = new MockServer(data);
|
||||
|
||||
@@ -288,11 +288,11 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
<form>
|
||||
<field name="name" />
|
||||
<field name="allday" />
|
||||
<group attrs="{'invisible': [['allday', '=', True]]}">
|
||||
<group invisible="allday">
|
||||
<field name="start" />
|
||||
<field name="stop" />
|
||||
</group>
|
||||
<group attrs="{'invisible': [['allday', '=', False]]}">
|
||||
<group invisible="not allday">
|
||||
<field name="start_date" />
|
||||
<field name="stop_date" />
|
||||
</group>
|
||||
@@ -301,8 +301,8 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
"event,1,form": `
|
||||
<form>
|
||||
<field name="allday" invisible="1" />
|
||||
<field name="start" attrs="{'invisible': [['allday', '=', False]]}" />
|
||||
<field name="stop" attrs="{'invisible': [['allday', '=', True]]}" />
|
||||
<field name="start" invisible="not allday" />
|
||||
<field name="stop" invisible="allday" />
|
||||
</form>
|
||||
`,
|
||||
},
|
||||
@@ -1370,8 +1370,8 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
<calendar date_start="start" date_stop="stop" all_day="allday" mode="week">
|
||||
<field name="priority" widget="priority" readonly="1" />
|
||||
<field name="is_hatched" invisible="1" />
|
||||
<field name="partner_id" attrs="{'invisible': [['is_hatched', '=', False]]}" />
|
||||
<field name="start" attrs="{'invisible': [['is_hatched', '=', True]]}" />
|
||||
<field name="partner_id" invisible="not is_hatched" />
|
||||
<field name="start" invisible="is_hatched" />
|
||||
</calendar>
|
||||
`,
|
||||
});
|
||||
@@ -4692,7 +4692,8 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month">
|
||||
<field name="name" attrs="{'readonly': [['unknown_field', '=', 42]]}" />
|
||||
<field name="delay" invisible="True" />
|
||||
<field name="name" readonly="delay == 42" />
|
||||
</calendar>
|
||||
`,
|
||||
});
|
||||
@@ -4709,7 +4710,8 @@ QUnit.module("Views", ({ beforeEach }) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<calendar date_start="start" date_stop="stop" all_day="allday" mode="month" scales="month,year">
|
||||
<field name="name" attrs="{'readonly': [['unknown_field', '=', 42]]}" />
|
||||
<field name="delay" invisible="True" />
|
||||
<field name="name" readonly="delay == 42" />
|
||||
</calendar>
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -429,7 +429,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="int_field" />
|
||||
<field name="foo" attrs="{'invisible': [('int_field', '==', 9)]}"/>
|
||||
<field name="foo" invisible="int_field == 9"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
|
||||
@@ -294,7 +294,7 @@ Ho-ho-hoooo Merry Christmas`,
|
||||
<sheet>
|
||||
<div>
|
||||
<field name="text_field" disabled="1" widget="CopyClipboardButton"/>
|
||||
<field name="char_field" disabled="[('char_field', '=', 'yop')]" widget="CopyClipboardButton"/>
|
||||
<field name="char_field" disabled="char_field == 'yop'" widget="CopyClipboardButton"/>
|
||||
<field name="char_field" widget="char"/>
|
||||
</div>
|
||||
</sheet>
|
||||
|
||||
@@ -871,7 +871,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
serverData,
|
||||
arch: /* xml */ `
|
||||
<form>
|
||||
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}" attrs="{'required':[('datetime', '!=', False), ('datetime_end', '!=', False)]}"/>
|
||||
<field name="datetime" widget="daterange" options="{'end_date_field': 'datetime_end'}" required="datetime and datetime_end"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<group>
|
||||
<field name="timmy" widget="many2many_checkboxes" attrs="{'readonly': true}" />
|
||||
<field name="timmy" widget="many2many_checkboxes" readonly="True" />
|
||||
</group>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
@@ -400,7 +400,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="float_field" widget="monetary"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -495,7 +495,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="monetary_field"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
|
||||
@@ -436,7 +436,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
);
|
||||
|
||||
QUnit.test("O2M with parented m2o and domain on parent.m2o", async function (assert) {
|
||||
assert.expect(4);
|
||||
assert.expect(7);
|
||||
|
||||
// Records in an o2m can have a m2o pointing to themselves.
|
||||
// In that case, a domain evaluation on that field followed by name_search
|
||||
@@ -475,6 +475,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
mockRPC(route, { kwargs }) {
|
||||
if (route === "/web/dataset/call_kw/turtle/name_search") {
|
||||
assert.deepEqual(kwargs.args, [["id", "in", []]]);
|
||||
assert.deepEqual(JSON.stringify(kwargs.args), '[["id","in",[]]]');
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -648,8 +649,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="p">
|
||||
<tree editable="bottom">
|
||||
<field name="turtles" invisible="1"/>
|
||||
<field name="foo" attrs="{"readonly" : [("turtles", "!=", [])] }"/>
|
||||
<field name="qux" attrs="{"readonly" : [("turtles", "!=", [])] }"/>
|
||||
<field name="foo" readonly="turtles"/>
|
||||
<field name="qux" readonly="turtles"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -1502,7 +1503,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<tree editable="bottom" default_order="turtle_int" limit="10">
|
||||
<field name="turtle_int" widget="handle"/>
|
||||
<field name="turtle_foo"/>
|
||||
<field name="turtle_qux" attrs="{'readonly': [('turtle_foo', '=', False)]}"/>
|
||||
<field name="turtle_qux" readonly="not turtle_foo"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -2310,7 +2311,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
serverData.views = {
|
||||
"turtle,false,form": `
|
||||
<form>
|
||||
<field name="display_name" attrs="{'invisible': [('turtle_foo', '==', 'yop')]}"/>
|
||||
<field name="display_name" invisible="turtle_foo == 'yop'"/>
|
||||
<field name="turtle_foo"/>
|
||||
</form>`,
|
||||
};
|
||||
@@ -5258,7 +5259,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<form>
|
||||
<field name="turtles">
|
||||
<tree editable="bottom">
|
||||
<field name="turtle_foo" widget="char" attrs="{'readonly': [('turtle_int', '==', 11111)]}"/>
|
||||
<field name="turtle_foo" widget="char" readonly="turtle_int == 11111"/>
|
||||
<field name="turtle_int"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -5322,7 +5323,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<form>
|
||||
<field name="p">
|
||||
<tree editable="top">
|
||||
<field name="display_name" attrs='{"readonly": [["product_id", "=", false]]}'/>
|
||||
<field name="display_name" readonly="not product_id"/>
|
||||
<field name="product_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -6520,7 +6521,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="turtles">
|
||||
<tree editable="top" limit="2">
|
||||
<field name="turtle_foo"/>
|
||||
<field name="partner_ids" widget="many2many_tags" attrs="{'readonly': [('turtle_foo', '=', 'a')]}"/>
|
||||
<field name="partner_ids" widget="many2many_tags" readonly="turtle_foo == 'a'"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -7856,7 +7857,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="turtles">
|
||||
<tree editable="top">
|
||||
<field name="turtle_int"/>
|
||||
<field name="turtle_foo" attrs='{"required": [["turtle_int", "=", 0]]}'/>
|
||||
<field name="turtle_foo" required='turtle_int == 0'/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -8343,7 +8344,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="name"/>
|
||||
<field name="p" attrs="{'readonly': [['name', '=', 'readonly']]}">
|
||||
<field name="p" readonly="name == 'readonly'">
|
||||
<tree><field name="display_name"/></tree>
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
@@ -10615,8 +10616,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="bar"/>
|
||||
<field name="p">
|
||||
<tree>
|
||||
<field name="foo" attrs="{'column_invisible': [('parent.product_id', '!=', False)]}"/>
|
||||
<field name="bar" attrs="{'column_invisible': [('parent.bar', '=', False)]}"/>
|
||||
<field name="foo" column_invisible="parent.product_id"/>
|
||||
<field name="bar" column_invisible="not parent.bar"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
@@ -10667,7 +10668,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="p">
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<button name="abc" string="Do it" class="some_button" attrs="{'column_invisible': [('parent.product_id', '=', False)]}"/>
|
||||
<button name="abc" string="Do it" class="some_button" column_invisible="not parent.product_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -10702,10 +10703,10 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="p">
|
||||
<tree>
|
||||
<button name="abc1" string="Do it 1" class="some_button1"/>
|
||||
<button name="abc2" string="Do it 2" class="some_button2" attrs="{'column_invisible': [('parent.product_id', '!=', False)]}"/>
|
||||
<button name="abc2" string="Do it 2" class="some_button2" column_invisible="parent.product_id"/>
|
||||
<field name="foo"/>
|
||||
<button name="abc3" string="Do it 3" class="some_button3" attrs="{'column_invisible': [('parent.product_id', '!=', False)]}"/>
|
||||
<button name="abc4" string="Do it 4" class="some_button4" attrs="{'column_invisible': [('parent.trululu', '!=', False)]}"/>
|
||||
<button name="abc3" string="Do it 3" class="some_button3" column_invisible="parent.product_id"/>
|
||||
<button name="abc4" string="Do it 4" class="some_button4" column_invisible="parent.trululu"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -12012,7 +12013,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="p">
|
||||
<tree editable="bottom">
|
||||
<field name="foo"/>
|
||||
<field name="int_field" attrs="{'column_invisible': [('parent.bar', '=', False)]}"/>
|
||||
<field name="int_field" column_invisible="not parent.bar"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -12038,8 +12039,8 @@ QUnit.module("Fields", (hooks) => {
|
||||
serverData.views = {
|
||||
"partner,false,list": `
|
||||
<tree>
|
||||
<field name="foo" attrs="{'column_invisible': [('parent.product_id', '!=', False)]}"/>
|
||||
<field name="bar" attrs="{'column_invisible': [('parent.bar', '=', False)]}"/>
|
||||
<field name="foo" column_invisible="parent.product_id"/>
|
||||
<field name="bar" column_invisible="not parent.bar"/>
|
||||
</tree>`,
|
||||
};
|
||||
|
||||
|
||||
@@ -1818,7 +1818,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="company_id"/>
|
||||
<field name="display_name"/>
|
||||
<field name="properties" widget="properties"/>
|
||||
<div class="o_test_properties_not_empty" attrs="{'invisible': [('properties', '=', [])]}">
|
||||
<div class="o_test_properties_not_empty" invisible="not properties">
|
||||
Properties not empty
|
||||
</div>
|
||||
</group>
|
||||
@@ -1872,7 +1872,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<field name="company_id"/>
|
||||
<field name="display_name"/>
|
||||
<field name="properties" widget="properties"/>
|
||||
<div class="o_test_properties_not_empty" attrs="{'invisible': [('properties', '=', [])]}">
|
||||
<div class="o_test_properties_not_empty" invisible="not properties">
|
||||
Properties not empty
|
||||
</div>
|
||||
</group>
|
||||
|
||||
@@ -276,7 +276,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="feedback_value" />
|
||||
<field name="color" attrs="{'required': [('feedback_value', '=', 'bad')]}" />
|
||||
<field name="color" required="feedback_value == 'bad'" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
@@ -329,7 +329,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="feedback_value" />
|
||||
<field name="color" attrs="{'required': [('feedback_value', '=', 'bad')]}" />
|
||||
<field name="color" required="feedback_value == 'bad'" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="bar" />
|
||||
<field name="txt" attrs="{'invisible': [('bar', '=', True)]}" />
|
||||
<field name="txt" invisible="bar" />
|
||||
</form>`,
|
||||
});
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
resId: 1,
|
||||
arch: /*xml*/ `
|
||||
<tree string="Colors" editable="top">
|
||||
<field name="tz_offset" invisible="True"/>
|
||||
<field name="tz_offset" column_invisible="True"/>
|
||||
<field name="color" widget="timezone_mismatch" />
|
||||
</tree>
|
||||
`,
|
||||
|
||||
@@ -276,7 +276,7 @@ QUnit.module("Fields", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" widget="url" attrs="{'readonly': True}" />
|
||||
<field name="foo" widget="url" readonly="True" />
|
||||
<field name="foo2" />
|
||||
</group>
|
||||
</sheet>
|
||||
|
||||
@@ -228,18 +228,18 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
// ```<form>
|
||||
// <field name="display_name" invisible="1" />
|
||||
// <div class="visible3" invisible="0"/>
|
||||
// <div modifiers="{'invisible': [['display_name', '=', 'take']]}"/>
|
||||
// <div invisible="display_name == 'take'"/>
|
||||
// </form>````
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field field_id="display_name" name="display_name" modifiers="{"invisible": true}" />
|
||||
<div class="visible3" modifiers="{"invisible": false}"/>
|
||||
<div modifiers="{"invisible": [["display_name", "=", "take"]]}"/>
|
||||
<field field_id="display_name" name="display_name" invisible="True" />
|
||||
<div class="visible3" invisible="False"/>
|
||||
<div invisible="display_name == "take""/>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="visible3" />
|
||||
<div t-if="!__comp__.evalDomainFromRecord(__comp__.props.record,[["display_name","=","take"]])" />
|
||||
<div t-if="!__comp__.evaluateBooleanExpr("display_name == \\"take\\"",__comp__.props.record.evalContext)" />
|
||||
`;
|
||||
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
@@ -248,14 +248,14 @@ QUnit.module("Form Compiler", (hooks) => {
|
||||
QUnit.test("compile invisible containing string as domain", async (assert) => {
|
||||
const arch = /*xml*/ `
|
||||
<form>
|
||||
<field name="display_name" modifiers="{"invisible": true}" />
|
||||
<div class="visible3" modifiers="{"invisible": false}"/>
|
||||
<div modifiers="{"invisible": "[['display_name', '=', 'take']]"}"/>
|
||||
<field name="display_name" invisible="True" />
|
||||
<div class="visible3" invisible="False"/>
|
||||
<div invisible="display_name == 'take'"/>
|
||||
</form>`;
|
||||
|
||||
const expected = /*xml*/ `
|
||||
<div class="visible3" />
|
||||
<div t-if="!__comp__.evalDomainFromRecord(__comp__.props.record,"[['display_name','=','take']]")" />
|
||||
<div t-if="!__comp__.evaluateBooleanExpr("display_name == 'take'",__comp__.props.record.evalContext)" />
|
||||
`;
|
||||
assert.areContentEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
@@ -350,14 +350,14 @@ QUnit.module("Form Renderer", (hooks) => {
|
||||
};
|
||||
});
|
||||
|
||||
QUnit.test("compile form with modifiers and attrs - string as domain", async (assert) => {
|
||||
QUnit.test("compile form with modifiers", async (assert) => {
|
||||
serverData.views = {
|
||||
"partner,1,form": /*xml*/ `
|
||||
<form>
|
||||
<div modifiers="{"invisible": "[['display_name', '=', uid]]"}">
|
||||
<div invisible="display_name == uid">
|
||||
<field name="charfield"/>
|
||||
</div>
|
||||
<field name="display_name" attrs="{'readonly': "[['display_name', '=', uid]]"}"/>
|
||||
<field name="display_name" readonly="display_name == uid"/>
|
||||
</form>`,
|
||||
};
|
||||
|
||||
@@ -379,7 +379,7 @@ QUnit.module("Form Renderer", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page name="p1" attrs="{'invisible': [['display_name', '=', 'lol']]}"><field name="charfield"/></page>
|
||||
<page name="p1" invisible="display_name == 'lol'"><field name="charfield"/></page>
|
||||
<page name="p2"><field name="display_name"/></page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
@@ -482,9 +482,9 @@ QUnit.module("Form Renderer", (hooks) => {
|
||||
},
|
||||
});
|
||||
|
||||
const arch = `<myNode modifiers="{"invisible": [["field", "=", "value"]]}" />`;
|
||||
const arch = `<myNode invisible="field == 'value'" />`;
|
||||
|
||||
const expected = `<t><div class="myNode" t-if="( myCondition or myOtherCondition ) and !__comp__.evalDomainFromRecord(__comp__.props.record,[["field","=","value"]])" t-ref="compiled_view_root"/></t>`;
|
||||
const expected = `<t><div class="myNode" t-if="( myCondition or myOtherCondition ) and !__comp__.evaluateBooleanExpr("field == 'value'",__comp__.props.record.evalContext)" t-ref="compiled_view_root"/></t>`;
|
||||
assert.areEquivalent(compileTemplate(arch), expected);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -497,11 +497,11 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<group>
|
||||
<group>
|
||||
<field name="foo" class="foo_1" attrs="{'invisible': [('bar','=',False)]}"/>
|
||||
<field name="foo" class="foo_2" attrs="{'invisible': [('bar','=',True)]}"/>
|
||||
<field name="foo" class="foo_1" invisible="not bar"/>
|
||||
<field name="foo" class="foo_2" invisible="bar"/>
|
||||
<field name="foo" class="foo_3"/>
|
||||
<field name="int_field" class="int_field_1" attrs="{'readonly': [('bar','=',True)]}"/>
|
||||
<field name="int_field" class="int_field_2" attrs="{'readonly': [('bar','=',False)]}"/>
|
||||
<field name="int_field" class="int_field_1" readonly="bar"/>
|
||||
<field name="int_field" class="int_field_2" readonly="not bar"/>
|
||||
<field name="bar"/>
|
||||
</group>
|
||||
</group>
|
||||
@@ -1215,8 +1215,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<group>
|
||||
<field name="int_field"/>
|
||||
<field name="timmy" invisible="1"/>
|
||||
<field name="foo" attrs='{"invisible": [("int_field", "=", 10)]}'/>
|
||||
<field name="bar" attrs='{"invisible": [("bar", "=", False), ("timmy", "=", [])]}'/>
|
||||
<field name="foo" invisible="int_field == 10"/>
|
||||
<field name="bar" invisible="not bar and not timmy"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>`,
|
||||
@@ -1249,10 +1249,12 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<div class="a" attrs='{"invisible": [("foo", "=", False)]}'>a</div>
|
||||
<div class="b" attrs='{"invisible": [("foo", "=", "")]}'>b</div>
|
||||
<div class="c" attrs='{"invisible": [("display_name", "=", False)]}'>c</div>
|
||||
<div class="d" attrs='{"invisible": [("display_name", "=", "")]}'>d</div>
|
||||
<div class="a" invisible="foo == False">b</div>
|
||||
<div class="b" invisible="foo == ''">b</div>
|
||||
<div class="c" invisible="not display_name">c</div>
|
||||
<div class="d" invisible="display_name == ''">d</div>
|
||||
<div class="e" invisible="not foo">e</div>
|
||||
<div class="f" invisible="display_name == False">f</div>
|
||||
<field name="foo" invisible="1"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
@@ -1262,6 +1264,8 @@ QUnit.module("Views", (hooks) => {
|
||||
assert.containsNone(target, "div.b");
|
||||
assert.containsNone(target, "div.c");
|
||||
assert.containsOnce(target, "div.d");
|
||||
assert.containsNone(target, "div.e");
|
||||
assert.containsNone(target, "div.f");
|
||||
});
|
||||
|
||||
QUnit.test(
|
||||
@@ -1275,7 +1279,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="int_field"/>
|
||||
<notebook name="test_name" class="new_class" attrs='{"invisible": [["int_field", "=", 10]]}'>
|
||||
<notebook name="test_name" class="new_class" invisible="int_field == 10">
|
||||
<page string="Foo">
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
@@ -1367,10 +1371,10 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<field name="bar"/>
|
||||
<notebook>
|
||||
<page string="A" attrs='{"invisible": [["bar", "=", false]]}'>
|
||||
<page string="A" invisible='not bar'>
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="B" attrs='{"invisible": [["bar", "=", true]]}'>
|
||||
<page string="B" invisible='bar'>
|
||||
<field name="int_field"/>
|
||||
</page>
|
||||
<page string="C">
|
||||
@@ -1405,7 +1409,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<field name="int_field"/>
|
||||
<notebook>
|
||||
<page string="Foo" attrs='{"invisible": [["int_field", "=", 44]]}'>
|
||||
<page string="Foo" invisible='int_field == 44'>
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Bar">
|
||||
@@ -1438,7 +1442,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<field name="bar"/>
|
||||
<notebook>
|
||||
<page string="Foo" attrs='{"invisible": [["bar", "!=", false]]}'>
|
||||
<page string="Foo" invisible='bar'>
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
</notebook>
|
||||
@@ -1493,13 +1497,13 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<field name="bar"/>
|
||||
<notebook class="new_class">
|
||||
<page string="Foo" attrs="{'invisible': [['bar', '=', true]]}">
|
||||
<page string="Foo" invisible="bar">
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Qux" invisible="1">
|
||||
<field name="qux"/>
|
||||
</page>
|
||||
<page string="IntField" attrs="{'invisible': [['bar', '=', true]]}">
|
||||
<page string="IntField" invisible="bar">
|
||||
<field name="int_field"/>
|
||||
</page>
|
||||
</notebook>
|
||||
@@ -1670,7 +1674,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="bar"/>
|
||||
<group attrs='{"invisible": [["bar", "!=", true]]}'>
|
||||
<group invisible='not bar'>
|
||||
<group>
|
||||
<field name="foo"/>
|
||||
</group>
|
||||
@@ -1699,7 +1703,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="foo"/>
|
||||
<group attrs='{"invisible": [["int_field", "=", 0.0]]}'>
|
||||
<group invisible='int_field == 0.0'>
|
||||
<div class="hello">this should be invisible</div>
|
||||
<field name="int_field"/>
|
||||
</group>
|
||||
@@ -1861,7 +1865,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<button class="oe_stat_button" type="action" name="someaction">
|
||||
<field name="int_field"/>
|
||||
</button>
|
||||
<button class="oe_stat_button" name="some_action" type="action" attrs='{"invisible": [["bar", "=", true]]}'>
|
||||
<button class="oe_stat_button" name="some_action" type="action" invisible='bar'>
|
||||
<field name="bar"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -1917,7 +1921,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<button class="oe_stat_button">
|
||||
<field name="int_field"/>
|
||||
</button>
|
||||
<button class="oe_stat_button" name="some_action" type="action" attrs='{"invisible": [["bar", "=", true]]}'>
|
||||
<button class="oe_stat_button" name="some_action" type="action" invisible='bar'>
|
||||
<field name="bar"/>
|
||||
</button>
|
||||
</div>
|
||||
@@ -2099,8 +2103,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<label for="bar" string="label1" attrs='{"invisible": [["bar", "=", True]]}'/>
|
||||
<label for="bar" string="label2" attrs='{"invisible": [["bar", "=", False]]}'/>
|
||||
<label for="bar" string="label1" invisible='bar'/>
|
||||
<label for="bar" string="label2" invisible='not bar'/>
|
||||
<field name="bar" nolabel="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -2129,7 +2133,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<group>
|
||||
<div>
|
||||
<label for="bar" attrs='{"invisible": [["bar", "=", True]]}'/>
|
||||
<label for="bar" invisible='bar'/>
|
||||
<field name="bar" />
|
||||
</div>
|
||||
</group>
|
||||
@@ -2356,8 +2360,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<group>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
<field name="foo" attrs="{'required': [['bar', '=', false]]}"/>
|
||||
<field name="foo" attrs="{'required': [['int_field', '=', 44]]}"/>
|
||||
<field name="foo" required="not bar"/>
|
||||
<field name="foo" required="int_field == 44"/>
|
||||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
@@ -2412,8 +2416,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<group>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
<field name="foo" attrs="{'readonly': [['bar', '=', false]]}"/>
|
||||
<field name="foo" attrs="{'readonly': [['int_field', '=', 0]]}"/>
|
||||
<field name="foo" readonly="not bar"/>
|
||||
<field name="foo" readonly="int_field == 0"/>
|
||||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
@@ -2466,8 +2470,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<group>
|
||||
<field name="bar"/>
|
||||
<field name="int_field"/>
|
||||
<field name="foo" required="1" attrs="{'invisible': [['bar', '=', false]]}"/>
|
||||
<field name="foo" attrs="{'invisible': [['int_field', '=', 0]]}"/>
|
||||
<field name="foo" required="1" invisible="not bar"/>
|
||||
<field name="foo" invisible="int_field == 0"/>
|
||||
</group>
|
||||
</form>`,
|
||||
mockRPC(route, args) {
|
||||
@@ -2627,7 +2631,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" attrs="{'readonly': [['bar', '=', True]]}"/>
|
||||
<field name="foo" readonly="bar"/>
|
||||
<field name="bar"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -2668,7 +2672,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="int_field" attrs="{'readonly': [('id', '!=', False)]}"/>
|
||||
<field name="int_field" readonly="id"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
@@ -2722,7 +2726,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="trululu"/>
|
||||
<field name="product_ids" attrs="{'readonly': [['trululu', '=', False]]}">
|
||||
<field name="product_ids" readonly="not trululu">
|
||||
<tree editable="top"><field name="int_field" widget="handle" /><field name="name"/></tree>
|
||||
</field>
|
||||
</form>
|
||||
@@ -2770,7 +2774,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo"/>
|
||||
<field name="trululu" attrs="{'readonly': [['foo', '=', False]]}"/>
|
||||
<field name="trululu" readonly="not foo"/>
|
||||
<field name="int_field"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -2831,7 +2835,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<group>
|
||||
<field name="foo"/>
|
||||
<field name="display_name" attrs="{'readonly': [['foo', '=', 'readonly']]}"/>
|
||||
<field name="display_name" readonly="foo == 'readonly'"/>
|
||||
</group>
|
||||
</form>`,
|
||||
resId: 2,
|
||||
@@ -2912,9 +2916,9 @@ QUnit.module("Views", (hooks) => {
|
||||
<sheet>
|
||||
<label for="foo" string="Foo"/>
|
||||
<field name="foo"/>
|
||||
<label for="trululu" string="Trululu" attrs="{'readonly': [['foo', '=', False]]}"/>
|
||||
<field name="trululu" attrs="{'readonly': [['foo', '=', False]]}"/>
|
||||
<label for="int_field" string="IntField" attrs="{'readonly': [['int_field', '=', False]]}"/>
|
||||
<label for="trululu" string="Trululu" readonly="not foo"/>
|
||||
<field name="trululu" readonly="not foo"/>
|
||||
<label for="int_field" string="IntField" readonly="not int_field"/>
|
||||
<field name="int_field"/>
|
||||
</sheet>
|
||||
</form>`,
|
||||
@@ -2947,7 +2951,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" attrs="{'required': [['bar', '=', True]]}"/>
|
||||
<field name="foo" required="bar"/>
|
||||
<field name="bar"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -3063,7 +3067,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<separator string="Geolocation" attrs='{"invisible": [["bar", "=", True]]}'/>
|
||||
<separator string="Geolocation" invisible='bar'/>
|
||||
<field name="bar"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -3252,7 +3256,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<header>
|
||||
<button name="post" class="p" string="Confirm" type="object"/>
|
||||
<button name="some_method" class="s" string="Do it" type="object"/>
|
||||
<button name="some_other_method" states="ab,ef" string="Do not" type="object"/>
|
||||
<button name="some_other_method" invisible="state not in ['ab', 'ef']" string="Do not" type="object"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
@@ -5899,7 +5903,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="foo" class="foo_field" attrs='{"invisible": [["bar", "=", True]]}'/>
|
||||
<field name="foo" class="foo_field" invisible='bar'/>
|
||||
<field name="bar"/>
|
||||
</group>
|
||||
</sheet>
|
||||
@@ -6339,7 +6343,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<field name="p">
|
||||
<tree editable="top">
|
||||
<field name="display_name" attrs="{'readonly': [('timmy', '=', false)]}"/>
|
||||
<field name="display_name" readonly="not timmy"/>
|
||||
<field name="timmy"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -7580,7 +7584,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<form>
|
||||
<field name="foo" attrs="{'readonly': [('bar','=',False)]}"/>
|
||||
<field name="foo" readonly="not bar"/>
|
||||
<field name="bar"/>
|
||||
</form>`,
|
||||
resId: 1,
|
||||
@@ -7664,7 +7668,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<field name="timmy" widget="many2many_tags" attrs="{'readonly': [('bar','=',True)]}"/>
|
||||
<field name="timmy" widget="many2many_tags" readonly="bar"/>
|
||||
</form>`,
|
||||
resId: 5,
|
||||
});
|
||||
@@ -7714,11 +7718,11 @@ QUnit.module("Views", (hooks) => {
|
||||
"partner,false,form": `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<footer attrs="{'invisible': [('bar','=',False)]}">
|
||||
<footer invisible="not bar">
|
||||
<button>Hello</button>
|
||||
<button>World</button>
|
||||
</footer>
|
||||
<footer attrs="{'invisible': [('bar','!=',False)]}">
|
||||
<footer invisible="bar">
|
||||
<button>Foo</button>
|
||||
</footer>
|
||||
</form>`,
|
||||
@@ -8531,7 +8535,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<notebook>
|
||||
<page string="Foo" attrs='{"invisible": [["id", "=", 2]]}'>
|
||||
<page string="Foo" invisible='id == 2'>
|
||||
<field name="foo"/>
|
||||
</page>
|
||||
<page string="Bar">
|
||||
@@ -9036,8 +9040,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<field name="foo" />
|
||||
<group>
|
||||
<div class="cell1" attrs='{"invisible": [["foo", "=", "1"]]}' />
|
||||
<div class="cell2" attrs='{"invisible": [["foo", "=", "2"]]}' />
|
||||
<div class="cell1" invisible='foo == "1"' />
|
||||
<div class="cell2" invisible='foo == "2"' />
|
||||
</group>
|
||||
</form>`,
|
||||
});
|
||||
@@ -9953,7 +9957,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<tree><field name="display_name"/></tree>
|
||||
<form>
|
||||
<field name="display_name"/>
|
||||
<field name="foo" attrs="{'readonly': [['display_name', '=', 'readonly']]}"/>
|
||||
<field name="foo" readonly="display_name == 'readonly'"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -10029,7 +10033,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<field name="id"/>
|
||||
<field name="foo" attrs="{'readonly': [['id', '=', False]]}"/>
|
||||
<field name="foo" readonly="not id"/>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
@@ -10150,7 +10154,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<header>
|
||||
<button name="some_method" class="oe_highlight" string="Button" type="object" title="This is title" attrs="{'readonly': [['display_name', '=', 'readonly']]}"/>
|
||||
<button name="some_method" class="oe_highlight" string="Button" type="object" title="This is title" readonly="display_name == 'readonly'"/>
|
||||
<button name="empty_method" string="Empty Button" type="object"/>
|
||||
</header>
|
||||
<button name="other_method" class="oe_highlight" string="Button2" type="object" help="help Button2"/>
|
||||
@@ -10168,7 +10172,7 @@ QUnit.module("Views", (hooks) => {
|
||||
await nextTick();
|
||||
assert.strictEqual(
|
||||
target.querySelector(".o-tooltip").textContent,
|
||||
` Button : ButtonThis is titleObject:partnerModifiers:{"readonly":[["display_name","=","readonly"]]}Button Type:objectMethod:some_method`
|
||||
` Button : ButtonThis is titleObject:partnerReadonly:display_name == 'readonly'Button Type:objectMethod:some_method`
|
||||
);
|
||||
|
||||
await mouseEnter(target.querySelector("button[name='other_method']"));
|
||||
@@ -10251,7 +10255,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="p">
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<field name="bar" invisible="context.get('hide_bar', False)"/>
|
||||
<field name="bar" column_invisible="context.get('hide_bar', False)"/>
|
||||
<field name="int_field" column_invisible="True"/>
|
||||
</tree>
|
||||
</field>
|
||||
</form>`,
|
||||
@@ -10272,7 +10277,7 @@ QUnit.module("Views", (hooks) => {
|
||||
"partner,false,list": `
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<field name="bar" invisible="context.get('hide_bar', False)"/>
|
||||
<field name="bar" column_invisible="context.get('hide_bar', False)"/>
|
||||
</tree>`,
|
||||
};
|
||||
await makeView({
|
||||
@@ -10480,7 +10485,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="product_id" context="{'lang': 'en_US'}" attrs='{"invisible": [["product_id", "=", 33]]}' widget="many2one"/>
|
||||
<field name="product_id" context="{'lang': 'en_US'}" invisible="product_id == 33" widget="many2one"/>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
@@ -10500,14 +10505,14 @@ QUnit.module("Views", (hooks) => {
|
||||
);
|
||||
assert.containsOnce(
|
||||
target,
|
||||
".o-tooltip--technical > li[data-item='modifiers']",
|
||||
"modifiers should be present for this field"
|
||||
".o-tooltip--technical > li[data-item='invisible']",
|
||||
"invisible should be present for this field"
|
||||
);
|
||||
assert.strictEqual(
|
||||
target.querySelector('.o-tooltip--technical > li[data-item="modifiers"]').lastChild
|
||||
target.querySelector('.o-tooltip--technical > li[data-item="invisible"]').lastChild
|
||||
.textContent,
|
||||
'{"invisible":[["product_id","=",33]]}',
|
||||
"modifiers should be properly stringified"
|
||||
'product_id == 33',
|
||||
"invisible should be properly stringified"
|
||||
);
|
||||
|
||||
assert.containsOnce(
|
||||
@@ -12507,7 +12512,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<field name="length"/>
|
||||
<field name="display_name"/>
|
||||
<field name="foo" attrs="{'readonly': [['display_name', '=', 'readonly']]}"/>
|
||||
<field name="foo" readonly="display_name =='readonly'"/>
|
||||
</form>
|
||||
</field>
|
||||
</form>`,
|
||||
|
||||
@@ -2416,8 +2416,8 @@ QUnit.module("Views", (hooks) => {
|
||||
<graph type="pie">
|
||||
<field name="revenue" type="measure"/>
|
||||
<field name="date" interval="day"/>
|
||||
<field name="foo" modifiers='{"invisible": false}'/>
|
||||
<field name="bar" modifiers='{"invisible": true}' string="My invisible field"/>
|
||||
<field name="foo" invisible="False"/>
|
||||
<field name="bar" invisible="True" string="My invisible field"/>
|
||||
<field name="id"/>
|
||||
<field name="fighters" string="FooFighters"/>
|
||||
</graph>
|
||||
|
||||
@@ -2619,7 +2619,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData.views["partner,some_view_ref,form"] =
|
||||
"<form>" +
|
||||
'<field name="foo" required="1"/>' +
|
||||
'<field name="int_field" attrs=\'{"invisible": [["foo", "=", false]]}\'/>' +
|
||||
'<field name="int_field" invisible="not foo"/>' +
|
||||
"</form>";
|
||||
|
||||
await makeView({
|
||||
@@ -8047,11 +8047,8 @@ QUnit.module("Views", (hooks) => {
|
||||
'<field name="bar"/>' +
|
||||
'<field name="state"/>' +
|
||||
'<templates><div t-name="kanban-box">' +
|
||||
'<button class="o_btn_test_1" type="object" name="a1" ' +
|
||||
"attrs=\"{'invisible': [['foo', '!=', 'yop']]}\"/>" +
|
||||
'<button class="o_btn_test_2" type="object" name="a2" ' +
|
||||
"attrs=\"{'invisible': [['bar', '=', True]]}\" " +
|
||||
'states="abc,def"/>' +
|
||||
'<button class="o_btn_test_1" type="object" name="a1" invisible="foo != \'yop\'"/>' +
|
||||
'<button class="o_btn_test_2" type="object" name="a2" invisible="bar and state not in [\'abc\', \'def\']"/>' +
|
||||
"</div></templates>" +
|
||||
"</kanban>",
|
||||
});
|
||||
@@ -8221,7 +8218,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="foo" attrs="{'invisible': [['id', '=', 1]]}"/>
|
||||
<field name="foo" invisible="id == 1"/>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -8396,7 +8393,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<kanban>
|
||||
<field name="bar" />
|
||||
<field name="product_id" attrs="{'readonly': [('bar', '!=', True)]}"/>
|
||||
<field name="product_id" readonly="not bar"/>
|
||||
<templates>
|
||||
<t t-name="kanban-box">
|
||||
<div><field name="id"/></div>
|
||||
@@ -8563,7 +8560,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<t t-name="kanban-box">
|
||||
<div>
|
||||
<field name="foo"/>
|
||||
<button type="object" attrs="{'invisible':['|', ('bar','=',True), ('category_ids', '!=', [])]}" class="btn btn-primary float-end" name="arbitrary">Join</button>
|
||||
<button type="object" invisible="bar or category_ids" class="btn btn-primary float-end" name="arbitrary">Join</button>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
|
||||
@@ -746,10 +746,10 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree>
|
||||
<button name="a" type="object" icon="fa-car"/>
|
||||
<field name="foo" invisible="1"/>
|
||||
<!--Here the invisible=1 is used to simulate a group on the case that the user
|
||||
<field name="foo" column_invisible="1"/>
|
||||
<!--Here the column_invisible=1 is used to simulate a group on the case that the user
|
||||
don't have the rights to see the button.-->
|
||||
<button name="b" type="object" icon="fa-car" invisible="1"/>
|
||||
<button name="b" type="object" icon="fa-car" column_invisible="1"/>
|
||||
<button name="x" type="object" icon="fa-star"/>
|
||||
<button name="y" type="object" icon="fa-refresh"/>
|
||||
<button name="z" type="object" icon="fa-exclamation"/>
|
||||
@@ -786,7 +786,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree>
|
||||
<button name="a" type="object" icon="fa-car"/>
|
||||
<field name="foo" attrs="{'invisible': [['foo', '=', 'blip']]}"/>
|
||||
<field name="foo" invisible="foo == 'blip'"/>
|
||||
<button name="x" type="object" icon="fa-star"/>
|
||||
<button name="y" type="object" icon="fa-refresh"/>
|
||||
<button name="z" type="object" icon="fa-exclamation"/>
|
||||
@@ -839,9 +839,9 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<button name="x" type="object" icon="fa-star" attrs="{'invisible': [['foo', '=', 'blip']]}"/>
|
||||
<button name="y" type="object" icon="fa-refresh" attrs="{'invisible': [['foo', '=', 'yop']]}"/>
|
||||
<button name="z" type="object" icon="fa-exclamation" attrs="{'invisible': [['foo', '=', 'gnap']]}"/>
|
||||
<button name="x" type="object" icon="fa-star" invisible="foo == 'blip'"/>
|
||||
<button name="y" type="object" icon="fa-refresh" invisible="foo == 'yop'"/>
|
||||
<button name="z" type="object" icon="fa-exclamation" invisible="foo == 'gnap'"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -1405,7 +1405,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<field name="bar" invisible="1"/>
|
||||
<field name="bar" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -1422,8 +1422,9 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="foo" invisible="context.get('notInvisible')"/>
|
||||
<field name="bar" invisible="context.get('invisible')"/>
|
||||
<field name="date" column_invisible="True"/>
|
||||
<field name="foo" column_invisible="context.get('notInvisible')"/>
|
||||
<field name="bar" column_invisible="context.get('invisible')"/>
|
||||
</tree>`,
|
||||
context: {
|
||||
invisible: true,
|
||||
@@ -1437,6 +1438,81 @@ QUnit.module("Views", (hooks) => {
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"invisible column based on the context are correctly displayed in o2m",
|
||||
async function (assert) {
|
||||
serverData.models.foo.fields.foo_o2m = {
|
||||
string: "Foo O2M",
|
||||
type: "one2many",
|
||||
relation: "foo",
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "foo",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="foo_o2m">
|
||||
<tree>
|
||||
<field name="foo" column_invisible="context.get('notInvisible')"/>
|
||||
<field name="bar" column_invisible="context.get('invisible')"/>
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
</form>`,
|
||||
context: {
|
||||
invisible: true,
|
||||
notInvisible: false,
|
||||
},
|
||||
});
|
||||
|
||||
// 1 for 1 visible column (foo), 1 th for delete button
|
||||
assert.containsN(target, "th", 2, "should have 2 th");
|
||||
assert.strictEqual(target.querySelectorAll("th")[0].dataset.name, "foo");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test(
|
||||
"invisible column based on the parent are correctly displayed in o2m",
|
||||
async function (assert) {
|
||||
serverData.models.foo.fields.foo_o2m = {
|
||||
string: "Foo O2M",
|
||||
type: "one2many",
|
||||
relation: "foo",
|
||||
};
|
||||
await makeView({
|
||||
type: "form",
|
||||
resModel: "foo",
|
||||
serverData,
|
||||
resId: 1,
|
||||
arch: `
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="int_field"/>
|
||||
<field name="m2m" invisible="True"/>
|
||||
<field name="properties" invisible="True"/>
|
||||
<field name="foo_o2m">
|
||||
<tree>
|
||||
<field name="date" column_invisible="True"/>
|
||||
<field name="foo" column_invisible="parent.int_field == 3"/>
|
||||
<field name="bar" column_invisible="parent.int_field == 10"/>
|
||||
<field name="qux" column_invisible="parent.m2m"/>
|
||||
<field name="amount" column_invisible="parent.properties"/>
|
||||
</tree>
|
||||
</field>
|
||||
</sheet>
|
||||
</form>`,
|
||||
});
|
||||
|
||||
// 1 for 2 visible column (foo, properties), 1 th for delete button
|
||||
assert.containsN(target, "th", 3, "should have 3 th");
|
||||
assert.strictEqual(target.querySelectorAll("th")[0].dataset.name, "foo");
|
||||
assert.strictEqual(target.querySelectorAll("th")[1].dataset.name, "amount");
|
||||
}
|
||||
);
|
||||
|
||||
QUnit.test("save a record with an invisible required field", async function (assert) {
|
||||
serverData.models.foo.fields.foo.required = true;
|
||||
|
||||
@@ -1446,7 +1522,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="foo" invisible="1"/>
|
||||
<field name="foo" column_invisible="1"/>
|
||||
<field name="int_field"/>
|
||||
</tree>`,
|
||||
mockRPC(_, { args, method }) {
|
||||
@@ -1625,7 +1701,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="foo"/>
|
||||
<field name="bar" attrs="{'invisible': [('id','=', 1)]}"/>
|
||||
<field name="bar" invisible="id == 1"/>
|
||||
<field name="int_field"/>
|
||||
</tree>`,
|
||||
});
|
||||
@@ -1642,7 +1718,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="foo" attrs="{'invisible': [('id','=', 1)]}"/>
|
||||
<field name="foo" invisible="id == 1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -1662,7 +1738,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="foo"/>
|
||||
<field name="m2o" invisible="1"/>
|
||||
<field name="m2o" column_invisible="1"/>
|
||||
</tree>`,
|
||||
mockRPC(route) {
|
||||
assert.step(route.split("/").pop());
|
||||
@@ -3993,8 +4069,8 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree>
|
||||
<field name="company_currency_id" invisible="1"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="company_currency_id" column_invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
<field name="amount"/>
|
||||
<field name="amount_currency"/>
|
||||
</tree>`,
|
||||
@@ -4905,7 +4981,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<sheet>
|
||||
<field name="bar"/>
|
||||
<field name="o2m" attrs="{'invisible': [('bar', '=', True)]}">
|
||||
<field name="o2m" invisible="bar">
|
||||
<tree editable="bottom">
|
||||
<field name="titi"/>
|
||||
<field name="grosminet"/>
|
||||
@@ -5437,7 +5513,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="date"/>
|
||||
<field name="text"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
<field name="m2o"/>
|
||||
<field name="m2m" widget="many2many_tags"/>
|
||||
</tree>`,
|
||||
@@ -7144,7 +7220,7 @@ QUnit.module("Views", (hooks) => {
|
||||
resModel: "foo",
|
||||
serverData,
|
||||
arch: `
|
||||
<tree decoration-info="datetime == '2017-02-27 12:51:35'" decoration-danger="datetime > '2017-02-27 12:51:35' AND datetime < '2017-02-27 10:51:35'">
|
||||
<tree decoration-info="datetime == '2017-02-27 12:51:35'" decoration-danger="datetime > '2017-02-27 12:51:35' and datetime < '2017-02-27 10:51:35'">
|
||||
<field name="datetime"/>
|
||||
<field name="int_field"/>
|
||||
</tree>`,
|
||||
@@ -7871,7 +7947,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<groupby name="currency_id">
|
||||
<field name="position"/>
|
||||
<button string="Button 1" type="object" name="button_method" attrs='{"invisible": [("position", "=", "after")]}'/>
|
||||
<button string="Button 1" type="object" name="button_method" invisible="position == 'after'"/>
|
||||
</groupby>
|
||||
</tree>`,
|
||||
mockRPC(route, args) {
|
||||
@@ -7918,7 +7994,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<groupby name="currency_id">
|
||||
<field name="m2o"/>
|
||||
<button string="Button 1" type="object" name="button_method" attrs='{"invisible": [("m2o", "=", false)]}'/>
|
||||
<button string="Button 1" type="object" name="button_method" invisible="not m2o"/>
|
||||
</groupby>
|
||||
</tree>`,
|
||||
mockRPC(route, args) {
|
||||
@@ -7950,7 +8026,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<groupby name="currency_id">
|
||||
<field name="position"/>
|
||||
<button string="Button 1" type="object" name="button_method" attrs='{"invisible": [("position", "=", "after")]}'/>
|
||||
<button string="Button 1" type="object" name="button_method" invisible="position == 'after'"/>
|
||||
</groupby>
|
||||
</tree>`,
|
||||
groupBy: ["currency_id"],
|
||||
@@ -7972,7 +8048,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<groupby name="currency_id">
|
||||
<field name="position"/>
|
||||
<button string="Button 1" type="object" name="button_method" attrs='{"invisible": [("position", "=", "after")]}'/>
|
||||
<button string="Button 1" type="object" name="button_method" invisible="position == 'after'"/>
|
||||
</groupby>
|
||||
</tree>`,
|
||||
groupBy: ["currency_id"],
|
||||
@@ -8046,7 +8122,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<field name="currency_id"/>
|
||||
<groupby name="currency_id">
|
||||
<field name="position" invisible="1"/>
|
||||
<field name="position" column_invisible="1"/>
|
||||
</groupby>
|
||||
</tree>`,
|
||||
groupBy: ["currency_id"],
|
||||
@@ -8422,10 +8498,10 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<button string="a button" name="button_action" icon="fa-car" type="object" attrs="{'invisible': [('id','=', 1)]}"/>
|
||||
<button string="a button" name="button_action" icon="fa-car" type="object" invisible="id == 1"/>
|
||||
<field name="int_field"/>
|
||||
<field name="qux"/>
|
||||
<field name="foo" attrs="{'invisible': [('id','=', 1)]}"/>
|
||||
<field name="foo" invisible="id == 1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -8459,7 +8535,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<tree>
|
||||
<field name="id"/>
|
||||
<field name="amount"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -8874,7 +8950,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="foo" attrs="{'invisible': [['bar', '=', True]]}"/>
|
||||
<field name="foo" invisible="bar"/>
|
||||
<field name="bar"/>
|
||||
</tree>`,
|
||||
});
|
||||
@@ -8927,7 +9003,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="foo" attrs="{'readonly': [['bar', '=', True]]}"/>
|
||||
<field name="foo" readonly="bar"/>
|
||||
<field name="bar"/>
|
||||
</tree>`,
|
||||
});
|
||||
@@ -8984,7 +9060,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="foo" attrs="{'required': [['bar', '=', True]]}"/>
|
||||
<field name="foo" required="bar"/>
|
||||
<field name="bar"/>
|
||||
</tree>`,
|
||||
});
|
||||
@@ -9059,7 +9135,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<form>
|
||||
<field name="o2m">
|
||||
<tree editable="top">
|
||||
<field name="display_name" attrs="{'invisible': [('stage', '=', 'open')]}"/>
|
||||
<field name="display_name" invisible="stage == 'open'"/>
|
||||
<field name="stage"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -10096,7 +10172,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="foo"/>
|
||||
<field name="bar" invisible="1"/>
|
||||
<field name="bar" column_invisible="1"/>
|
||||
<field name="int_field"/>
|
||||
</tree>`,
|
||||
resId: 1,
|
||||
@@ -10260,7 +10336,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="foo"/>
|
||||
<field name="qux"/>
|
||||
<field name="amount" widget="monetary"/>
|
||||
<field name="currency_id" invisible="1"/>
|
||||
<field name="currency_id" column_invisible="1"/>
|
||||
</tree>`,
|
||||
});
|
||||
patchWithCleanup(session, { currencies });
|
||||
@@ -10962,7 +11038,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree expand="1">
|
||||
<field name="foo" invisible="1"/>
|
||||
<field name="foo" column_invisible="1"/>
|
||||
<field name="reference"/>
|
||||
</tree>`,
|
||||
groupBy: ["foo"],
|
||||
@@ -11039,7 +11115,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree expand="1" multi_edit="1">
|
||||
<field name="foo" invisible="1"/>
|
||||
<field name="foo" column_invisible="1"/>
|
||||
<field name="bar" widget="boolean_toggle"/>
|
||||
<field name="reference"/>
|
||||
</tree>`,
|
||||
@@ -12053,7 +12129,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<tree multi_edit="1">
|
||||
<field name="id"/>
|
||||
<field name="foo"/>
|
||||
<field name="int_field" attrs='{"readonly": [("id", ">" , 2)]}'/>
|
||||
<field name="int_field" readonly="id > 2"/>
|
||||
</tree>`,
|
||||
mockRPC(route, args) {
|
||||
if (args.method === "write") {
|
||||
@@ -12637,8 +12713,8 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="bar"/>
|
||||
<field name="foo" attrs="{'readonly': [['bar','=',True]]}"/>
|
||||
<field name="m2o" attrs="{'readonly': [['bar','=',False]]}"/>
|
||||
<field name="foo" readonly="bar"/>
|
||||
<field name="m2o" readonly="not bar"/>
|
||||
<field name="int_field"/>
|
||||
</tree>`,
|
||||
});
|
||||
@@ -13288,9 +13364,9 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<tree editable="top">
|
||||
<field name="id" invisible="1"/>
|
||||
<field name="foo" attrs="{'readonly': [['id','!=',False]]}"/>
|
||||
<field name="int_field" attrs="{'invisible': [['id','!=',False]]}"/>
|
||||
<field name="id" column_invisible="1"/>
|
||||
<field name="foo" readonly="id"/>
|
||||
<field name="int_field" invisible="id"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -13330,7 +13406,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="foo"/>
|
||||
<field name="bar" attrs="{'readonly': [('foo', '!=', 'yop')]}"/>
|
||||
<field name="bar" readonly="foo != 'yop'"/>
|
||||
</tree>`,
|
||||
});
|
||||
|
||||
@@ -17131,7 +17207,7 @@ QUnit.module("Views", (hooks) => {
|
||||
<field name="o2m">
|
||||
<tree editable="bottom">
|
||||
<field name="foo"/>
|
||||
<field name="date" attrs="{'readonly': [('foo', '!=', 'yop')]}"/>
|
||||
<field name="date" readonly="foo != 'yop'"/>
|
||||
<field name="int_field"/>
|
||||
</tree>
|
||||
</field>
|
||||
@@ -17993,7 +18069,7 @@ QUnit.module("Views", (hooks) => {
|
||||
serverData,
|
||||
arch: `
|
||||
<list>
|
||||
<field name="bar" invisible="1"/>
|
||||
<field name="bar" column_invisible="1"/>
|
||||
<widget name="test_widget"/>
|
||||
</list>
|
||||
`,
|
||||
@@ -19039,7 +19115,7 @@ QUnit.module("Views", (hooks) => {
|
||||
arch: `
|
||||
<tree editable="bottom">
|
||||
<field name="m2o"/>
|
||||
<field name="properties" invisible="1"/>
|
||||
<field name="properties" column_invisible="1"/>
|
||||
</tree>
|
||||
`,
|
||||
mockRPC(route, { method, args }) {
|
||||
|
||||
@@ -107,11 +107,11 @@ QUnit.module("ViewDialogs", (hooks) => {
|
||||
"partner,false,form": `
|
||||
<form>
|
||||
<field name="bar"/>
|
||||
<footer attrs="{'invisible': [('bar','=',False)]}">
|
||||
<footer invisible="not bar">
|
||||
<button>Hello</button>
|
||||
<button>World</button>
|
||||
</footer>
|
||||
<footer attrs="{'invisible': [('bar','!=',False)]}">
|
||||
<footer invisible="bar">
|
||||
<button>Foo</button>
|
||||
</footer>
|
||||
</form>`,
|
||||
|
||||
@@ -340,7 +340,7 @@ export function getActionManagerServerData() {
|
||||
"partner,666,form": `<form>
|
||||
<header></header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box" modifiers="{}">
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button class="oe_stat_button" type="action" name="1" icon="fa-star" context="{'default_partner': active_id}">
|
||||
<field string="Partners" name="o2m" widget="statinfo"/>
|
||||
</button>
|
||||
|
||||
@@ -97,7 +97,7 @@ QUnit.module("SettingsFormView", (hooks) => {
|
||||
<div>This is a different setting</div>
|
||||
</setting>
|
||||
</block>
|
||||
<block title="Hide group Foo" attrs="{'invisible': [('bar','=',False)]}">
|
||||
<block title="Hide group Foo" invisible="not bar">
|
||||
<setting string="Hide Foo" help="this is hide foo">
|
||||
<field name="foo"/>
|
||||
</setting>
|
||||
|
||||
@@ -59,7 +59,7 @@ QUnit.test("basic rendering", async (assert) => {
|
||||
<form>
|
||||
<field name="lines" widget="slide_category_one2many">
|
||||
<tree>
|
||||
<field name="is_category" invisible="1"/>
|
||||
<field name="is_category" column_invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="display_name"/>
|
||||
<field name="int"/>
|
||||
@@ -87,7 +87,7 @@ QUnit.test("click on section behaves as usual in readonly mode", async (assert)
|
||||
<form>
|
||||
<field name="lines" widget="slide_category_one2many">
|
||||
<tree>
|
||||
<field name="is_category" invisible="1"/>
|
||||
<field name="is_category" column_invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="int"/>
|
||||
</tree>
|
||||
@@ -109,7 +109,7 @@ QUnit.test("click on section edit the section in place", async (assert) => {
|
||||
<form>
|
||||
<field name="lines" widget="slide_category_one2many">
|
||||
<tree>
|
||||
<field name="is_category" invisible="1"/>
|
||||
<field name="is_category" column_invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="int"/>
|
||||
</tree>
|
||||
@@ -131,7 +131,7 @@ QUnit.test("click on real line opens a dialog", async (assert) => {
|
||||
<form>
|
||||
<field name="lines" widget="slide_category_one2many">
|
||||
<tree>
|
||||
<field name="is_category" invisible="1"/>
|
||||
<field name="is_category" column_invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="int"/>
|
||||
</tree>
|
||||
@@ -152,7 +152,7 @@ QUnit.test("can create section inline", async (assert) => {
|
||||
<form>
|
||||
<field name="lines" widget="slide_category_one2many">
|
||||
<tree>
|
||||
<field name="is_category" invisible="1"/>
|
||||
<field name="is_category" column_invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="int"/>
|
||||
<control>
|
||||
@@ -180,7 +180,7 @@ QUnit.test("creates real record in form dialog", async (assert) => {
|
||||
<form>
|
||||
<field name="lines" widget="slide_category_one2many">
|
||||
<tree>
|
||||
<field name="is_category" invisible="1"/>
|
||||
<field name="is_category" column_invisible="1"/>
|
||||
<field name="name"/>
|
||||
<field name="int"/>
|
||||
<control>
|
||||
|
||||
Reference in New Issue
Block a user