From 1f438445495b21e454c76e9a526370fefaf72efb Mon Sep 17 00:00:00 2001 From: FrancoisGe Date: Mon, 29 Aug 2022 14:48:03 +0000 Subject: [PATCH] [FIX] web: save an empty many2many_tags required Before this commit, the required attribute has no effect on the x2many fields. It will always be valid unless one of its records is invalid. Problem: In some cases, we would like to be able to make some x2many fields required. For example, many2many_tags should be invalid if it contains no records. In legacy, if a field is required, then it has the responsibility to evaluate if its value is valid. To do this, each field contained the isSet function which returns true if the value is set. In our new architecture, it is the model that evaluates if the value of a field is valid based on the field type. It is therefore no longer possible to customise the evaluation of the validity of a field based on the FieldComponent. Solution: This commit will reintroduce the possibility to customize the evaluation of the validity of a field based on the FieldComponent. To do this, we add the possibility to define a static isSet function on the FieldComponent. If this function is present, it will be used to evaluate if the field value is set or not. closes odoo/odoo#98873 Signed-off-by: Aaron Bohy (aab) --- .../src/views/basic_relational_model.js | 13 ++++++++++-- .../many2many_tags/many2many_tags_field.js | 1 + .../web/static/src/views/relational_model.js | 11 +++++++++- .../fields/many2many_tags_field_tests.js | 21 +++++++++++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/addons/web/static/src/views/basic_relational_model.js b/addons/web/static/src/views/basic_relational_model.js index 5f25eb4aacc..76620713b17 100644 --- a/addons/web/static/src/views/basic_relational_model.js +++ b/addons/web/static/src/views/basic_relational_model.js @@ -355,8 +355,8 @@ export class Record extends DataPoint { } for (const fieldName in this.activeFields) { const fieldType = this.fields[fieldName].type; + const activeField = this.activeFields[fieldName]; if (fieldName in this._requiredFields) { - const activeField = this.activeFields[fieldName]; if ( !evalDomain(this._requiredFields[fieldName], this.evalContext) || (activeField && activeField.alwaysInvisible) @@ -365,6 +365,15 @@ export class Record extends DataPoint { continue; } } + + const isSet = + activeField && activeField.FieldComponent && activeField.FieldComponent.isSet; + + if (this.isRequired(fieldName) && isSet && !isSet(this.data[fieldName])) { + this.setInvalidField(fieldName); + continue; + } + switch (fieldType) { case "boolean": case "float": @@ -383,7 +392,7 @@ export class Record extends DataPoint { } break; default: - if (this.isRequired(fieldName) && !this.data[fieldName]) { + if (!isSet && this.isRequired(fieldName) && !this.data[fieldName]) { this.setInvalidField(fieldName); } } diff --git a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js index 8fab6d0daa4..c0585db16ec 100644 --- a/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js +++ b/addons/web/static/src/views/fields/many2many_tags/many2many_tags_field.js @@ -304,6 +304,7 @@ Many2ManyTagsField.supportedTypes = ["many2many"]; Many2ManyTagsField.fieldsToFetch = { display_name: { name: "display_name", type: "char" }, }; +Many2ManyTagsField.isSet = (value) => value.count > 0; Many2ManyTagsField.extractProps = ({ attrs, field }) => { return { diff --git a/addons/web/static/src/views/relational_model.js b/addons/web/static/src/views/relational_model.js index ec01a45cd02..3503c325d45 100644 --- a/addons/web/static/src/views/relational_model.js +++ b/addons/web/static/src/views/relational_model.js @@ -579,6 +579,15 @@ export class Record extends DataPoint { this._removeInvalidFields([fieldName]); continue; } + + const isSet = + activeField && activeField.FieldComponent && activeField.FieldComponent.isSet; + + if (this.isRequired(fieldName) && isSet && !isSet(this.data[fieldName])) { + this.setInvalidField(fieldName); + continue; + } + switch (fieldType) { case "boolean": case "float": @@ -592,7 +601,7 @@ export class Record extends DataPoint { } break; default: - if (this.isRequired(fieldName) && !this.data[fieldName]) { + if (!isSet && this.isRequired(fieldName) && !this.data[fieldName]) { this.setInvalidField(fieldName); } } diff --git a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js index f87166c1c26..defc1ff834e 100644 --- a/addons/web/static/tests/views/fields/many2many_tags_field_tests.js +++ b/addons/web/static/tests/views/fields/many2many_tags_field_tests.js @@ -1559,4 +1559,25 @@ QUnit.module("Fields", (hooks) => { "goldsilver" ); }); + + QUnit.test("save a record with an empty many2many_tags required", async function (assert) { + assert.expect(3); + + const form = await makeView({ + type: "form", + resModel: "partner", + serverData, + arch: '
', + }); + + patchWithCleanup(form.env.services.notification, { + add: (message, params) => { + assert.strictEqual(message.toString(), "
  • pokemon
"); + assert.deepEqual(params, { title: "Invalid fields: ", type: "danger" }); + }, + }); + + await clickSave(target); + assert.containsOnce(target, "[name='timmy'].o_field_invalid"); + }); });