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"/>