[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
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<templates xml:space="preserve">
|
||||
<!-- Many2one -->
|
||||
<t t-inherit="web.PropertyValue" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('o_field_property_many2one_value')]//img" position="attributes">
|
||||
<attribute name="t-on-click.prevent.stop">_onAvatarClicked</attribute>
|
||||
<attribute name="t-att-comodel">props.comodel</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
<!-- Many2many -->
|
||||
<t t-name="mail.Many2manyPropertiesTagsList" t-inherit="web.TagsList"
|
||||
t-inherit-mode="primary" owl="1">
|
||||
<img position="attributes">
|
||||
<attribute name="t-on-click.prevent.stop">() => this._onAvatarClicked(tag_index)</attribute>
|
||||
<attribute name="t-att-comodel">tag.comodel</attribute>
|
||||
</img>
|
||||
</t>
|
||||
</templates>
|
||||
@@ -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";
|
||||
},
|
||||
});
|
||||
@@ -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
|
||||
|
||||
@@ -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')
|
||||
@@ -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
|
||||
|
||||
|
@@ -7,4 +7,5 @@ addModelNamesToFetch([
|
||||
"mail.test.activity",
|
||||
"mail.test.multi.company",
|
||||
"mail.test.multi.company.read",
|
||||
"mail.test.properties",
|
||||
]);
|
||||
|
||||
@@ -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 = `
|
||||
<form string="Form With Avatar Users">
|
||||
<sheet>
|
||||
<field name="name"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="properties"/>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>`;
|
||||
|
||||
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");
|
||||
});
|
||||
@@ -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 &&
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -66,12 +66,13 @@
|
||||
</t>
|
||||
<div
|
||||
t-elif="props.type === 'many2one'"
|
||||
t-attf-class="d-flex flex-row align-items-center o_field_property_many2one_value {{showAvatar ? 'avatar' : ''}}">
|
||||
class="o_field_property_many2one_value d-flex flex-row align-items-center"
|
||||
t-att-class="{'avatar': showAvatar}">
|
||||
<t t-if="props.comodel">
|
||||
<t t-set="hasAccess" t-value="!propertyValue || propertyValue[1]"/>
|
||||
<img t-if="showAvatar and propertyValue[0] and hasAccess"
|
||||
t-attf-src="/web/image/{{props.comodel}}/{{propertyValue[0]}}/avatar_128"
|
||||
class="rounded-circle me-1"/>
|
||||
class="rounded me-1"/>
|
||||
<t t-if="props.readonly || !hasAccess">
|
||||
<a
|
||||
t-if="clickableRelational || !hasAccess"
|
||||
|
||||
@@ -1056,25 +1056,10 @@ export class Record extends DataPoint {
|
||||
const propertyFieldName = `${fieldName}.${definition.name}`;
|
||||
if (propertyFieldName in this.fields) {
|
||||
continue;
|
||||
} else if (["many2one", "many2many"].includes(definition.type) && !definition.comodel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let widget = definition.type;
|
||||
if (["many2one", "many2many"].includes(definition.type)) {
|
||||
if (!definition.comodel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (["res.users", "res.partner"].includes(definition.comodel)) {
|
||||
widget =
|
||||
definition.type === "many2one"
|
||||
? "many2one_avatar"
|
||||
: "many2many_tags_avatar";
|
||||
} else {
|
||||
widget = definition.type === "many2one" ? widget : "many2many_tags";
|
||||
}
|
||||
} else if (widget === "tags") {
|
||||
widget = `property_tags`;
|
||||
}
|
||||
const widget = this._getWidgetFromDefinition(definition);
|
||||
|
||||
const parent = this.data[propertiesField.definition_record];
|
||||
const relatedPropertyField = {
|
||||
@@ -1124,6 +1109,17 @@ export class Record extends DataPoint {
|
||||
}
|
||||
}
|
||||
|
||||
_getWidgetFromDefinition(definition) {
|
||||
if (definition.type === "many2many") {
|
||||
return "many2many_tags";
|
||||
} else if (definition.type === "tags") {
|
||||
return "property_tags";
|
||||
} else if (["res.users", "res.partner"].includes(definition.comodel)) {
|
||||
return definition.type === "many2one" ? "many2one_avatar" : "many2many_tags_avatar";
|
||||
}
|
||||
return definition.type;
|
||||
}
|
||||
|
||||
_createStaticList(fieldName) {
|
||||
const field = this.fields[fieldName];
|
||||
const activeField = this.activeFields[fieldName];
|
||||
|
||||
Reference in New Issue
Block a user