From 09095db293ca8c35aeea291332aa0f4b76666641 Mon Sep 17 00:00:00 2001 From: std-odoo Date: Thu, 2 Mar 2023 14:39:08 +0000 Subject: [PATCH] [IMP] web, mail: open the chat window when clicking on an avatar property Purpose ======= Like standard avatar widget, we want to open the chat window of a user when clicking on a many2many / many2one avatar property. Task-3208449 Part-of: odoo/odoo#114200 --- addons/bus/models/ir_model.py | 5 +- .../fields/properties_field/property_value.js | 54 +++++++++++++ .../properties_field/property_value.scss | 13 ++++ .../properties_field/property_value.xml | 18 +++++ .../mail/static/src/views/relational_model.js | 13 ++++ addons/test_mail/models/__init__.py | 1 + .../test_mail/models/test_discuss_models.py | 15 ++++ addons/test_mail/security/ir.model.access.csv | 1 + .../tests/helpers/model_definitions_setup.js | 1 + .../static/tests/properties_field_tests.js | 77 +++++++++++++++++++ .../views/fields/properties/property_value.js | 1 + .../fields/properties/property_value.scss | 18 +---- .../fields/properties/property_value.xml | 5 +- .../web/static/src/views/relational_model.js | 32 ++++---- 14 files changed, 216 insertions(+), 38 deletions(-) create mode 100644 addons/mail/static/src/views/fields/properties_field/property_value.js create mode 100644 addons/mail/static/src/views/fields/properties_field/property_value.scss create mode 100644 addons/mail/static/src/views/fields/properties_field/property_value.xml create mode 100644 addons/mail/static/src/views/relational_model.js create mode 100644 addons/test_mail/models/test_discuss_models.py create mode 100644 addons/test_mail/static/tests/properties_field_tests.js diff --git a/addons/bus/models/ir_model.py b/addons/bus/models/ir_model.py index 350fe651246..b673c6b089e 100644 --- a/addons/bus/models/ir_model.py +++ b/addons/bus/models/ir_model.py @@ -14,7 +14,10 @@ class IrModel(models.Model): fields_data_by_fname = { fname: field_data for fname, field_data in model.fields_get( - attributes=['name', 'type', 'relation', 'required', 'readonly', 'selection', 'string'] + attributes={ + 'name', 'type', 'relation', 'required', 'readonly', 'selection', + 'string', 'definition_record', 'definition_record_field', + }, ).items() if not field_data.get('relation') or field_data['relation'] in model_names_to_fetch } diff --git a/addons/mail/static/src/views/fields/properties_field/property_value.js b/addons/mail/static/src/views/fields/properties_field/property_value.js new file mode 100644 index 00000000000..91bbacfc2e7 --- /dev/null +++ b/addons/mail/static/src/views/fields/properties_field/property_value.js @@ -0,0 +1,54 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { PropertyValue } from "@web/views/fields/properties/property_value"; +import { TagsList } from "@web/core/tags_list/tags_list"; +import { useOpenChat } from "@mail/web/open_chat_hook"; + +/** + * Allow to open the chatter of the user when we click on the avatar of a Many2one + * property (like we do for many2one_avatar_user widget). + */ +patch(PropertyValue.prototype, "mail.PropertyValue", { + setup() { + this._super(); + + if (this.env.services["mail.thread"]) { + // work only for the res.users model + this.openChat = useOpenChat("res.users"); + } + }, + + _onAvatarClicked() { + if (this.openChat && this.showAvatar && this.props.comodel === "res.users") { + this.openChat(this.props.value[0]); + } + }, +}); + +/** + * Allow to open the chatter of the user when we click on the avatar of a Many2many + * property (like we do for many2many_avatar_user widget). + */ +export class Many2manyPropertiesTagsList extends TagsList { + static template = "mail.Many2manyPropertiesTagsList"; + + setup() { + super.setup(); + if (this.env.services["mail.thread"]) { + this.openChat = useOpenChat("res.users"); + } + } + + _onAvatarClicked(tagIndex) { + const tag = this.props.tags[tagIndex]; + if (this.openChat && tag.comodel === "res.users") { + this.openChat(tag.id); + } + } +} + +PropertyValue.components = { + ...PropertyValue.components, + TagsList: Many2manyPropertiesTagsList, +}; diff --git a/addons/mail/static/src/views/fields/properties_field/property_value.scss b/addons/mail/static/src/views/fields/properties_field/property_value.scss new file mode 100644 index 00000000000..ef3aa40a65d --- /dev/null +++ b/addons/mail/static/src/views/fields/properties_field/property_value.scss @@ -0,0 +1,13 @@ +.o_property_field_value { + .o_field_property_many2one_value img[comodel="res.users"], + .o_m2m_avatar[comodel="res.users"] { + height: var(--Avatar-size, 1.7145em); + width: auto; + aspect-ratio: 1; + + &:hover { + cursor: context-menu; + outline: $border-width solid $o-action; + } + } +} diff --git a/addons/mail/static/src/views/fields/properties_field/property_value.xml b/addons/mail/static/src/views/fields/properties_field/property_value.xml new file mode 100644 index 00000000000..a0e0e90e0ae --- /dev/null +++ b/addons/mail/static/src/views/fields/properties_field/property_value.xml @@ -0,0 +1,18 @@ + + + + + + _onAvatarClicked + props.comodel + + + + + + () => this._onAvatarClicked(tag_index) + tag.comodel + + + diff --git a/addons/mail/static/src/views/relational_model.js b/addons/mail/static/src/views/relational_model.js new file mode 100644 index 00000000000..6ae1a9cd4b7 --- /dev/null +++ b/addons/mail/static/src/views/relational_model.js @@ -0,0 +1,13 @@ +/** @odoo-module **/ + +import { patch } from "@web/core/utils/patch"; +import { Record } from "@web/views/relational_model"; + +patch(Record.prototype, "mail.Record", { + _getWidgetFromDefinition(definition) { + if (definition.comodel !== "res.users") { + return this._super(...arguments); + } + return definition.type === "many2one" ? "many2one_avatar_user" : "many2many_avatar_user"; + }, +}); diff --git a/addons/test_mail/models/__init__.py b/addons/test_mail/models/__init__.py index 64cc5bb37b9..2479510cedc 100644 --- a/addons/test_mail/models/__init__.py +++ b/addons/test_mail/models/__init__.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +from . import test_discuss_models from . import test_mail_corner_case_models from . import test_mail_models from . import test_mail_thread_models diff --git a/addons/test_mail/models/test_discuss_models.py b/addons/test_mail/models/test_discuss_models.py new file mode 100644 index 00000000000..fcd4214cb4c --- /dev/null +++ b/addons/test_mail/models/test_discuss_models.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class MailTestProperties(models.Model): + _description = 'Mail Test Properties' + _name = 'mail.test.properties' + _inherit = ['mail.thread'] + + name = fields.Char('Name') + parent_id = fields.Many2one('mail.test.properties', string='Parent') + properties = fields.Properties('Properties', definition='parent_id.definition_properties') + definition_properties = fields.PropertiesDefinition('Definitions') diff --git a/addons/test_mail/security/ir.model.access.csv b/addons/test_mail/security/ir.model.access.csv index 7cfb1d72f56..2b977ad7634 100644 --- a/addons/test_mail/security/ir.model.access.csv +++ b/addons/test_mail/security/ir.model.access.csv @@ -46,3 +46,4 @@ access_mail_test_track_compute,mail.test.track.compute,model_mail_test_track_com access_mail_test_track_monetary,mail.test.track.monetary,model_mail_test_track_monetary,base.group_user,1,1,1,1 access_mail_test_track_selection_portal,mail.test.track.selection.portal,model_mail_test_track_selection,base.group_portal,0,0,0,0 access_mail_test_track_selection_user,mail.test.track.selection.user,model_mail_test_track_selection,base.group_user,1,1,1,1 +access_mail_test_properties_user,mail.test.properties.user,model_mail_test_properties,base.group_user,1,1,1,1 diff --git a/addons/test_mail/static/tests/helpers/model_definitions_setup.js b/addons/test_mail/static/tests/helpers/model_definitions_setup.js index df480f37c4d..3da3a499455 100644 --- a/addons/test_mail/static/tests/helpers/model_definitions_setup.js +++ b/addons/test_mail/static/tests/helpers/model_definitions_setup.js @@ -7,4 +7,5 @@ addModelNamesToFetch([ "mail.test.activity", "mail.test.multi.company", "mail.test.multi.company.read", + "mail.test.properties", ]); diff --git a/addons/test_mail/static/tests/properties_field_tests.js b/addons/test_mail/static/tests/properties_field_tests.js new file mode 100644 index 00000000000..d4ff15fc0a0 --- /dev/null +++ b/addons/test_mail/static/tests/properties_field_tests.js @@ -0,0 +1,77 @@ +/** @odoo-module **/ + +import { start, startServer, click } from "@mail/../tests/helpers/test_utils"; + +QUnit.module("properties field"); + +/** + * Open a chat window when clicking on an avatar many2one / many2many properties. + */ +async function testPropertyFieldAvatarOpenChat(assert, propertyType) { + const pyEnv = await startServer(); + const view = ` +
+ + + + + +
+ +
+
`; + + const { openView } = await start({ + serverData: { + views: { + "mail.test.properties,false,form": view, + }, + }, + async mockRPC(route) { + if (route.includes("/mail.test.properties/check_access_rights")) { + return true; + } else if (route === "/web/dataset/call_kw/res.users/read") { + assert.step("read res.users"); + return [{ id: userId, partner_id: [partnerId, "Partner Test"] }]; + } else if (route === "/web/dataset/call_kw/res.users/search_read") { + return [{ id: userId, name: "User Test" }]; + } + }, + }); + + const partnerId = pyEnv["res.partner"].create({ name: "Partner Test" }); + const userId = pyEnv["res.users"].create({ name: "User Test" }); + const parentId = pyEnv["mail.test.properties"].create({ name: "Parent" }); + const value = propertyType === "many2one" ? [userId, "User Test"] : [[userId, "User Test"]]; + const childId = pyEnv["mail.test.properties"].create({ + name: "Test", + parent_id: parentId, + properties: [ + { type: propertyType, comodel: "res.users", name: "user", string: "user", value }, + ], + }); + + await openView({ + res_id: childId, + res_model: "mail.test.properties", + views: [[false, "form"]], + }); + + assert.verifySteps([]); + await click( + propertyType === "many2one" ? ".o_field_property_many2one_value img" : ".o_m2m_avatar" + ); + assert.verifySteps(["read res.users"]); + + const chatter = document.querySelectorAll(".o-mail-ChatWindow-name"); + assert.strictEqual(chatter.length, 1); + assert.strictEqual(chatter[0].innerText, "Partner Test"); +} + +QUnit.test("Properties fields: many2one avatar open chat on click", async function (assert) { + await testPropertyFieldAvatarOpenChat(assert, "many2one"); +}); + +QUnit.test("Properties fields: m2m avatar list open chat on click", async function (assert) { + await testPropertyFieldAvatarOpenChat(assert, "many2many"); +}); diff --git a/addons/web/static/src/views/fields/properties/property_value.js b/addons/web/static/src/views/fields/properties/property_value.js index 05c90caf8ed..a15b6ba2fad 100644 --- a/addons/web/static/src/views/fields/properties/property_value.js +++ b/addons/web/static/src/views/fields/properties/property_value.js @@ -103,6 +103,7 @@ export class PropertyValue extends Component { const hasAccess = many2manyValue[1] !== null; return { id: many2manyValue[0], + comodel: this.props.comodel, text: hasAccess ? many2manyValue[1] : _lt("No Access"), onClick: hasAccess && diff --git a/addons/web/static/src/views/fields/properties/property_value.scss b/addons/web/static/src/views/fields/properties/property_value.scss index 8947176184d..7fa33a09efe 100644 --- a/addons/web/static/src/views/fields/properties/property_value.scss +++ b/addons/web/static/src/views/fields/properties/property_value.scss @@ -19,18 +19,6 @@ input.o_field_property_input[type=number] { } .o_field_property_many2one_value { - img { - height: 1.4em; - width: 1.4em; - object-fit: cover; - } - - &.avatar .o_input_dropdown{ - // counter the avatar size to keep things aligned - margin-left: -1.6em; - padding-left: 1.6em; - } - a.disabled { pointer-events: none; color: $gray-400; @@ -45,6 +33,7 @@ input.o_field_property_input[type=number] { } .o_delete { line-height: 1; + margin-right: 5px; } .o_input_dropdown { width: 100px !important; @@ -61,11 +50,6 @@ input.o_field_property_input[type=number] { } .o_field_property_many2many_value.avatar .o_tag{ - img { - margin: -0.3em; - margin-right: 0.3em; - } - .o_tag_badge_text { padding: 0px !important; } diff --git a/addons/web/static/src/views/fields/properties/property_value.xml b/addons/web/static/src/views/fields/properties/property_value.xml index e9599f52642..9fb554dc438 100644 --- a/addons/web/static/src/views/fields/properties/property_value.xml +++ b/addons/web/static/src/views/fields/properties/property_value.xml @@ -66,12 +66,13 @@
+ class="o_field_property_many2one_value d-flex flex-row align-items-center" + t-att-class="{'avatar': showAvatar}"> + class="rounded me-1"/>