[FIX] *: domains should not have as many binary operator as there are terms
* modify client-side domain normalisation to error if the domain is invalid (not enough terms / segments for the number of operators) * add better error reporting to attrs / modifiers parsing * add a few layered error augmentation to provide clearer context e.g. "error: invalid domain <thing>" is helpful but "error while parsing modifiers for field foo: modifier invisible: invalid domain <thing>" is much more helpful * rework _evalModifiers to deduplicate it in order to more easily implement this contextual augmentation * test that improper domains are properly found improper * fix a bunch of incorrect attrs domains * also removed an apparently undefined (& unused) "options" argument to a _applyModifiers call closes odoo/odoo#44642 Related: odoo/enterprise#8175 Signed-off-by: Xavier Morel (xmo) <xmo@odoo.com>
This commit is contained in:
@@ -634,7 +634,7 @@
|
||||
placeholder="Terms"
|
||||
attrs="{'invisible': [('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
|
||||
<span class="o_form_label mx-3 oe_edit_only"
|
||||
attrs="{'invisible': [ '|', '|', '|', ('state', '!=', 'draft'), ('invoice_payment_term_id', '!=', False), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"> or </span>
|
||||
attrs="{'invisible': [ '|', '|', ('state', '!=', 'draft'), ('invoice_payment_term_id', '!=', False), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"> or </span>
|
||||
<field name="invoice_date_due" force_save="1"
|
||||
placeholder="Date"
|
||||
attrs="{'invisible': ['|', ('invoice_payment_term_id', '!=', False), ('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund', 'out_receipt', 'in_receipt'))]}"/>
|
||||
@@ -1024,7 +1024,7 @@
|
||||
</sheet>
|
||||
<!-- Attachment preview -->
|
||||
<div class="o_attachment_preview"
|
||||
attrs="{'invisible': ['|', '|',
|
||||
attrs="{'invisible': ['|',
|
||||
('type', 'not in', ('out_invoice', 'out_refund', 'in_invoice', 'in_refund')),
|
||||
('state', '!=', 'draft')]}" />
|
||||
<!-- Chatter -->
|
||||
|
||||
@@ -612,12 +612,12 @@ action = model.setting_init_bank_account_action()
|
||||
<label for="balance_start"/>
|
||||
<div>
|
||||
<field class="oe_inline" name="balance_start"/>
|
||||
<button name="open_cashbox_id" attrs="{'invisible': ['|','|',('state','!=','open'),('journal_type','!=','cash')]}" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'start'}"/>
|
||||
<button name="open_cashbox_id" attrs="{'invisible': ['|',('state','!=','open'),('journal_type','!=','cash')]}" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'start'}"/>
|
||||
</div>
|
||||
<label for="balance_end_real"/>
|
||||
<div>
|
||||
<field class="oe_inline" name="balance_end_real"/>
|
||||
<button name="open_cashbox_id" attrs="{'invisible': ['|','|',('state','!=','open'),('journal_type','!=','cash')]}" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'close'}"/>
|
||||
<button name="open_cashbox_id" attrs="{'invisible': ['|',('state','!=','open'),('journal_type','!=','cash')]}" string="→ Count" type="object" class="oe_edit_only oe_link oe_inline" context="{'balance':'close'}"/>
|
||||
</div>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
class="oe_stat_button"
|
||||
disabled="1"
|
||||
invisible="context.get('from_my_profile', False)"
|
||||
attrs="{'invisible': ['|', ('hr_presence_state', '=', 'absent')]}">
|
||||
attrs="{'invisible': [('hr_presence_state', '=', 'absent')]}">
|
||||
<div role="img" class="fa fa-fw fa-circle text-success o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'present')]}" aria-label="Available" title="Available"/>
|
||||
<div role="img" class="fa fa-fw fa-circle text-warning o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'to_define')]}" aria-label="Away" title="Away"/>
|
||||
<div role="img" class="fa fa-fw fa-circle text-danger o_button_icon" attrs="{'invisible': [('hr_presence_state', '!=', 'absent')]}" aria-label="Not available" title="Not available"/>
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[@id='hr_presence_button']" position="attributes">
|
||||
<attribute name="attrs">
|
||||
{'invisible': ['|', '|', ('hr_presence_state', '=', 'absent'), ('attendance_state', '=', 'checked_in')]}
|
||||
{'invisible': ['|', ('hr_presence_state', '=', 'absent'), ('attendance_state', '=', 'checked_in')]}
|
||||
</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//div[@name='button_box']" position="inside">
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<field name="subject" placeholder="Subject..." required="True"/>
|
||||
<!-- mass post -->
|
||||
<field name="notify"
|
||||
attrs="{'invisible':['|', ('composition_mode', '!=', 'mass_post')]}"/>
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_post')]}"/>
|
||||
<!-- mass mailing -->
|
||||
<field name="no_auto_thread" attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="reply_to" placeholder="Email address to redirect replies..."
|
||||
|
||||
@@ -373,7 +373,7 @@
|
||||
<!-- change attrs of fields added in view_template_property_form
|
||||
to restrict the display for templates -->
|
||||
<xpath expr="//group[@name='group_lots_and_weight']" position="attributes">
|
||||
<attribute name="attrs">{'invisible':['|', ('type', 'not in', ['product', 'consu'])]}</attribute>
|
||||
<attribute name="attrs">{'invisible': [('type', 'not in', ['product', 'consu'])]}</attribute>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//group[@name='group_lots_and_weight']" position="inside">
|
||||
|
||||
@@ -383,8 +383,10 @@ var Domain = collections.Tree.extend({
|
||||
* to normalize (! will be normalized in-place)
|
||||
* @returns {Array} the normalized JS prefix-array representation of the
|
||||
* given domain
|
||||
* @throws {Error} if the domain is invalid and can't be normalised
|
||||
*/
|
||||
normalizeArray: function (domain) {
|
||||
if (domain.length === 0) { return domain; }
|
||||
var expected = 1;
|
||||
_.each(domain, function (item) {
|
||||
if (item === "&" || item === "|") {
|
||||
@@ -395,6 +397,11 @@ var Domain = collections.Tree.extend({
|
||||
});
|
||||
if (expected < 0) {
|
||||
domain.unshift.apply(domain, _.times(Math.abs(expected), _.constant("&")));
|
||||
} else if (expected > 0) {
|
||||
throw new Error(_.str.sprintf(
|
||||
"invalid domain %s (missing %d segment(s))",
|
||||
JSON.stringify(domain), expected
|
||||
));
|
||||
}
|
||||
return domain;
|
||||
},
|
||||
|
||||
@@ -2259,31 +2259,27 @@ var BasicModel = AbstractModel.extend({
|
||||
* context.
|
||||
* @param {Object} modifiers
|
||||
* @returns {Object}
|
||||
* @throws {Error} if one of the modifier domains is invalid
|
||||
*/
|
||||
_evalModifiers: function (element, modifiers) {
|
||||
var result = {};
|
||||
var self = this;
|
||||
var evalContext;
|
||||
function evalModifier(mod) {
|
||||
let evalContext = null;
|
||||
const evaluated = {};
|
||||
for (const k of ['invisible', 'column_invisible', 'readonly', 'required']) {
|
||||
const mod = modifiers[k];
|
||||
if (mod === undefined || mod === false || mod === true) {
|
||||
return !!mod;
|
||||
if (k in modifiers) {
|
||||
evaluated[k] = !!mod;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
evalContext = evalContext || this._getEvalContext(element);
|
||||
evaluated[k] = new Domain(mod, evalContext).compute(evalContext);
|
||||
} catch (e) {
|
||||
throw new Error(_.str.sprintf('for modifier "%s": %s', k, e.message));
|
||||
}
|
||||
evalContext = evalContext || self._getEvalContext(element);
|
||||
return new Domain(mod, evalContext).compute(evalContext);
|
||||
}
|
||||
if ('invisible' in modifiers) {
|
||||
result.invisible = evalModifier(modifiers.invisible);
|
||||
}
|
||||
if ('column_invisible' in modifiers) {
|
||||
result.column_invisible = evalModifier(modifiers.column_invisible);
|
||||
}
|
||||
if ('readonly' in modifiers) {
|
||||
result.readonly = evalModifier(modifiers.readonly);
|
||||
}
|
||||
if ('required' in modifiers) {
|
||||
result.required = evalModifier(modifiers.required);
|
||||
}
|
||||
return result;
|
||||
return evaluated;
|
||||
},
|
||||
/**
|
||||
* Fetch all name_gets for the many2ones in a group
|
||||
|
||||
@@ -547,6 +547,7 @@ var BasicRenderer = AbstractRenderer.extend(WidgetAdapterMixin, {
|
||||
* value (if not given, it is set to this.mode, the mode of the renderer)
|
||||
* @returns {Object} for code efficiency, returns the last evaluated
|
||||
* modifiers for the given node and record.
|
||||
* @throws {Error} if one of the modifier domains is not valid
|
||||
*/
|
||||
_registerModifiers: function (node, record, element, options) {
|
||||
options = options || {};
|
||||
@@ -575,7 +576,15 @@ var BasicRenderer = AbstractRenderer.extend(WidgetAdapterMixin, {
|
||||
|
||||
// Evaluate if necessary
|
||||
if (!modifiersData.evaluatedModifiers[record.id]) {
|
||||
modifiersData.evaluatedModifiers[record.id] = record.evalModifiers(modifiersData.modifiers);
|
||||
try {
|
||||
modifiersData.evaluatedModifiers[record.id] = record.evalModifiers(modifiersData.modifiers);
|
||||
} catch (e) {
|
||||
throw new Error(_.str.sprintf(
|
||||
"While parsing modifiers for %s%s: %s",
|
||||
node.tag, node.tag === 'field' ? ' ' + node.attrs.name : '',
|
||||
e.message
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Element might not be given yet (a second call to the function can
|
||||
@@ -597,7 +606,7 @@ var BasicRenderer = AbstractRenderer.extend(WidgetAdapterMixin, {
|
||||
}
|
||||
modifiersData.elementsByRecord[record.id].push(newElement);
|
||||
|
||||
this._applyModifiers(modifiersData, record, newElement, options);
|
||||
this._applyModifiers(modifiersData, record, newElement);
|
||||
}
|
||||
|
||||
return modifiersData.evaluatedModifiers[record.id];
|
||||
|
||||
@@ -7,6 +7,11 @@ QUnit.module('core', {}, function () {
|
||||
|
||||
QUnit.module('domain');
|
||||
|
||||
QUnit.test("empty", function (assert) {
|
||||
assert.expect(1);
|
||||
assert.ok(new Domain([]).compute({}));
|
||||
});
|
||||
|
||||
QUnit.test("basic", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
@@ -59,6 +64,22 @@ QUnit.module('core', {}, function () {
|
||||
assert.notOk(new Domain(0).compute({}));
|
||||
});
|
||||
|
||||
QUnit.test("invalid domains should not succeed", function (assert) {
|
||||
assert.expect(3);
|
||||
assert.throws(
|
||||
() => new Domain(['|', ['hr_presence_state', '=', 'absent']]),
|
||||
/invalid domain .* \(missing 1 segment/
|
||||
);
|
||||
assert.throws(
|
||||
() => new Domain(['|', '|', ['hr_presence_state', '=', 'absent'], ['attendance_state', '=', 'checked_in']]),
|
||||
/invalid domain .* \(missing 1 segment/
|
||||
);
|
||||
assert.throws(
|
||||
() => new Domain(['&', ['composition_mode', '!=', 'mass_post']]),
|
||||
/invalid domain .* \(missing 1 segment/
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test("domain <=> condition", function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user