[IMP][ADD] mail, various: timeline view.
This commit introduces a new view type, the timeline view. This view is intended to display messages like the previous chatter. This is now a view like the form or list view. Like other views it will be possible to have custom templates for some specific needs, allowing customization. [IMP] mail: the value tracking is modified. Previously messages were created containing the modified values. Those values are now stored, using a new model mail.tracking.value. The message body is dynamically build based on the values. Tests have been updated. This version is temporary. Indeed two main modifications will come in a short future : - the new design will improve the display - the slack mode will change the way the Inbox and notifications are managed All glory to the Hypnotoad. Special thanks to Valerie Pirenne (vpi), Jerome Maes (jem) and Richat Mathot (rim) that did not code but said a lot of things. Martin Trigaux (mat) did nothing, a bit like for the slides modules, but he is busy sending emails.
This commit is contained in:
committed by
Thibault Delavallée
parent
946d245c2b
commit
c24332e6da
@@ -1,185 +1,146 @@
|
||||
@charset "UTF-8";
|
||||
.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_goal {
|
||||
width: 230px;
|
||||
min-height: 200px;
|
||||
}
|
||||
min-height: 200px; }
|
||||
.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_badge {
|
||||
width: 250px;
|
||||
min-height: 150px; }
|
||||
.openerp .oe_kanban_badge_avatars {
|
||||
margin-top: 8px; }
|
||||
.openerp .oe_kanban_badge_avatars img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding-left: 0;
|
||||
margin-top: 3px;
|
||||
border-radius: 2px;
|
||||
-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); }
|
||||
.openerp .oe_margin_top_8 {
|
||||
margin-top: 8px; }
|
||||
.openerp .oe_margin_top_16 {
|
||||
margin-top: 16px; }
|
||||
.openerp .oe_kanban_goal .oe_goal_state_block {
|
||||
width: 200px;
|
||||
height: 130px;
|
||||
margin: auto;
|
||||
margin-bottom: -20px;
|
||||
}
|
||||
.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state {
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
padding-top: 30px;
|
||||
}
|
||||
.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state .fa {
|
||||
font-size: 3em;
|
||||
margin-top: -17px;
|
||||
margin-left: 17px;
|
||||
}
|
||||
margin-bottom: -20px; }
|
||||
.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state {
|
||||
font-size: 2.5em;
|
||||
font-weight: bold;
|
||||
padding-top: 30px; }
|
||||
.openerp .oe_kanban_goal .oe_goal_state_block .oe_goal_state .fa {
|
||||
font-size: 3em;
|
||||
margin-top: -17px;
|
||||
margin-left: 17px; }
|
||||
.openerp .oe_kanban_goal .oe_goal_state_block, .openerp .oe_kanban_goal p {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_card.oe_kanban_badge {
|
||||
width: 250px;
|
||||
min-height: 150px;
|
||||
}
|
||||
.openerp .oe_kanban_badge_avatars {
|
||||
width: 30px;
|
||||
}
|
||||
.openerp .oe_margin_top_8 {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.openerp .oe_margin_top_16 {
|
||||
margin-top: 16px;
|
||||
}
|
||||
text-align: center; }
|
||||
.openerp .oe_kanban_content .oe_goal_gauge:first-child {
|
||||
margin: auto; }
|
||||
.openerp .oe_kanban_content .oe_goal_gauge svg {
|
||||
margin-top: -20px; }
|
||||
.openerp .oe_no_overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
overflow: hidden; }
|
||||
.openerp .oe_red {
|
||||
color: red;
|
||||
}
|
||||
color: red; }
|
||||
.openerp .oe_green {
|
||||
color: green;
|
||||
}
|
||||
color: green; }
|
||||
.openerp .oe_orange {
|
||||
color: orange;
|
||||
}
|
||||
color: orange; }
|
||||
.openerp .oe_form td .oe_no_padding {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail_wall_aside {
|
||||
margin-top: 15px;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 280px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_challenge_list {
|
||||
background-color: #ededf6;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_suggestion {
|
||||
background-color: #d3def1;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail_wall_aside .oe_gamification_suggestion ul {
|
||||
padding-left: 15px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail_wall_aside h4, .openerp .oe_mail_wall .oe_mail_wall_aside .oe_goals_list .oe_thead {
|
||||
text-align: center;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail_wall_aside > div {
|
||||
border-bottom: solid 5px white;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal {
|
||||
border-bottom: solid 3px white;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_update_challenge.fa, .openerp .oe_mail_wall .oe_goal .oe_goal_action.fa {
|
||||
visibility: hidden;
|
||||
font-size: 16px;
|
||||
float: right;
|
||||
position: relative;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal div:hover > .oe_update_challenge, .openerp .oe_mail_wall .oe_goal div:hover > .oe_goal_action, .openerp .oe_mail_wall .oe_goal th:hover > .oe_goal_action {
|
||||
visibility: visible;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list {
|
||||
padding-left: 0;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress div {
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress::before {
|
||||
content: "•";
|
||||
padding-right: 4px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached::before {
|
||||
content: "✓";
|
||||
padding-right: 0;
|
||||
margin-left: -2px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached .oe_cell {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell.oe_goal_current {
|
||||
font-size: 150%;
|
||||
font-weight: bold;
|
||||
min-width: 50px;
|
||||
padding: 0 5px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_outer_box {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
border: solid 1px rgba(0, 0, 0, 0.03);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_outer_box.oe_no_progress {
|
||||
border: none;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_progress_background {
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_goal_progress {
|
||||
background-color: #d4e9de;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_thead {
|
||||
font-weight: normal;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_goal .oe_goals_list .oe_cell {
|
||||
padding: 3px 0;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_table.oe_goals_list .col0 {
|
||||
margin-left: -6px; }
|
||||
.openerp .oe_timeline .oe_tl_inbox_aside .oe_gamification_suggestion {
|
||||
background-color: #d3def1; }
|
||||
.openerp .oe_timeline .oe_tl_inbox_aside .oe_gamification_suggestion ul {
|
||||
padding-left: 15px; }
|
||||
.openerp .oe_timeline .oe_tl_inbox_aside .oe_gamification_suggestion span {
|
||||
background-color: #f0eeee;
|
||||
display: inline-block;
|
||||
height: 25px;
|
||||
vertical-align: middle;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-top: -20px;
|
||||
padding-left: 0 5px 0px 5px; }
|
||||
.openerp .oe_timeline .oe_goal {
|
||||
background-color: #f0eeee;
|
||||
border: solid 1px #cacaca;
|
||||
margin-top: 20px;
|
||||
padding-left: 10px; }
|
||||
.openerp .oe_timeline .oe_goal .oe_update_challenge.fa, .openerp .oe_timeline .oe_goal .oe_goal_action.fa {
|
||||
visibility: hidden;
|
||||
font-size: 16px;
|
||||
float: right;
|
||||
position: relative; }
|
||||
.openerp .oe_timeline .oe_goal div:hover > .oe_update_challenge, .openerp .oe_timeline .oe_goal div:hover > .oe_goal_action, .openerp .oe_timeline .oe_goal th:hover > .oe_goal_action {
|
||||
visibility: visible; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list {
|
||||
padding-left: 0;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
width: 100%; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress div {
|
||||
display: inline-block; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress::before {
|
||||
content: "•";
|
||||
padding-right: 4px; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached::before {
|
||||
content: "✓";
|
||||
padding-right: 0;
|
||||
margin-left: -2px; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_no_progress.oe_goal_reached .oe_cell {
|
||||
text-decoration: line-through; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_cell.oe_goal_current {
|
||||
font-size: 150%;
|
||||
font-weight: bold;
|
||||
min-width: 50px;
|
||||
padding: 0 5px; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_outer_box {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
vertical-align: middle;
|
||||
width: 100%;
|
||||
border: solid 1px rgba(0, 0, 0, 0.03);
|
||||
border-radius: 2px; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_outer_box.oe_no_progress {
|
||||
border: none; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_progress_background {
|
||||
background-color: white;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -2;
|
||||
top: 0;
|
||||
left: 0; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_goal_progress {
|
||||
background-color: #d4e9de;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 0;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_thead {
|
||||
font-weight: normal;
|
||||
padding: 5px;
|
||||
text-align: center; }
|
||||
.openerp .oe_timeline .oe_goal .oe_goals_list .oe_cell {
|
||||
padding: 3px 0; }
|
||||
.openerp .oe_timeline .oe_table.oe_goals_list .col0 {
|
||||
font-size: 200%;
|
||||
font-weight: bold;
|
||||
width: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_table.oe_goals_list .col1 {
|
||||
padding: 0 5px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_table.oe_goals_list .col2 {
|
||||
width: auto;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_user_avatar {
|
||||
text-align: center; }
|
||||
.openerp .oe_timeline .oe_table.oe_goals_list .col1 {
|
||||
padding: 0 5px; }
|
||||
.openerp .oe_timeline .oe_table.oe_goals_list .col2 {
|
||||
width: auto; }
|
||||
.openerp .oe_timeline .oe_user_avatar {
|
||||
width: 24px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_mail {
|
||||
display: inline-block;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_table {
|
||||
display: table;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_row {
|
||||
display: table-row;
|
||||
}
|
||||
.openerp .oe_mail_wall .oe_cell {
|
||||
padding-right: 5px; }
|
||||
.openerp .oe_timeline .oe_table {
|
||||
display: table; }
|
||||
.openerp .oe_timeline .oe_row {
|
||||
display: table-row; }
|
||||
.openerp .oe_timeline .oe_cell {
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
vertical-align: middle; }
|
||||
|
||||
@@ -62,34 +62,29 @@
|
||||
.oe_form td .oe_no_padding
|
||||
margin-left: -6px
|
||||
|
||||
|
||||
.oe_mail_wall
|
||||
.oe_mail_wall_aside
|
||||
margin-top: 15px
|
||||
position: relative
|
||||
display: inline-block
|
||||
vertical-align: top
|
||||
width: 280px
|
||||
border-radius: 2px
|
||||
|
||||
.oe_gamification_challenge_list
|
||||
background-color: #EDEDF6
|
||||
.oe_timeline
|
||||
.oe_tl_inbox_aside
|
||||
.oe_gamification_suggestion
|
||||
background-color: rgb(211, 222, 241)
|
||||
ul
|
||||
background-color: #d3def1
|
||||
|
||||
ul
|
||||
padding-left: 15px
|
||||
|
||||
h4, .oe_goals_list .oe_thead
|
||||
text-align: center
|
||||
padding-bottom: 15px
|
||||
|
||||
.oe_mail_wall_aside > div
|
||||
border-bottom: solid 5px white
|
||||
border-radius: 2px
|
||||
span
|
||||
background-color: #f0eeee
|
||||
display: inline-block
|
||||
height: 25px
|
||||
vertical-align: middle
|
||||
font-size: 14px
|
||||
font-weight: bold
|
||||
margin-top: -20px
|
||||
padding-left: 0 5px 0px 5px
|
||||
|
||||
.oe_goal
|
||||
border-bottom: solid 3px white
|
||||
padding: 5px 10px
|
||||
background-color: #f0eeee
|
||||
border: solid 1px #CACACA
|
||||
margin-top: 20px
|
||||
padding-left: 10px
|
||||
.oe_update_challenge.fa, .oe_goal_action.fa
|
||||
visibility: hidden
|
||||
font-size: 16px
|
||||
@@ -97,7 +92,6 @@
|
||||
position: relative
|
||||
div:hover > .oe_update_challenge, div:hover > .oe_goal_action, th:hover > .oe_goal_action
|
||||
visibility: visible
|
||||
|
||||
.oe_goals_list
|
||||
padding-left: 0
|
||||
margin-top: 5px
|
||||
@@ -170,18 +164,15 @@
|
||||
padding: 0 5px
|
||||
.col2
|
||||
width: auto
|
||||
|
||||
|
||||
.oe_user_avatar
|
||||
width: 24px
|
||||
padding-right: 5px
|
||||
|
||||
.oe_mail
|
||||
display: inline-block
|
||||
|
||||
.oe_table
|
||||
display: table
|
||||
.oe_row
|
||||
display: table-row
|
||||
.oe_cell
|
||||
display: table-cell
|
||||
vertical-align: middle
|
||||
vertical-align: middle
|
||||
@@ -119,11 +119,11 @@ var Sidebar = Widget.extend({
|
||||
}
|
||||
});
|
||||
|
||||
mail.Wall.include({
|
||||
mail.TimelineView.include({
|
||||
start: function() {
|
||||
this._super();
|
||||
var sidebar = new Sidebar(this);
|
||||
sidebar.appendTo($('.oe_mail_wall_aside'));
|
||||
sidebar.appendTo($('.o_timeline__inbox_aside'));
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
</t>
|
||||
<t t-name="gamification.ChallengeSummary">
|
||||
<div class="oe_goal">
|
||||
<div>
|
||||
<div class="oe_gamification_title">
|
||||
<i class="oe_update_challenge fa fa-refresh" rol="button" t-attf-id="{challenge.id}"/>
|
||||
<h4><t t-esc="challenge.name" /></h4>
|
||||
<span><t t-esc="challenge.name" /></span>
|
||||
</div>
|
||||
|
||||
<t t-if="challenge.visibility_mode == 'personal'">
|
||||
|
||||
+18
-13
@@ -1,19 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="mail.action_mail_inbox_feeds" model="ir.actions.client">
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('to_read', '=', True),
|
||||
('starred', '=', False),
|
||||
],
|
||||
'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'read_action': 'read',
|
||||
'show_compose_message': True
|
||||
}""/>
|
||||
<record model="ir.actions.act_window" id="mail.action_mail_inbox_feeds">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">timeline</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'options' : {'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'show_write_to_followers': True}
|
||||
}</field>
|
||||
<field name="domain">[
|
||||
'|',
|
||||
('notification_ids.partner_id.user_ids', 'in', [uid]),
|
||||
('author_id.user_ids', 'in', [uid]),
|
||||
]</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -72,7 +72,7 @@ var Employees = Widget.extend({
|
||||
}
|
||||
});
|
||||
|
||||
mail.WallSidebar.include({
|
||||
mail.Sidebar.include({
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var sug_employees = new Employees(this);
|
||||
|
||||
@@ -31,6 +31,7 @@ Main Features
|
||||
'wizard/invite_view.xml',
|
||||
'wizard/mail_compose_message_view.xml',
|
||||
'views/mail_message_subtype_views.xml',
|
||||
'views/mail_tracking_views.xml',
|
||||
'views/mail_message_views.xml',
|
||||
'views/mail_mail_views.xml',
|
||||
'views/mail_followers_views.xml',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import ir_attachment
|
||||
import mail_message_subtype
|
||||
import mail_tracking_value
|
||||
import mail_alias
|
||||
import mail_followers
|
||||
import mail_message
|
||||
|
||||
@@ -76,27 +76,28 @@ class MailGroup(models.Model):
|
||||
).create(vals)
|
||||
group.alias_id.write({"alias_force_thread_id": group.id, 'alias_parent_thread_id': group.id})
|
||||
|
||||
# Create client action for this group and link the menu to it
|
||||
# Create action window for this group and link the menu to it
|
||||
inbox_ref = self.env.ref('mail.action_mail_group_feeds')
|
||||
search_ref = self.env.ref('mail.view_message_search')
|
||||
params = {
|
||||
'search_view_id': search_ref.id,
|
||||
'domain': [
|
||||
('model', '=', 'mail.group'),
|
||||
('res_id', '=', group.id),
|
||||
],
|
||||
'context': {
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': group.id,
|
||||
},
|
||||
'res_model': 'mail.message',
|
||||
'thread_level': 1,
|
||||
'header_description': group._get_header(),
|
||||
'view_mailbox': True,
|
||||
'compose_placeholder': 'Send a message to the group',
|
||||
}
|
||||
new_action = inbox_ref.sudo().copy(default={'params': str(params), 'name': vals['name']})
|
||||
menu.write({'action': 'ir.actions.client,%d' % new_action.id, 'mail_group_id': group.id})
|
||||
act_domain = [('model', '=', 'mail.group'), ('res_id', '=', group.id)]
|
||||
act_context = {'default_model': 'mail.group',
|
||||
'default_res_id': group.id,
|
||||
'options': {'view_mailbox': False,
|
||||
'view_inbox': True,
|
||||
'read_action': 'read',
|
||||
'compose_placeholder': 'Send a message to the group'},
|
||||
'params': {'header_description': group._get_header(),
|
||||
'name': vals['name'],}
|
||||
}
|
||||
act_res_model = 'mail.message'
|
||||
act_search_view_id = search_ref.id
|
||||
|
||||
new_action = inbox_ref.sudo().copy(default={'domain': act_domain,
|
||||
'context': act_context,
|
||||
'res_model': act_res_model,
|
||||
'search_view_id': act_search_view_id,
|
||||
'name': vals['name']})
|
||||
menu.write({'action': 'ir.actions.act_window,%d' % new_action.id, 'mail_group_id': group.id})
|
||||
|
||||
if vals.get('group_ids'):
|
||||
group._subscribe_users()
|
||||
@@ -126,12 +127,11 @@ class MailGroup(models.Model):
|
||||
result = super(MailGroup, self).write(vals)
|
||||
if vals.get('group_ids'):
|
||||
self._subscribe_users()
|
||||
# if description, name or alias is changed: update client action
|
||||
# if description, name or alias is changed: update action window
|
||||
if vals.get('description') or vals.get('name') or vals.get('alias_id') or vals.get('alias_name'):
|
||||
for group in self:
|
||||
new_params = group.menu_id.action.params
|
||||
new_params['header_description'] = group._generate_header_description()
|
||||
group.menu_id.action.sudo().write({'params': str(new_params)})
|
||||
if (vals.get('name')):
|
||||
group.menu_id.action.sudo().write({'name': vals.get('name')})
|
||||
# if name is changed: update menu
|
||||
if vals.get('name'):
|
||||
self.sudo().mapped('menu_id').write({'name': vals.get('name')})
|
||||
|
||||
+115
-148
@@ -31,7 +31,7 @@ class Message(models.Model):
|
||||
|
||||
_message_read_limit = 30
|
||||
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
|
||||
'message_type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name']
|
||||
'message_type', 'vote_user_ids', 'attachment_ids', 'tracking_value_ids' ,'author_id', 'partner_ids', 'record_name']
|
||||
_message_record_name_length = 18
|
||||
_message_read_more_limit = 1024
|
||||
|
||||
@@ -98,6 +98,7 @@ class Message(models.Model):
|
||||
'res.users', 'mail_vote', 'message_id', 'user_id', string='Votes',
|
||||
help='Users that voted for this message')
|
||||
mail_server_id = fields.Many2one('ir.mail_server', 'Outgoing mail server', readonly=1)
|
||||
tracking_value_ids = fields.One2many('mail.tracking.value', 'mail_message_id', string='Tracking values')
|
||||
|
||||
@api.depends('notification_ids')
|
||||
def _get_to_read(self):
|
||||
@@ -236,32 +237,43 @@ class Message(models.Model):
|
||||
"""
|
||||
pid = self.env.user.partner_id.id
|
||||
|
||||
# 1. Aggregate partners (author_id and partner_ids) and attachments
|
||||
partner_ids = set()
|
||||
attachment_ids = set()
|
||||
# 1. Aggregate partners (author_id and partner_ids), attachments and tracking values
|
||||
partners = self.env['res.partner']
|
||||
attachments = self.env['ir.attachment']
|
||||
trackings = self.env['mail.tracking.value']
|
||||
for key, message in message_tree.iteritems():
|
||||
if message.author_id:
|
||||
partner_ids |= set([message.author_id.id])
|
||||
partners |= message.author_id
|
||||
if message.subtype_id and message.notified_partner_ids: # take notified people of message with a subtype
|
||||
partner_ids |= set([partner.id for partner in message.notified_partner_ids])
|
||||
partners |= message.notified_partner_ids
|
||||
elif not message.subtype_id and message.partner_ids: # take specified people of message without a subtype (log)
|
||||
partner_ids |= set([partner.id for partner in message.partner_ids])
|
||||
partners |= message.partner_ids
|
||||
if message.attachment_ids:
|
||||
attachment_ids |= set([attachment.id for attachment in message.attachment_ids])
|
||||
attachments |= message.attachment_ids
|
||||
if message.tracking_value_ids:
|
||||
trackings |= message.tracking_value_ids
|
||||
# Read partners as SUPERUSER -> display the names like classic m2o even if no access
|
||||
partners = self.env['res.partner'].sudo().browse(partner_ids).name_get()
|
||||
partner_tree = dict((partner[0], partner) for partner in partners)
|
||||
partners_names = partners.sudo().name_get()
|
||||
partner_tree = dict((partner[0], partner) for partner in partners_names)
|
||||
|
||||
# 2. Attachments as SUPERUSER, because could receive msg and attachments for doc uid cannot see
|
||||
attachments = self.env['ir.attachment'].sudo().browse(attachment_ids).read(['id', 'datas_fname', 'name', 'file_type_icon'])
|
||||
attachments_data = attachments.sudo().read(['id', 'datas_fname', 'name', 'file_type_icon'])
|
||||
attachments_tree = dict((attachment['id'], {
|
||||
'id': attachment['id'],
|
||||
'filename': attachment['datas_fname'],
|
||||
'name': attachment['name'],
|
||||
'file_type_icon': attachment['file_type_icon'],
|
||||
}) for attachment in attachments)
|
||||
}) for attachment in attachments_data)
|
||||
|
||||
# 3. Update message dictionaries
|
||||
# 3. Tracking values
|
||||
tracking_tree = dict((tracking.id, {
|
||||
'id': tracking.id,
|
||||
'changed_field': tracking.field_desc,
|
||||
'old_value': tracking.get_old_display_value()[0],
|
||||
'new_value': tracking.get_new_display_value()[0],
|
||||
}) for tracking in trackings)
|
||||
|
||||
# 4. Update message dictionaries
|
||||
for message_dict in messages:
|
||||
message_id = message_dict.get('id')
|
||||
message = message_tree[message_id]
|
||||
@@ -280,13 +292,20 @@ class Message(models.Model):
|
||||
for attachment in message.attachment_ids:
|
||||
if attachment.id in attachments_tree:
|
||||
attachment_ids.append(attachments_tree[attachment.id])
|
||||
tracking_value_ids = []
|
||||
for tracking_value in message.tracking_value_ids:
|
||||
if tracking_value.id in tracking_tree:
|
||||
tracking_value_ids.append(tracking_tree[tracking_value.id])
|
||||
|
||||
message_dict.update({
|
||||
'is_author': pid == author[0],
|
||||
'author_id': author,
|
||||
'partner_ids': partner_ids,
|
||||
'attachment_ids': attachment_ids,
|
||||
'tracking_value_ids': tracking_value_ids,
|
||||
'user_pid': pid
|
||||
})
|
||||
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
@@ -306,22 +325,10 @@ class Message(models.Model):
|
||||
vote_nb = len(self.vote_user_ids)
|
||||
has_voted = self._uid in [user.id for user in self.vote_user_ids]
|
||||
|
||||
try:
|
||||
if parent_id:
|
||||
max_length = 300
|
||||
else:
|
||||
max_length = 100
|
||||
body_short = tools.html_email_clean(self.body, remove=False, shorten=True, max_length=max_length)
|
||||
|
||||
except Exception:
|
||||
body_short = '<p><b>Encoding Error : </b><br/>Unable to convert this message (id: %s).</p>' % self.id
|
||||
_logger.exception(Exception)
|
||||
|
||||
return {'id': self.id,
|
||||
'type': self.message_type,
|
||||
'message_type': self.message_type,
|
||||
'subtype': self.subtype_id.name if self.subtype_id else False,
|
||||
'body': self.body,
|
||||
'body_short': body_short,
|
||||
'model': self.model,
|
||||
'res_id': self.res_id,
|
||||
'record_name': self.record_name,
|
||||
@@ -338,108 +345,17 @@ class Message(models.Model):
|
||||
'has_voted': has_voted,
|
||||
'is_favorite': self.starred,
|
||||
'attachment_ids': [],
|
||||
'tracking_value_ids': [],
|
||||
}
|
||||
|
||||
@api.model
|
||||
def _message_read_add_expandables(self, messages, message_tree, parent_tree,
|
||||
message_unload_ids=[], thread_level=0, domain=[], parent_id=False):
|
||||
""" Create expandables for message_read, to load new messages.
|
||||
1. get the expandable for new threads
|
||||
if display is flat (thread_level == 0):
|
||||
fetch message_ids < min(already displayed ids), because we
|
||||
want a flat display, ordered by id
|
||||
else:
|
||||
fetch message_ids that are not childs of already displayed
|
||||
messages
|
||||
2. get the expandables for new messages inside threads if display
|
||||
is not flat
|
||||
for each thread header, search for its childs
|
||||
for each hole in the child list based on message displayed,
|
||||
create an expandable
|
||||
|
||||
:param list messages: list of message structure for the Chatter
|
||||
widget to which expandables are added
|
||||
:param dict message_tree: dict [id]: browse record of this message
|
||||
:param dict parent_tree: dict [parent_id]: [child_ids]
|
||||
:param list message_unload_ids: list of message_ids we do not want
|
||||
to load
|
||||
:return bool: True
|
||||
"""
|
||||
def _get_expandable(domain, message_nb, parent_id, max_limit):
|
||||
return {
|
||||
'domain': domain,
|
||||
'nb_messages': message_nb,
|
||||
'type': 'expandable',
|
||||
'parent_id': parent_id,
|
||||
'max_limit': max_limit,
|
||||
}
|
||||
|
||||
if not messages:
|
||||
return True
|
||||
message_ids = sorted(message_tree.keys())
|
||||
|
||||
# 1. get the expandable for new threads
|
||||
if thread_level == 0:
|
||||
exp_domain = domain + [('id', '<', min(message_unload_ids + message_ids))]
|
||||
else:
|
||||
exp_domain = domain + ['!', ('id', 'child_of', message_unload_ids + parent_tree.keys())]
|
||||
more_count = self.search_count(exp_domain)
|
||||
if more_count:
|
||||
# inside a thread: prepend
|
||||
if parent_id:
|
||||
messages.insert(0, _get_expandable(exp_domain, -1, parent_id, True))
|
||||
# new threads: append
|
||||
else:
|
||||
messages.append(_get_expandable(exp_domain, -1, parent_id, True))
|
||||
|
||||
# 2. get the expandables for new messages inside threads if display is not flat
|
||||
if thread_level == 0:
|
||||
return True
|
||||
for message_id in message_ids:
|
||||
message = message_tree[message_id]
|
||||
|
||||
# generate only for thread header messages (TDE note: parent_id may be False is uid cannot see parent_id, seems ok)
|
||||
if message.parent_id:
|
||||
continue
|
||||
|
||||
# check there are message for expandable
|
||||
child_ids = set([child.id for child in message.child_ids]) - set(message_unload_ids)
|
||||
child_ids = sorted(list(child_ids), reverse=True)
|
||||
if not child_ids:
|
||||
continue
|
||||
|
||||
# make groups of unread messages
|
||||
id_min, id_max, nb = max(child_ids), 0, 0
|
||||
for child_id in child_ids:
|
||||
if not child_id in message_ids:
|
||||
nb += 1
|
||||
if id_min > child_id:
|
||||
id_min = child_id
|
||||
if id_max < child_id:
|
||||
id_max = child_id
|
||||
elif nb > 0:
|
||||
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
|
||||
idx = [msg.get('id') for msg in messages].index(child_id) + 1
|
||||
# messages.append(_get_expandable(exp_domain, nb, message_id, False))
|
||||
messages.insert(idx, _get_expandable(exp_domain, nb, message_id, False))
|
||||
id_min, id_max, nb = max(child_ids), 0, 0
|
||||
else:
|
||||
id_min, id_max, nb = max(child_ids), 0, 0
|
||||
if nb > 0:
|
||||
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
|
||||
idx = [msg.get('id') for msg in messages].index(message_id) + 1
|
||||
# messages.append(_get_expandable(exp_domain, nb, message_id, id_min))
|
||||
messages.insert(idx, _get_expandable(exp_domain, nb, message_id, False))
|
||||
|
||||
return True
|
||||
|
||||
@api.cr_uid_context
|
||||
def message_read_wrapper(self, cr, uid, ids=None, domain=None, message_unload_ids=None,
|
||||
thread_level=0, context=None, parent_id=False, limit=None):
|
||||
return self.message_read(cr, uid, ids, domain=domain, message_unload_ids=message_unload_ids, thread_level=thread_level, parent_id=parent_id, limit=limit)
|
||||
def message_read_wrapper(self, cr, uid, ids=None, domain=None, context=None,
|
||||
thread_level=0, parent_id=False, limit=None, child_limit=None):
|
||||
return self.message_read(cr, uid, ids, domain=domain, thread_level=thread_level, context=context,
|
||||
parent_id=parent_id, limit=limit, child_limit=child_limit)
|
||||
|
||||
@api.multi
|
||||
def message_read(self, domain=None, message_unload_ids=None, thread_level=0, parent_id=False, limit=None):
|
||||
def message_read(self, domain=None, thread_level=0, context= None, parent_id=False, limit=None, child_limit=None):
|
||||
""" Read messages from mail.message, and get back a list of structured
|
||||
messages to be displayed as discussion threads. If IDs is set,
|
||||
fetch these records. Otherwise use the domain to fetch messages.
|
||||
@@ -447,31 +363,33 @@ class Message(models.Model):
|
||||
well formed threads, if uid has access to them.
|
||||
|
||||
After reading the messages, expandable messages are added in the
|
||||
message list (see ``_message_read_add_expandables``). It consists
|
||||
in messages holding the 'read more' data: number of messages to
|
||||
read, domain to apply.
|
||||
message list. It consists in messages holding the 'read more' data:
|
||||
number of messages to read, domain to apply.
|
||||
|
||||
:param list ids: optional IDs to fetch
|
||||
:param list domain: optional domain for searching ids if ids not set
|
||||
:param list message_unload_ids: optional ids we do not want to fetch,
|
||||
because i.e. they are already displayed somewhere
|
||||
:param int parent_id: context of parent_id
|
||||
- if parent_id reached when adding ancestors, stop going further
|
||||
in the ancestor search
|
||||
- if set in flat mode, ancestor_id is set to parent_id
|
||||
:param int limit: number of messages to fetch, before adding the
|
||||
ancestors and expandables
|
||||
:return list: list of message structure for the Chatter widget
|
||||
:param int child_limit: number of child messages to fetch
|
||||
:return dict:
|
||||
- int: number of messages read (status 'unread' to 'read')
|
||||
- list: list of threads [[messages_of_thread1], [messages_of_thread2]]
|
||||
"""
|
||||
assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level
|
||||
|
||||
domain = domain if domain is not None else []
|
||||
message_unload_ids = message_unload_ids if message_unload_ids is not None else []
|
||||
if message_unload_ids:
|
||||
domain += [('id', 'not in', message_unload_ids)]
|
||||
limit = limit or self._message_read_limit
|
||||
child_limit = child_limit or self._message_read_limit
|
||||
|
||||
message_tree = {}
|
||||
message_list = []
|
||||
parent_tree = {}
|
||||
child_ids = []
|
||||
parent_ids = []
|
||||
exp_domain = []
|
||||
|
||||
# no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
|
||||
if not self.ids and domain:
|
||||
@@ -497,24 +415,73 @@ class Message(models.Model):
|
||||
message_tree[parent.id] = parent
|
||||
# newest messages first
|
||||
parent_tree.setdefault(tree_parent_id, [])
|
||||
if tree_parent_id != message_id:
|
||||
parent_tree[tree_parent_id].append(message_tree[message_id]._message_read_dict(parent_id=tree_parent_id))
|
||||
|
||||
if thread_level:
|
||||
for key, message_id_list in parent_tree.iteritems():
|
||||
message_id_list.sort(key=lambda item: item['id'])
|
||||
message_id_list.insert(0, message_tree[key]._message_read_dict())
|
||||
# build thread structure
|
||||
# for each parent_id: get child messages, add message expandable and parent message if needed [child1, child2, expandable, parent_message]
|
||||
# add thread expandable if it remains some uncaught parent_id
|
||||
if self.ids and len(self.ids) > 0:
|
||||
for parent in parent_tree:
|
||||
parent_ids.append(parent)
|
||||
|
||||
# create final ordered message_list based on parent_tree
|
||||
parent_list = parent_tree.items()
|
||||
parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item[1]]) if item[1] else item[0], reverse=True)
|
||||
message_list = [message for (key, msg_list) in parent_list for message in msg_list]
|
||||
if not thread_level:
|
||||
child_ids = self.ids;
|
||||
exp_domain = domain + [('id', '<', min(child_ids))]
|
||||
else:
|
||||
child_ids = [msg.id for msg in self.browse(parent).child_ids][0:child_limit]
|
||||
exp_domain = [('parent_id', '=', parent), ('id', '>', parent)]
|
||||
if len(child_ids):
|
||||
exp_domain += [('id', '<', min(child_ids))]
|
||||
|
||||
# get the child expandable messages for the tree
|
||||
self._message_read_dict_postprocess(message_list, message_tree)
|
||||
self._message_read_add_expandables(message_list, message_tree, parent_tree,
|
||||
thread_level=thread_level, message_unload_ids=message_unload_ids, domain=domain, parent_id=parent_id)
|
||||
return message_list
|
||||
for cid in child_ids:
|
||||
if cid not in message_tree:
|
||||
message_tree[cid] = self.browse(cid)
|
||||
parent_tree[parent].append(message_tree[cid]._message_read_dict(parent_id=parent))
|
||||
|
||||
if parent and thread_level:
|
||||
parent_tree[parent].sort(key=lambda item: item['id'])
|
||||
parent_tree[parent].reverse();
|
||||
parent_tree[parent].append(message_tree[parent]._message_read_dict())
|
||||
|
||||
self._message_read_dict_postprocess(parent_tree[parent], message_tree)
|
||||
|
||||
# add 'message' expandable (inside a thread)
|
||||
more_count = self.search_count(exp_domain)
|
||||
if more_count:
|
||||
exp = {'message_type':'expandable',
|
||||
'domain': exp_domain,
|
||||
'nb_messages': more_count,
|
||||
'parent_id': parent}
|
||||
|
||||
if parent and thread_level:
|
||||
#insert expandable before parent message
|
||||
parent_tree[parent].insert(len(parent_tree[parent])-1, exp)
|
||||
else:
|
||||
#insert expandable at the end of the message list
|
||||
parent_tree[parent].append(exp)
|
||||
|
||||
# create final ordered parent_list based on parent_tree
|
||||
parent_list = parent_tree.values()
|
||||
parent_list = sorted(parent_list, key=lambda item: max([msg.get('id') for msg in item]), reverse=True)
|
||||
|
||||
#add 'thread' expandable
|
||||
if thread_level:
|
||||
exp_domain = domain + [('id', '<', min(self.ids)), ('id', 'not in', parent_ids), ('parent_id', 'not in', parent_ids)]
|
||||
more_count = self.search_count(exp_domain)
|
||||
if more_count:
|
||||
parent_list.append([{'message_type':'expandable',
|
||||
'domain': exp_domain,
|
||||
'nb_messages': more_count,
|
||||
'parent_id': parent_id}])
|
||||
|
||||
nb_read = 0
|
||||
if context and 'mail_read_set_read' in context and context['mail_read_set_read']:
|
||||
nb_read = self.set_message_read(True, create_missing=False)
|
||||
|
||||
else:
|
||||
nb_read = 0
|
||||
parent_list = []
|
||||
|
||||
return {'nb_read': nb_read, 'threads': parent_list}
|
||||
|
||||
@api.multi
|
||||
def get_like_names(self, limit=10):
|
||||
|
||||
@@ -426,57 +426,27 @@ class MailThread(models.AbstractModel):
|
||||
|
||||
@api.multi
|
||||
def message_track(self, tracked_fields, initial_values):
|
||||
|
||||
def convert_for_display(value, col_info):
|
||||
if not value and col_info['type'] == 'boolean':
|
||||
return 'False'
|
||||
if not value:
|
||||
return ''
|
||||
if col_info['type'] == 'many2one':
|
||||
return value.name_get()[0][1]
|
||||
if col_info['type'] == 'selection':
|
||||
return dict(col_info['selection'])[value]
|
||||
return value
|
||||
|
||||
def format_message(message_description, tracked_values):
|
||||
message = ''
|
||||
if message_description:
|
||||
message = '<span>%s</span>' % message_description
|
||||
for name, change in tracked_values.items():
|
||||
message += '<div> • <b>%s</b>: ' % change.get('col_info')
|
||||
if change.get('old_value'):
|
||||
message += '%s → ' % change.get('old_value')
|
||||
message += '%s</div>' % change.get('new_value')
|
||||
return message
|
||||
|
||||
if not tracked_fields:
|
||||
return True
|
||||
|
||||
for record in self:
|
||||
initial = initial_values[record.id]
|
||||
changes = set()
|
||||
tracked_values = {}
|
||||
tracking_value_ids = []
|
||||
|
||||
# generate tracked_values data structure: {'col_name': {col_info, new_value, old_value}}
|
||||
for col_name, col_info in tracked_fields.items():
|
||||
field = self._fields[col_name]
|
||||
initial_value = initial[col_name]
|
||||
record_value = getattr(record, col_name)
|
||||
new_value = getattr(record, col_name)
|
||||
|
||||
if new_value != initial_value and (new_value or initial_value): # because browse null != False
|
||||
tracking = self.env['mail.tracking.value'].create_tracking_values(initial_value, new_value, col_name, col_info)
|
||||
if tracking:
|
||||
tracking_value_ids.append([0, 0, tracking])
|
||||
|
||||
if record_value == initial_value and getattr(field, 'track_visibility', None) == 'always':
|
||||
tracked_values[col_name] = dict(
|
||||
col_info=col_info['string'],
|
||||
new_value=convert_for_display(record_value, col_info),
|
||||
)
|
||||
elif record_value != initial_value and (record_value or initial_value): # because browse null != False
|
||||
if getattr(field, 'track_visibility', None) in ['always', 'onchange']:
|
||||
tracked_values[col_name] = dict(
|
||||
col_info=col_info['string'],
|
||||
old_value=convert_for_display(initial_value, col_info),
|
||||
new_value=convert_for_display(record_value, col_info),
|
||||
)
|
||||
if col_name in tracked_fields:
|
||||
changes.add(col_name)
|
||||
|
||||
if not changes:
|
||||
continue
|
||||
|
||||
@@ -500,10 +470,10 @@ class MailThread(models.AbstractModel):
|
||||
if not (subtype_rec and subtype_rec.exists()):
|
||||
_logger.debug('subtype %s not found' % subtype_xmlid)
|
||||
continue
|
||||
message = format_message(subtype_rec.description if subtype_rec.description else subtype_rec.name, tracked_values)
|
||||
record.message_post(subtype=subtype_xmlid, tracking_value_ids=tracking_value_ids)
|
||||
else:
|
||||
message = format_message('', tracked_values)
|
||||
record.message_post(body=message, subtype=subtype_xmlid)
|
||||
record.message_post(tracking_value_ids=tracking_value_ids)
|
||||
|
||||
return True
|
||||
|
||||
#------------------------------------------------------
|
||||
@@ -1509,7 +1479,6 @@ class MailThread(models.AbstractModel):
|
||||
content_subtype='html', **kwargs):
|
||||
""" Post a new message in an existing thread, returning the new
|
||||
mail.message ID.
|
||||
|
||||
:param int thread_id: thread ID to post into, or list with one ID;
|
||||
if False/0, mail.message model will also be set as False
|
||||
:param str body: body of the message, usually raw HTML that will
|
||||
@@ -1520,7 +1489,6 @@ class MailThread(models.AbstractModel):
|
||||
parent partners to the message in case of private discussion
|
||||
:param tuple(str,str) attachments or list id: list of attachment tuples in the form
|
||||
``(name,content)``, where content is NOT base64 encoded
|
||||
|
||||
Extra keyword arguments will be used as default column values for the
|
||||
new mail.message record. Special cases:
|
||||
- attachment_ids: supposed not attached to any document; attach them
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
from datetime import datetime
|
||||
|
||||
from openerp import api, fields, models, tools
|
||||
|
||||
|
||||
class MailTracking(models.Model):
|
||||
_name = 'mail.tracking.value'
|
||||
_description = 'Mail Tracking Value'
|
||||
|
||||
field = fields.Char('Changed Field', required=True, readonly=1)
|
||||
field_desc = fields.Char('Field Description', required=True, readonly=1)
|
||||
field_type = fields.Char('Field Type')
|
||||
|
||||
old_value_integer = fields.Integer('Old Value Integer', readonly=1)
|
||||
old_value_float = fields.Float('Old Value Float', readonly=1)
|
||||
old_value_char = fields.Char('Old Value Char', readonly=1)
|
||||
old_value_text = fields.Text('Old Value Text', readonly=1)
|
||||
old_value_datetime = fields.Datetime('Old Value DateTime', readonly=1)
|
||||
|
||||
new_value_integer = fields.Integer('New Value Integer', readonly=1)
|
||||
new_value_float = fields.Float('New Value Float', readonly=1)
|
||||
new_value_char = fields.Char('New Value Char', readonly=1)
|
||||
new_value_text = fields.Text('New Value Text', readonly=1)
|
||||
new_value_datetime = fields.Datetime('New Value Datetime', readonly=1)
|
||||
|
||||
mail_message_id = fields.Many2one('mail.message', 'Message ID', required=True, select=True, ondelete='cascade')
|
||||
|
||||
@api.model
|
||||
def create_tracking_values(self, initial_value, new_value, col_name, col_info):
|
||||
tracked = True
|
||||
values = {'field': col_name, 'field_desc': col_info['string'], 'field_type': col_info['type']}
|
||||
|
||||
if col_info['type'] in ['integer', 'float', 'char', 'text', 'datetime']:
|
||||
values.update({
|
||||
'old_value_%s' % col_info['type']: initial_value,
|
||||
'new_value_%s' % col_info['type']: new_value
|
||||
})
|
||||
elif col_info['type'] == 'date':
|
||||
values.update({
|
||||
'old_value_datetime': initial_value and datetime.strftime(datetime.combine(datetime.strptime(initial_value, tools.DEFAULT_SERVER_DATE_FORMAT), datetime.min.time()), tools.DEFAULT_SERVER_DATETIME_FORMAT) or False,
|
||||
'new_value_datetime': new_value and datetime.strftime(datetime.combine(datetime.strptime(new_value, tools.DEFAULT_SERVER_DATE_FORMAT), datetime.min.time()), tools.DEFAULT_SERVER_DATETIME_FORMAT) or False,
|
||||
})
|
||||
elif col_info['type'] == 'boolean':
|
||||
values.update({
|
||||
'old_value_integer': initial_value,
|
||||
'new_value_integer': new_value
|
||||
})
|
||||
elif col_info['type'] == 'selection':
|
||||
values.update({
|
||||
'old_value_char': initial_value and dict(col_info['selection'])[initial_value] or '',
|
||||
'new_value_char': new_value and dict(col_info['selection'])[new_value] or ''
|
||||
})
|
||||
elif col_info['type'] == 'many2one':
|
||||
values.update({
|
||||
'old_value_integer': initial_value and initial_value.id or 0,
|
||||
'new_value_integer': new_value and new_value.id or 0,
|
||||
'old_value_char': initial_value and initial_value.name_get()[0][1] or '',
|
||||
'new_value_char': new_value and new_value.name_get()[0][1] or ''
|
||||
})
|
||||
else:
|
||||
tracked = False
|
||||
|
||||
if tracked:
|
||||
return values
|
||||
return {}
|
||||
|
||||
@api.multi
|
||||
def get_old_display_value(self):
|
||||
result = []
|
||||
for record in self:
|
||||
if record.field_type in ['integer', 'float', 'char', 'text', 'datetime']:
|
||||
result.append(getattr(record, 'old_value_%s' % record.field_type))
|
||||
elif record.field_type == 'date':
|
||||
if record.old_value_datetime:
|
||||
result.append(datetime.strptime(record.old_value_integer, tools.DEFAULT_SERVER_DATETIME_FORMAT).date())
|
||||
else:
|
||||
result.append(record.old_value_datetime)
|
||||
elif record.field_type == 'boolean':
|
||||
result.append(bool(record.old_value_integer))
|
||||
elif record.field_type in ['many2one', 'selection']:
|
||||
result.append(record.old_value_char)
|
||||
else:
|
||||
result.append(record.old_value_char)
|
||||
return result
|
||||
|
||||
@api.multi
|
||||
def get_new_display_value(self):
|
||||
result = []
|
||||
for record in self:
|
||||
if record.field_type in ['integer', 'float', 'char', 'text', 'datetime']:
|
||||
result.append(getattr(record, 'new_value_%s' % record.field_type))
|
||||
elif record.field_type == 'date':
|
||||
if record.new_value_datetime:
|
||||
result.append(datetime.strptime(record.old_value_integer, tools.DEFAULT_SERVER_DATETIME_FORMAT).date())
|
||||
else:
|
||||
result.append(record.new_value_datetime)
|
||||
elif record.field_type == 'boolean':
|
||||
result.append(bool(record.new_value_integer))
|
||||
elif record.field_type in ['many2one', 'selection']:
|
||||
result.append(record.new_value_char)
|
||||
else:
|
||||
result.append(record.new_value_char)
|
||||
return result
|
||||
@@ -21,6 +21,10 @@ access_mail_alias_user,mail.alias.user,model_mail_alias,base.group_user,1,1,1,0
|
||||
access_mail_alias_system,mail.alias.system,model_mail_alias,base.group_system,1,1,1,1
|
||||
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
|
||||
access_mail_message_subtype_system,mail.message.subtype.system,model_mail_message_subtype,base.group_system,1,1,1,1
|
||||
access_mail_tracking_value_all,mail.tracking.value.all,model_mail_tracking_value,,1,0,0,0
|
||||
access_mail_tracking_value_portal,mail.tracking.value.portal,model_mail_tracking_value,base.group_portal,1,1,1,1
|
||||
access_mail_tracking_value_user,mail.tracking.value.user,model_mail_tracking_value,base.group_user,1,1,1,1
|
||||
access_mail_tracking_value_system,mail.tracking.value.system,model_mail_tracking_value,base.group_system,1,1,1,1
|
||||
access_mail_thread_all,mail.thread.all,model_mail_thread,,1,1,1,1
|
||||
access_publisher_warranty_contract_all,publisher.warranty.contract.all,model_publisher_warranty_contract,,1,1,1,1
|
||||
access_mail_template,mail.template,model_mail_template,base.group_user,1,1,1,0
|
||||
|
||||
|
+1098
-747
File diff suppressed because it is too large
Load Diff
+1293
-1749
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ var qweb = core.qweb;
|
||||
|
||||
/**
|
||||
* ------------------------------------------------------------
|
||||
* mail_followers Widget
|
||||
* timeline_followers Widget
|
||||
* ------------------------------------------------------------
|
||||
*
|
||||
* This widget handles the display of a list of records as a vertical
|
||||
@@ -26,19 +26,19 @@ var qweb = core.qweb;
|
||||
|
||||
|
||||
var Followers = form_common.AbstractField.extend({
|
||||
template: 'mail.followers',
|
||||
template: 'mail_followers',
|
||||
|
||||
init: function() {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.image = this.node.attrs.image || 'image_small';
|
||||
this.comment = this.node.attrs.help || false;
|
||||
this.displayed_limit = this.node.attrs.displayed_nb || 10;
|
||||
this.displayed_nb = this.displayed_limit;
|
||||
this.ds_model = new data.DataSetSearch(this, this.view.model);
|
||||
this.ds_users = new data.DataSetSearch(this, 'res.users');
|
||||
|
||||
this.value = [];
|
||||
this.followers = [];
|
||||
this.data_subtype = {};
|
||||
|
||||
this.view_is_editable = this.__parentedParent.is_action_enabled('edit');
|
||||
},
|
||||
@@ -49,7 +49,7 @@ var Followers = form_common.AbstractField.extend({
|
||||
this.on_check_visibility_mode();
|
||||
this.reinit();
|
||||
this.bind_events();
|
||||
this._super();
|
||||
return this._super();
|
||||
},
|
||||
|
||||
on_check_visibility_mode: function () {
|
||||
@@ -62,37 +62,43 @@ var Followers = form_common.AbstractField.extend({
|
||||
},
|
||||
|
||||
reinit: function() {
|
||||
this.message_is_follower = false;
|
||||
this.data_subtype = {};
|
||||
this.message_is_follower == undefined;
|
||||
this.display_buttons();
|
||||
},
|
||||
|
||||
bind_events: function() {
|
||||
var self = this;
|
||||
|
||||
// event: click on '(Un)Follow' button, that toggles the follow for uid
|
||||
this.$('.oe_follower').on('click', function (event) {
|
||||
if($(this).hasClass('oe_notfollow'))
|
||||
this.$('.o_timeline_follower').on('click', function (event) {
|
||||
if($(this).hasClass('o_timeline_notfollow')) {
|
||||
self.do_follow();
|
||||
else
|
||||
}
|
||||
else {
|
||||
self.do_unfollow();
|
||||
}
|
||||
});
|
||||
|
||||
// event: click on a subtype, that (un)subscribe for this subtype
|
||||
this.$el.on('click', '.oe_subtype_list input', function(event) {
|
||||
this.$el.on('click', '.o_timeline_subtype_list input', function(event) {
|
||||
self.do_update_subscription(event);
|
||||
var $list = self.$('.oe_subtype_list');
|
||||
var $list = self.$('.o_timeline_subtype_list');
|
||||
if(!$list.hasClass('open')) {
|
||||
$list.addClass('open');
|
||||
}
|
||||
if(self.$('.oe_subtype_list ul')[0].children.length < 1) {
|
||||
if(self.$('.o_timeline_subtype_list ul')[0].children.length < 1) {
|
||||
$list.removeClass('open');
|
||||
}
|
||||
event.stopPropagation();
|
||||
});
|
||||
|
||||
// event: click on 'invite' button, that opens the invite wizard
|
||||
this.$('.oe_invite').on('click', self.on_invite_follower);
|
||||
this.$el.on('click', '.oe_invite', self.on_invite_follower);
|
||||
|
||||
// event: click on 'edit_subtype(pencil)' button to edit subscription
|
||||
this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
|
||||
this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
|
||||
this.$el.on('click', '.oe_show_more', self.on_show_more_followers);
|
||||
this.$el.on('click', 'a[data-partner]', self.on_follower_clicked);
|
||||
},
|
||||
|
||||
@@ -137,19 +143,16 @@ var Followers = form_common.AbstractField.extend({
|
||||
});
|
||||
},
|
||||
|
||||
on_show_more_followers: function (event) {
|
||||
this.displayed_nb += this.displayed_limit;
|
||||
this.display_followers(false);
|
||||
},
|
||||
|
||||
on_remove_follower: function (event) {
|
||||
var partner_id = $(event.target).data('id');
|
||||
var name = $(event.target).parent().find("a").html();
|
||||
if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
|
||||
var context = new data.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id], [partner_id], context])
|
||||
.then(this.proxy('read_value'));
|
||||
}
|
||||
var name = $(event.target).parent().find("a").html();
|
||||
if (confirm(_.str.sprintf(_t("Warning! \n %s won't be notified of any email or discussion on this document. Do you really want to remove him from the followers ?"), name))) {
|
||||
var context = new data.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call('message_unsubscribe', [[this.view.datarecord.id],
|
||||
[partner_id],
|
||||
context])
|
||||
.then(this.proxy('read_value'));
|
||||
}
|
||||
},
|
||||
|
||||
on_follower_clicked: function (event) {
|
||||
@@ -174,11 +177,11 @@ var Followers = form_common.AbstractField.extend({
|
||||
|
||||
read_value: function () {
|
||||
var self = this;
|
||||
this.displayed_nb = this.displayed_limit;
|
||||
return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids']).then(function (results) {
|
||||
self.value = results[0].message_follower_ids;
|
||||
self.render_value();
|
||||
});
|
||||
return this.ds_model.read_ids([this.view.datarecord.id], ['message_follower_ids'])
|
||||
.then(function (results) {
|
||||
self.value = results[0].message_follower_ids;
|
||||
self.render_value();
|
||||
});
|
||||
},
|
||||
|
||||
render_value: function () {
|
||||
@@ -200,10 +203,13 @@ var Followers = form_common.AbstractField.extend({
|
||||
fetch_generic: function (error, event) {
|
||||
var self = this;
|
||||
event.preventDefault();
|
||||
return this.ds_users.call('read', [[session.uid], ['partner_id']]).then(function (results) {
|
||||
var pid = results[0]['partner_id'][0];
|
||||
self.message_is_follower = (_.indexOf(self.value, pid) != -1);
|
||||
}).then(self.proxy('display_generic'));
|
||||
|
||||
return this.ds_users.call('read', [[session.uid], ['partner_id']])
|
||||
.then(function (results) {
|
||||
var pid = results[0]['partner_id'][0];
|
||||
self.message_is_follower = (_.indexOf(self.value, pid) != -1);
|
||||
})
|
||||
.then(self.proxy('display_generic'));
|
||||
},
|
||||
|
||||
_format_followers: function(count){
|
||||
@@ -220,8 +226,8 @@ var Followers = form_common.AbstractField.extend({
|
||||
|
||||
/* Display generic info about follower, for people not having access to res_partner */
|
||||
display_generic: function () {
|
||||
this.$('.oe_follower_list').empty();
|
||||
this.$('.oe_follower_title').html(this._format_followers(this.value.length));
|
||||
var node_user_list = this.$('.o_timeline_follower_list').empty();
|
||||
this.$('.o_timeline_follower_title').html(this._format_followers(this.value.length));
|
||||
},
|
||||
|
||||
/** Display the followers */
|
||||
@@ -229,140 +235,169 @@ var Followers = form_common.AbstractField.extend({
|
||||
var self = this;
|
||||
this.message_is_follower = false;
|
||||
this.followers = records || this.followers;
|
||||
|
||||
// clean and display title
|
||||
var node_user_list = this.$('.oe_follower_list').empty();
|
||||
this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
|
||||
self.message_is_follower = _.indexOf(this.followers.map(function (rec) { return rec[2]['is_uid'];}), true) != -1;
|
||||
var node_user_list = this.$('.o_timeline_follower_list').empty();
|
||||
this.$('.o_timeline_follower_title').html(this._format_followers(this.followers.length));
|
||||
|
||||
$(qweb.render('mail_followers_add_more', {'widget': self})).appendTo(node_user_list);
|
||||
|
||||
self.message_is_follower = _.indexOf(this.followers.map(function(rec) {return rec[2]['is_uid']}), true) != -1;
|
||||
// truncate number of displayed followers
|
||||
var truncated = this.followers.slice(0, this.displayed_nb);
|
||||
_(truncated).each(function (record) {
|
||||
_(this.followers).each(function (record) {
|
||||
var partner = {
|
||||
'id': record[0],
|
||||
'name': record[1],
|
||||
'is_uid': record[2]['is_uid'],
|
||||
'is_editable': record[2]['is_editable'],
|
||||
'avatar_url': mail_utils.get_image(session, 'res.partner', 'image_small', record[0]),
|
||||
};
|
||||
$(qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
|
||||
}
|
||||
|
||||
$(qweb.render('mail_followers_partner', {'record': partner, 'widget': self}))
|
||||
.appendTo(node_user_list);
|
||||
|
||||
// On mouse-enter it will show the edit_subtype pencil.
|
||||
if (partner.is_editable) {
|
||||
self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
|
||||
self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
|
||||
self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
|
||||
self.$('.o_timeline_follower_list').on('mouseenter mouseleave', function(e) {
|
||||
self.$('.o_timeline_edit_subtype')
|
||||
.toggleClass('oe_hidden', e.type == 'mouseleave');
|
||||
self.$('.o_timeline_follower_list').find('.oe_partner')
|
||||
.toggleClass('oe_partner_name', e.type == 'mouseenter');
|
||||
});
|
||||
}
|
||||
});
|
||||
// FVA note: be sure it is correctly translated
|
||||
if (truncated.length < this.followers.length) {
|
||||
$(qweb.render('mail.followers.show_more', {'number': (this.followers.length - truncated.length)} )).appendTo(node_user_list);
|
||||
}
|
||||
},
|
||||
|
||||
display_buttons: function () {
|
||||
if (this.message_is_follower) {
|
||||
this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
|
||||
this.$('button.o_timeline_follower').removeClass('o_timeline_notfollow').addClass('o_timeline_following');
|
||||
}
|
||||
else {
|
||||
this.$('button.oe_follower').removeClass('oe_following').addClass('oe_notfollow');
|
||||
this.$('button.o_timeline_follower').removeClass('o_timeline_following').addClass('o_timeline_notfollow');
|
||||
}
|
||||
|
||||
if (this.view.is_action_enabled('edit'))
|
||||
this.$('span.oe_mail_invite_wrapper').hide();
|
||||
else
|
||||
this.$('span.oe_mail_invite_wrapper').show();
|
||||
if (this.view.is_action_enabled('edit')) {
|
||||
this.$('span.oe_timeline_invite_wrapper').hide();
|
||||
}
|
||||
else {
|
||||
this.$('span.oe_timeline_invite_wrapper').show();
|
||||
}
|
||||
},
|
||||
|
||||
/** Fetch subtypes, only if current user is follower */
|
||||
fetch_subtypes: function (user_pid) {
|
||||
var self = this;
|
||||
var dialog = false;
|
||||
|
||||
if (user_pid) {
|
||||
dialog = true;
|
||||
} else {
|
||||
this.$('.oe_subtype_list ul').empty();
|
||||
}
|
||||
else {
|
||||
var subtype_list_ul = this.$('.o_timeline_subtype_list ul').empty();
|
||||
if (! this.message_is_follower) {
|
||||
this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', true);
|
||||
this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', true);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', false);
|
||||
this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', false);
|
||||
}
|
||||
}
|
||||
var id = this.view.datarecord.id;
|
||||
this.ds_model.call('message_get_subscription_data', [[id], user_pid, new data.CompoundContext(this.build_context(), {})])
|
||||
this.ds_model.call('message_get_subscription_data',
|
||||
[[id], user_pid, new data.CompoundContext(this.build_context(), {})])
|
||||
.then(function (data) {self.display_subtypes(data, id, dialog);});
|
||||
},
|
||||
|
||||
/** Display subtypes: {'name': default, followed} */
|
||||
display_subtypes:function (data, id, dialog) {
|
||||
var self = this;
|
||||
var $list;
|
||||
if (dialog) {
|
||||
$list = self.$dialog.$el;
|
||||
}
|
||||
else {
|
||||
$list = this.$('.oe_subtype_list ul');
|
||||
}
|
||||
var records = data[id].message_subtype_data;
|
||||
this.records_length = $.map(records, function(value, index) { return index; }).length;
|
||||
if (this.records_length > 1) { self.display_followers(); }
|
||||
var old_model = '';
|
||||
_(records).each(function (record, record_name) {
|
||||
if (old_model != record.parent_model){
|
||||
if (old_model !== ''){
|
||||
var index = $($list).find('.oe_subtype').length;
|
||||
$($($list).find('.oe_subtype')[index-1]).addClass('subtype-border');
|
||||
}
|
||||
old_model = record.parent_model;
|
||||
var $list = this.$('.o_timeline_subtype_list ul');
|
||||
|
||||
if (_.isEmpty(this.data_subtype)) {
|
||||
this.data_subtype = data;
|
||||
|
||||
if (dialog) {
|
||||
$list = this.$dialog.$el;
|
||||
}
|
||||
record.name = record_name;
|
||||
record.followed = record.followed || undefined;
|
||||
$(qweb.render('mail.followers.subtype', {'record': record, 'dialog': dialog})).appendTo($list);
|
||||
});
|
||||
|
||||
var old_model = '';
|
||||
var records = data[id].message_subtype_data;
|
||||
this.records_length = $.map(records, function(value, index) { return index; }).length;
|
||||
|
||||
if (this.records_length > 1) {
|
||||
self.display_followers();
|
||||
}
|
||||
|
||||
_.each(records, function (record, record_name) {
|
||||
if (old_model != record.parent_model) {
|
||||
if (old_model != '') {
|
||||
var index = $($list).find('.oe_subtype').length;
|
||||
$list.find('.oe_subtype')[index-1].addClass('subtype-border');
|
||||
}
|
||||
old_model = record.parent_model;
|
||||
}
|
||||
|
||||
record.name = record_name;
|
||||
record.followed = record.followed || undefined;
|
||||
$(qweb.render('mail_followers_subtype', {'record': record,
|
||||
'dialog': dialog}))
|
||||
.appendTo($list);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
do_follow: function () {
|
||||
var context = new data.CompoundContext(this.build_context(), {});
|
||||
this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', false);
|
||||
this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [session.uid], undefined, context])
|
||||
this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', false);
|
||||
this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id],
|
||||
[session.uid],
|
||||
undefined,
|
||||
context])
|
||||
.then(this.proxy('read_value'));
|
||||
|
||||
_.each(this.$('.oe_subtype_list input'), function (record) {
|
||||
_.each(this.$('.o_timeline_subtype_list input'), function (record) {
|
||||
$(record).attr('checked', 'checked');
|
||||
});
|
||||
},
|
||||
|
||||
do_unfollow: function (user_pid) {
|
||||
if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
|
||||
_(this.$('.oe_msg_subtype_check')).each(function (record) {
|
||||
_(this.$('.o_timeline_msg_subtype_check')).each(function (record) {
|
||||
$(record).attr('checked',false);
|
||||
});
|
||||
var action_unsubscribe = 'message_unsubscribe_users';
|
||||
this.$('.oe_subtype_list > .dropdown-toggle').attr('disabled', true);
|
||||
var follower_ids = [session.uid];
|
||||
if (user_pid) {
|
||||
action_unsubscribe = 'message_unsubscribe';
|
||||
follower_ids = [user_pid];
|
||||
}
|
||||
|
||||
var action_unsubscribe = 'message_unsubscribe_users';
|
||||
this.$('.o_timeline_subtype_list > .dropdown-toggle').attr('disabled', true);
|
||||
var follower_ids = [session.uid];
|
||||
|
||||
if (user_pid) {
|
||||
action_unsubscribe = 'message_unsubscribe';
|
||||
follower_ids = [user_pid];
|
||||
}
|
||||
|
||||
var context = new data.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
|
||||
.then(this.proxy('read_value'));
|
||||
return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id],
|
||||
follower_ids, context])
|
||||
.then(this.proxy('read_value'));
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
do_update_subscription: function (event, user_pid) {
|
||||
var self = this;
|
||||
this.data_subtype = {};
|
||||
|
||||
var action_subscribe = 'message_subscribe_users';
|
||||
var follower_ids = [session.uid];
|
||||
var oe_action = this.$('.oe_actions input[type="checkbox"]');
|
||||
|
||||
if (user_pid) {
|
||||
action_subscribe = 'message_subscribe';
|
||||
follower_ids = [user_pid];
|
||||
oe_action = $('.oe_edit_actions input[type="checkbox"]');
|
||||
}
|
||||
var checklist = [];
|
||||
|
||||
var checklist = new Array();
|
||||
_(oe_action).each(function (record) {
|
||||
if ($(record).is(':checked')) {
|
||||
checklist.push(parseInt($(record).data('id')));
|
||||
@@ -372,17 +407,23 @@ var Followers = form_common.AbstractField.extend({
|
||||
if (!checklist.length) {
|
||||
if (!this.do_unfollow(user_pid)) {
|
||||
$(event.target).attr("checked", "checked");
|
||||
} else {
|
||||
self.$('.oe_subtype_list ul').empty();
|
||||
}
|
||||
} else {
|
||||
else {
|
||||
self.$('.o_timeline_subtype_list ul').empty();
|
||||
}
|
||||
}
|
||||
else {
|
||||
var context = new data.CompoundContext(this.build_context(), {});
|
||||
return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
|
||||
.then(this.proxy('read_value'));
|
||||
return this.ds_model.call(action_subscribe, [[this.view.datarecord.id],
|
||||
follower_ids,
|
||||
checklist,
|
||||
context])
|
||||
.then(this.proxy('read_value'));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/* Add the widget to registry */
|
||||
core.form_widget_registry.add('mail_followers', Followers);
|
||||
|
||||
});
|
||||
});
|
||||
@@ -107,6 +107,22 @@ function breakword(str){
|
||||
return out;
|
||||
}
|
||||
|
||||
function bindTooltipTo($el, value, position) {
|
||||
$el.tooltip({
|
||||
'title': value,
|
||||
'placement': position,
|
||||
'html': true,
|
||||
'trigger': 'manual',
|
||||
'animation': false,
|
||||
}).on("mouseleave", function () {
|
||||
setTimeout(function () {
|
||||
if (!$(".tooltip:hover").length) {
|
||||
$el.tooltip("hide");
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
parse_email: parse_email,
|
||||
get_image: get_image,
|
||||
@@ -115,6 +131,7 @@ return {
|
||||
get_text2html: get_text2html,
|
||||
expand_domain: expand_domain,
|
||||
breakword: breakword,
|
||||
bindTooltipTo: bindTooltipTo,
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -48,7 +48,7 @@ var Groups = Widget.extend({
|
||||
if (this.groups.length === 0) {
|
||||
return this.$el.empty();
|
||||
}
|
||||
return this.$el.empty().html(QWeb.render('mail.suggestions.groups', {'widget': this}));
|
||||
return this.$el.empty().html(QWeb.render('TimelineSuggestionGroups', {'widget': this}));
|
||||
},
|
||||
|
||||
join_group: function (event) {
|
||||
@@ -72,7 +72,7 @@ var Groups = Widget.extend({
|
||||
}
|
||||
});
|
||||
|
||||
mail.WallSidebar.include({
|
||||
mail.Sidebar.include({
|
||||
start: function () {
|
||||
this._super.apply(this, arguments);
|
||||
var sug_groups = new Groups(this);
|
||||
@@ -80,4 +80,4 @@ mail.WallSidebar.include({
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
+236
-335
@@ -1,356 +1,257 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
|
||||
<!--
|
||||
mail.Widget template used to namespace the css -->
|
||||
<t t-name="mail.Root">
|
||||
<div class="oe_mail">
|
||||
</div>
|
||||
</t>
|
||||
<templates>
|
||||
|
||||
<!--
|
||||
mail.compose_message template
|
||||
This template holds the composition form to write a note or send
|
||||
an e-mail. It contains by default a textarea, that will be replaced
|
||||
by another composition form in the main wall composition form, or
|
||||
for main thread composition form in document form view.
|
||||
|
||||
mail.compose_message.compact template
|
||||
This template holds the composition form to write a message, this box is converted into
|
||||
mail.compose_message when focus on textarea
|
||||
-->
|
||||
<t t-name="mail.compose_message">
|
||||
<div t-if="widget.show_composer and !widget.options.readonly" t-attf-class="oe_msg oe_msg_composer #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
|
||||
<div class="oe_msg_left">
|
||||
<img class="oe_msg_icon" alt="User img" t-attf-src="#{widget.avatar}"/>
|
||||
</div>
|
||||
<div class="oe_msg_center">
|
||||
<div class="oe_msg_content">
|
||||
<button class="oe_full" title="Open the full mail composer"><i class='fa fa-pencil-square-o'/></button>
|
||||
<t t-call="mail.thread.list_recipients"/>
|
||||
<textarea class="field_text"></textarea>
|
||||
</div>
|
||||
<div class="oe_msg_footer">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
<button class="oe_post">
|
||||
<t t-if="!widget.is_log">Send</t>
|
||||
<t t-if="widget.is_log">Log an internal note</t>
|
||||
</button>
|
||||
<t t-call="mail.compose_message.add_attachment"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="widget.show_compact_message and !widget.show_composer and !widget.options.readonly" t-attf-class="oe_msg oe_msg_composer_compact #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
|
||||
<t t-if="widget.options.view_mailbox">
|
||||
<div class="field_text oe_compact oe_compact_inbox">
|
||||
<t t-if="widget.options.compose_placeholder" t-raw="widget.options.compose_placeholder"/>
|
||||
<t t-if="!widget.options.compose_placeholder and widget.options.view_mailbox">Share with my followers...</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="!widget.options.view_mailbox">
|
||||
<div class="field_text oe_compact oe_compact_record">
|
||||
<a class="oe_compose_post" t-if="widget.options.compose_placeholder"><t t-raw="widget.options.compose_placeholder"/></a>
|
||||
<a class="oe_compose_post" t-if="!widget.options.compose_placeholder and !widget.options.view_mailbox"
|
||||
title="Send a message. Followers will be notified.">Send a message</a>
|
||||
<t t-if="widget.options.display_log_button">
|
||||
<span class="oe_grey oe_sep_word">or</span>
|
||||
<a class="oe_compose_log" title="Log a note. Followers will not be notified.">Log an internal note</a>
|
||||
<div t-name="TimelineView.buttons" class="o_timeline_buttons">
|
||||
<t t-if="widget.options.action_buttons !== false">
|
||||
<button type="button" class="o_timeline_button_new btn btn-primary btn-sm">
|
||||
New message
|
||||
</button>
|
||||
<t t-if="!widget.options.readonly and widget.options.show_write_to_followers">
|
||||
<span class="oe_alternative">
|
||||
<span class="oe_fade">or</span>
|
||||
<div class="oe_bounce_container">
|
||||
<t t-if="widget.options.compose_placeholder">
|
||||
<a class="o_timeline_share text-info"><t t-raw="widget.options.compose_placeholder"/></a>
|
||||
</t>
|
||||
<t t-if="! widget.options.compose_placeholder">
|
||||
<a class="o_timeline_share text-info">Write to my followers</a>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<span t-if="!(widget.show_compact_message and !widget.show_composer) and !widget.show_composer" class="oe_placeholder_compose"></span>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
mail.compose_message.add_attachment template
|
||||
Small template to be inserted in the composition for add attachments
|
||||
-->
|
||||
<t t-name="mail.compose_message.add_attachment">
|
||||
<span class="oe_attachment_file">
|
||||
<span class="oe_add">
|
||||
<!-- uploader of file -->
|
||||
<button class="oe_attach"><i class="fa fa-paperclip"/></button>
|
||||
<span class='oe_attach_label'>Attach a File</span>
|
||||
<t t-call="HiddenInputFile">
|
||||
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
||||
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
|
||||
<input type="hidden" name="model" value="mail.compose.message"/>
|
||||
<input type="hidden" name="id" value="0"/>
|
||||
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
|
||||
</t>
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
mail.thread.message.attachments template
|
||||
Template used to display attachments in a mail.message
|
||||
-->
|
||||
<t t-name="mail.thread.message.attachments">
|
||||
<t t-foreach='widget.attachment_ids' t-as='attachment'>
|
||||
<t t-if="attachment.file_type_icon !== 'webimage'">
|
||||
<div t-attf-class="oe_attachment #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type_icon + '.png'"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name' /></div>
|
||||
</a>
|
||||
<div class="oe_delete" t-if="!attachment.upload">
|
||||
<i class='fa fa-times-circle' title="Delete this attachment" t-att-data-id="attachment.id"/>
|
||||
</div>
|
||||
<div class='oe_progress_bar'>
|
||||
uploading
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachment.file_type_icon === 'webimage'">
|
||||
<div t-attf-class="oe_attachment oe_preview #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name' /></div>
|
||||
</a>
|
||||
<div class="oe_delete" t-if="!attachment.upload">
|
||||
<i class='fa fa-times-circle' title="Delete this attachment" t-att-data-id="attachment.id"/>
|
||||
</div>
|
||||
<div class='oe_progress_bar'>
|
||||
uploading
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
template to the recipients list
|
||||
-->
|
||||
<t t-name="mail.thread.list_recipients">
|
||||
<div class="oe_mail_list_recipients" t-if="!widget.is_log">
|
||||
To:
|
||||
<t t-if="!widget.is_private">
|
||||
<span class="oe_all_follower">
|
||||
<t t-if="widget.parent_thread.parent_message.record_name">
|
||||
Followers of <t t-raw="'"' + widget.parent_thread.parent_message.record_name + '"'"/>
|
||||
</t>
|
||||
<t t-if="!widget.parent_thread.parent_message.record_name and widget.options.view_inbox">My Followers</t>
|
||||
<t t-if="!widget.parent_thread.parent_message.record_name and !widget.options.view_inbox">Followers of this document</t>
|
||||
</span>
|
||||
</t>
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-foreach="widget.partner_ids" t-as="partner">
|
||||
<t t-if="widget.is_private or (widget.user_pid != partner[0])">
|
||||
<t t-if="!widget.is_private and inc==0"> and </t>
|
||||
<span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}"><t t-esc="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
|
||||
</span>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="widget.partner_ids.length > 3">
|
||||
<span class="oe_more">, <a><t t-esc="widget.partner_ids.length - 3"/> others...</a></span>
|
||||
<a class="oe_more_hidden"><<<</a>
|
||||
</t>
|
||||
<div t-name="TimelineWall" t-attf-class="o_timeline #{widget.options.view_inbox ? 'o_timeline_inbox' : 'o_timeline_chatter'}">
|
||||
<div class="o_timeline_buttons"/>
|
||||
<div class="o_timeline-placeholder"></div>
|
||||
<t t-if="widget.options.view_inbox">
|
||||
<aside class="o_timeline_inbox_aside"></aside>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<t t-name="ComposeMessage">
|
||||
<div t-if="widget.show_composer and !widget.options.readonly"
|
||||
class="o_timeline_msg o_timeline_msg_composer_compact">
|
||||
<div class="o_timeline_msg o_timeline_msg_composer">
|
||||
<div class="o_timeline_msg_center">
|
||||
<div class="o_timeline_msg_content">
|
||||
<button class="oe_full" title="Open the full mail composer">
|
||||
<i class='fa fa-pencil-square-o'/>
|
||||
</button>
|
||||
<t t-call="ThreadListRecipients"/>
|
||||
<textarea class="field_text"></textarea>
|
||||
</div>
|
||||
<div class="o_timeline_msg_footer">
|
||||
<div class="o_timeline_msg_attachment_list"></div>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="oe_post btn btn-primary">
|
||||
<t t-if="!widget.is_log">Send</t>
|
||||
<t t-if="widget.is_log">Log</t>
|
||||
</button>
|
||||
<t t-call="ComposeMessage.AddAttachment"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_recipients" t-if="!widget.is_log">
|
||||
</div>
|
||||
|
||||
<div t-if="widget.show_compact_message and !widget.show_composer and !widget.options.readonly"
|
||||
class="o_timeline_msg o_timeline_msg_composer_compact">
|
||||
<t t-if="!widget.options.view_inbox">
|
||||
<div class="field_text o_timeline_compact o_timeline_compact_record">
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="o_timeline_compose_post btn btn-primary"
|
||||
t-if="widget.options.compose_placeholder">
|
||||
<t t-raw="widget.options.compose_placeholder"/>
|
||||
</button>
|
||||
<button type="button" class="o_timeline_compose_post btn btn-primary"
|
||||
t-if="!widget.options.compose_placeholder and !widget.options.view_inbox"
|
||||
title="Send a message. Followers will be notified.">
|
||||
New message
|
||||
</button>
|
||||
</div>
|
||||
<t t-if="widget.options.display_log_button">
|
||||
<span class="text-muted o_timeline_sep_word">or</span>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button class="o_timeline_compose_log btn btn-link btn-small"
|
||||
title="Log a note. Followers will not be notified.">
|
||||
Log an internal node
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<span t-if="!(widget.show_compact_message and !widget.show_composer) and !widget.show_composer"
|
||||
class="oe_placeholder_compose"></span>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
ComposeMessage.AddAttachment template
|
||||
Small template to be inserted in the composition for add attachments
|
||||
-->
|
||||
<t t-name="ComposeMessage.AddAttachment">
|
||||
<span class="oe_attachment_file">
|
||||
<span class="oe_add">
|
||||
<!-- uploader of file -->
|
||||
<button class="oe_attach"><i class="fa fa-paperclip"/></button>
|
||||
<span class='oe_attach_label'>Attach a file</span>
|
||||
<t t-call="HiddenInputFile">
|
||||
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
|
||||
<t t-set="fileupload_action" t-translation="off">/web/binary/upload_attachment</t>
|
||||
<input type="hidden" name="model" value="mail.compose.message"/>
|
||||
<input type="hidden" name="id" value="0"/>
|
||||
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
|
||||
</t>
|
||||
</span>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
template to the recipients list
|
||||
-->
|
||||
<t t-name="ThreadListRecipients">
|
||||
<div class="o_timeline_list_recipients" t-if="!widget.is_log">
|
||||
To:
|
||||
<t t-if="!widget.is_private">
|
||||
<span class="o_timeline_all_follower">
|
||||
<t t-if="widget.parent_thread.parent_message.record_name">
|
||||
Followers of
|
||||
<t t-raw="'"' + widget.parent_thread.parent_message.record_name + '"'"/>
|
||||
</t>
|
||||
<t t-if="!widget.parent_thread.parent_message.record_name and widget.options.view_mailbox">
|
||||
My Followers </t>
|
||||
<t t-if="!widget.parent_thread.parent_message.record_name and !widget.options.view_mailbox">
|
||||
Followers of this document </t>
|
||||
</span>
|
||||
</t>
|
||||
<t t-set="inc" t-value="0"/>
|
||||
<t t-foreach="widget.partner_ids" t-as="partner">
|
||||
<t t-if="widget.is_private or (widget.user_pid != partner[0])">
|
||||
<t t-if="!widget.is_private and inc==0"> and </t>
|
||||
<span t-attf-class="o_timeline_partner_follower #{inc>=3?'oe_hidden':''}">
|
||||
<t t-if="inc" t-raw="', '"/>
|
||||
<a t-if="widget.options.show_link"
|
||||
t-attf-href="#model=res.partner&id=#{partner[0]}">
|
||||
<t t-esc="partner[1]"/>
|
||||
</a>
|
||||
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
|
||||
</span>
|
||||
<t t-set="inc" t-value="inc+1"/>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-if="widget.partner_ids.length > 3">
|
||||
<span class="oe_more">
|
||||
, <a><t t-esc="widget.partner_ids.length - 3"/> others...</a>
|
||||
</span>
|
||||
</t>
|
||||
|
||||
<div class="o_timeline_recipients" t-if="!widget.is_log">
|
||||
<t t-foreach='widget.recipients' t-as='recipient'>
|
||||
<label t-attf-title="Add as recipient and follower (reason: #{recipient.reason})">
|
||||
<input type="checkbox" t-att-checked="recipient.checked ? 'checked' : undefined" t-att-data="recipient.full_name"/>
|
||||
<input type="checkbox"
|
||||
t-att-checked="recipient.checked ? 'checked' : undefined"
|
||||
t-att-data="recipient.full_name"/>
|
||||
<t t-esc="recipient.name"/>
|
||||
<t t-if="recipient.email_address">(<t t-esc="recipient.email_address"/>)</t>
|
||||
<t t-if="!recipient.email_address">(no email address)</t>
|
||||
</label>
|
||||
</t>
|
||||
</div>
|
||||
<div t-if="widget.is_log">
|
||||
<span>Log an internal note which will not be sent to followers. Note that users reading the document can read internal notes.</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
wall main template
|
||||
Template used to display the communication history in the wall.
|
||||
-->
|
||||
<div t-name="mail.wall" class="oe-view-manager oe_mail_wall oe_view_manager_current">
|
||||
<div class="oe-control-panel">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-6 oe-cp-title">
|
||||
<ol class="oe-view-title breadcrumb">
|
||||
<li class="active"><t t-esc="widget.action.name"/></li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="oe-cp-search-view col-md-6" />
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-6 col-sm-offset-6">
|
||||
<div class="oe-search-options btn-group"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe-view-manager-content">
|
||||
<div class="oe_mail-placeholder"></div>
|
||||
<aside class="oe_mail_wall_aside"></aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
Empty template that holds the sidebar of the Wall
|
||||
-->
|
||||
<t t-name="mail.wall.sidebar">
|
||||
<div class="oe_mail_wall_sidebar"></div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
display message on the wall when there are no message
|
||||
-->
|
||||
<t t-name="mail.wall_no_message">
|
||||
<div class="oe_view_nocontent">No messages.</div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
error message for uploading
|
||||
-->
|
||||
<t t-name="mail.error_upload">Uploading error</t>
|
||||
<t t-name="mail.error_upload_please_wait">Please, wait while the file is uploading.</t>
|
||||
|
||||
<!--
|
||||
record_thread main template
|
||||
Template used to display the communication history in documents
|
||||
form view.
|
||||
-->
|
||||
<div t-name="mail.record_thread" class="oe_record_thread">
|
||||
<div class="oe_mail-placeholder">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t t-name="mail.thread">
|
||||
<div t-attf-class="oe_thread #{widget.root?'oe_root_thread':''}"/>
|
||||
</t>
|
||||
|
||||
<!-- default layout -->
|
||||
<t t-name="mail.thread.message">
|
||||
<div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} #{widget.subtype ? '' : 'oe_msg_nobody'} oe_msg_#{widget.message_type}">
|
||||
|
||||
<div class='oe_msg_left'>
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
|
||||
<img class="oe_msg_icon" t-att-src="widget.avatar"/>
|
||||
</a>
|
||||
<img t-if="!widget.options.show_link" class="oe_msg_icon" t-att-src="widget.avatar"/>
|
||||
</div>
|
||||
|
||||
<div class="oe_msg_center">
|
||||
<div class='oe_msg_icons' t-if="!widget.options.readonly">
|
||||
<span class='oe_read' t-if="widget.options.show_read"><i title="Done" class="fa fa-check"/></span>
|
||||
<span class='oe_unread' t-if="widget.is_favorite and widget.options.show_unread"><i title="Set back to Todo" class="fa fa-inbox"/></span>
|
||||
<span class='oe_unread' t-if="!widget.is_favorite and widget.options.show_unread"><i title="Move to Inbox" class="fa fa-inbox"/></span>
|
||||
<span class='oe_reply' t-if="widget.options.show_reply_button"><i title="Reply" class="fa fa-reply"/></span>
|
||||
<span t-attf-class="oe_star #{widget.is_favorite?'oe_starred':''}"><i title="Mark as Todo" class="fa fa-star"/></span>
|
||||
</div>
|
||||
<!-- message itself -->
|
||||
<div class="oe_msg_content">
|
||||
<h1 t-if="(widget.show_record_name or widget.subject) and !widget.thread_level" class="oe_msg_title">
|
||||
<a t-if="widget.options.show_link and widget.show_record_name" class="oe_mail_action_model"
|
||||
t-attf-href="#action=mail.action_mail_redirect&model=#{widget.model}&res_id=#{widget.res_id}">
|
||||
<t t-esc="widget.record_name"/>
|
||||
</a>
|
||||
<span t-if="!widget.options.show_link and widget.show_record_name"><t t-esc="widget.record_name"/></span>
|
||||
<t t-if="widget.show_record_name and widget.subject">: </t>
|
||||
<t t-if="widget.subject" t-esc="widget.subject"/>
|
||||
</h1>
|
||||
<div class="oe_msg_body">
|
||||
<t t-if="widget.body_short">
|
||||
<div class="oe_msg_body_short"><t t-raw="widget.body_short"/></div>
|
||||
<div class="oe_msg_body_long" style="display: none;"><t t-raw="widget.body"/><span class="oe_mail_reduce"><a href="#">read less</a></span></div>
|
||||
</t>
|
||||
<t t-if="! widget.body_short">
|
||||
<t t-raw="widget.body"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_msg_footer">
|
||||
<t t-if="widget.attachment_ids.length > 0">
|
||||
<div class="oe_msg_attachment_list"></div>
|
||||
</t>
|
||||
<a t-if="widget.author_id and widget.options.show_link and widget.author_id[0]" t-attf-href="#model=res.partner&id=#{widget.author_id[0]}" t-att-data-partner="widget.author_id[0]" class="oe_mail_action_author"><t t-esc="widget.author_id[2]"/></a>
|
||||
<span t-if="widget.author_id and (!widget.options.show_link or !widget.author_id[0])"><t t-esc="widget.author_id[2]"/></span>
|
||||
<t t-if="widget.message_type == 'notification'">
|
||||
updated document
|
||||
<t t-if="widget.partner_ids.length > 0">
|
||||
<span class='oe_subtle'>•</span>
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="widget.message_type == 'comment' and ! widget.subtype and widget.partner_ids.length == 0">
|
||||
logged a note
|
||||
</t>
|
||||
<t t-if="(widget.message_type == 'comment' or widget.message_type == 'email') and (widget.subtype or widget.partner_ids.length > 0)">
|
||||
<!-- Remove nobody if user have no rights on partner that will display 'portal to nobody 5 minutes ago' that will confuse to end user, now it display 'portal 5 minutes ago' if no rights.-->
|
||||
<t t-if="widget.partner_ids.length > 0">
|
||||
to
|
||||
</t>
|
||||
</t>
|
||||
<t t-if="widget.message_type == 'notification' or ( (widget.message_type == 'email' or widget.message_type == 'comment') and (widget.subtype or widget.partner_ids.length > 0))"
|
||||
t-foreach="widget.partner_ids.slice(0, 3)" t-as="partner">
|
||||
<span t-attf-class="oe_partner_follower">
|
||||
<a t-if="widget.options.show_link" t-attf-href="#model=res.partner&id=#{partner[0]}" t-att-data-partner="partner[0]" class="oe_mail_action_author"><t t-esc="partner[1]"/></a>
|
||||
<t t-if="!widget.options.show_link" t-esc="partner[1]"/>
|
||||
</span>
|
||||
<t t-if="!partner_last">,</t>
|
||||
</t>
|
||||
<t t-if="widget.partner_ids.length > 3">
|
||||
<span t-att-title="widget.extra_partners_str">and <t t-esc="widget.extra_partners_nbr"/> more</span>
|
||||
</t>
|
||||
<t t-if="widget.message_type == 'notification' and widget.partner_ids.length > 0">
|
||||
notified
|
||||
</t>
|
||||
<span class='oe_subtle'>•</span>
|
||||
<span t-att-title="widget.date">
|
||||
<t t-if="widget.timerelative" t-esc="widget.timerelative"/>
|
||||
<t t-if="!widget.timerelative" t-raw="widget.display_date"/>
|
||||
</span>
|
||||
<span t-if="!widget.options.readonly" class='oe_subtle'>•</span>
|
||||
<t t-if="!widget.options.readonly" t-call="mail.thread.message.vote"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!-- expandable message layout -->
|
||||
<t t-name="mail.thread.expandable">
|
||||
<div t-attf-class="oe_msg oe_msg_#{widget.message_type} #{widget.max_limit ? 'oe_max_limit' : ''} #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
|
||||
<div class="oe_msg_content oe_msg_more_message">
|
||||
<div class='oe_separator'></div>
|
||||
<a t-if="widget.nb_messages <= 0" class="oe_msg_fetch_more">show more message</a>
|
||||
<a t-if="widget.nb_messages === 1" class="oe_msg_fetch_more">show one more message</a>
|
||||
<a t-if="widget.nb_messages > 1" class="oe_msg_fetch_more">show <t t-esc="widget.nb_messages" /> more messages</a>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
mail.compose_message.button_top_bar
|
||||
render of the button on the user bar for open wizard compose message
|
||||
-->
|
||||
<t t-name="mail.compose_message_top_button">
|
||||
<li title='Compose new Message' class="oe_topbar_item oe_topbar_compose_full_email">
|
||||
<a href="#">
|
||||
<i class="fa fa-envelope-o fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
<!-- mail.thread.message.vote
|
||||
Template used to display Like/Unlike in a mail.message
|
||||
-->
|
||||
<span t-name="mail.thread.message.vote">
|
||||
<span class="oe_mail_vote_count" t-if='widget.vote_nb > 0'>
|
||||
<t t-esc='widget.vote_nb' />
|
||||
<i class="fa fa-thumbs-o-up"></i>
|
||||
<div class="o_timeline_is_log" t-if="widget.is_log">
|
||||
<span class="text-muted">
|
||||
Log an internal note which will not be sent to followers.
|
||||
Note that users reading the document can read internal notes.
|
||||
</span>
|
||||
<a href='#' class="oe_msg_vote">
|
||||
<t t-if="!widget.has_voted">like</t>
|
||||
<t t-if="widget.has_voted">unlike</t>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
</template>
|
||||
<div t-name="MailThread" class="oe_thread">
|
||||
<div t-attf-class="#{widget.options.view_inbox ? 'o_timeline_thread_parent' : ''}">
|
||||
<t t-raw="widget.content"/>
|
||||
</div>
|
||||
<t t-if="!widget.messages.length and !widget.root">
|
||||
<t t-call="NoMessage" />
|
||||
</t>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
mail.thread.message.attachments template
|
||||
Template used to display attachments in a mail.message
|
||||
-->
|
||||
<t t-name="ThreadMessageAttachments">
|
||||
<t t-foreach='widget.format_attachments(record)' t-as='attachment'>
|
||||
<t t-if="attachment.file_type_icon !== 'webimage'">
|
||||
<div t-attf-class="oe_attachment #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="'/mail/static/src/img/mimetypes/' + attachment.file_type_icon + '.png'"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name' /></div>
|
||||
</a>
|
||||
<div class='oe_delete oe_e'>
|
||||
<i class='fa fa-times-circle' title="Delete this attachment" t-att-data-id="attachment.id"/>
|
||||
</div>
|
||||
<div class='oe_progress_bar'>
|
||||
uploading
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="attachment.file_type_icon === 'webimage'">
|
||||
<div t-attf-class="oe_attachment oe_preview #{attachment.upload ? 'oe_uploading' : ''}">
|
||||
<a t-att-href='attachment.url' target="_blank">
|
||||
<img t-att-src="widget.attachments_resize_image(attachment.id, [100,80])"></img>
|
||||
<div class='oe_name'><t t-raw='attachment.name'/></div>
|
||||
</a>
|
||||
<div class='oe_delete oe_e'>
|
||||
<i class='fa fa-times-circle' title="Delete this attachment" t-att-data-id="attachment.id"/>
|
||||
</div>
|
||||
<div class='oe_progress_bar'>
|
||||
uploading
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-name="MessageReadUnread">
|
||||
<i t-if="record.options.show_read" title="Done"
|
||||
class="oe_read fa fa-check" t-att-data-id="record.id"/>
|
||||
<i t-if="record.is_favorite and record.options.show_unread" title="Set back to Todo"
|
||||
class="oe_unread fa fa-inbox" t-att-data-id="record.id"/>
|
||||
<i t-if="!record.is_favorite and record.options.show_unread" title="Move to Inbox"
|
||||
class="oe_unread fa fa-inbox" t-att-data-id="record.id"/>
|
||||
</t>
|
||||
|
||||
<t t-name="MessageVote">
|
||||
<span class="o_timeline_vote_count" t-att-data-id="record.id" t-if='record.vote_nb > 0'>
|
||||
<t t-esc='record.vote_nb'/><i class="fa fa-thumbs-o-up" t-att-data-id="record.id"></i>
|
||||
</span>
|
||||
<t t-if="!record.has_voted">Like</t>
|
||||
<t t-if="record.has_voted">Unlike</t>
|
||||
</t>
|
||||
|
||||
<t t-name="NoMessage">
|
||||
<div class="oe_view_nocontent">No message.</div>
|
||||
</t>
|
||||
|
||||
<t t-name="TimelineSidebar">
|
||||
<div class="o_timeline_sidebar"></div>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
ComposeMessage.ButtonTopBar
|
||||
render of the button on the user bar for open wizard compose message
|
||||
-->
|
||||
<t t-name="ComposeMessageTopButton">
|
||||
<li title='Compose new Message' class="oe_topbar_item oe_topbar_compose_full_email">
|
||||
<a href="#">
|
||||
<i class="fa fa-envelope-o fa-fw"></i>
|
||||
</a>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -1,65 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<template>
|
||||
<templates>
|
||||
|
||||
<!--
|
||||
followers main template
|
||||
Template used to display the followers, the actions and the subtypes in a record.
|
||||
-->
|
||||
<div t-name="mail.followers" class="oe_followers">
|
||||
<div class="oe_actions">
|
||||
<div t-attf-class="btn-group oe_subtype_list">
|
||||
<button class="btn oe_follower oe_notfollow">
|
||||
<span class="oe_follow">Follow</span>
|
||||
<span class="oe_unfollow">Unfollow</span>
|
||||
<span class="oe_following">Following</span>
|
||||
</button>
|
||||
<button type="button" t-attf-class="btn btn-default dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu" role="menu"></ul>
|
||||
</div>
|
||||
|
||||
<t t-if="widget.comment">
|
||||
<h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
|
||||
</t>
|
||||
</div>
|
||||
<div class='oe_follower_title_box'>
|
||||
<h4 class='oe_follower_title'>Followers</h4>
|
||||
<a t-if="widget.view_is_editable" href='#' class="oe_invite">Add others</a>
|
||||
</div>
|
||||
<div class="oe_follower_list"></div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
followers.partner template
|
||||
Template used to display a partner following the record
|
||||
-->
|
||||
<div t-name="mail.followers.partner" class='oe_partner'>
|
||||
<img class="oe_mail_thumbnail oe_mail_frame" t-att-src="record.avatar_url"/>
|
||||
<a t-attf-href="#model=res.partner&id=#{record.id}" t-att-title="record.name" t-att-data-partner="record.id"><t t-esc="record.name"/></a>
|
||||
<i t-if="record.is_editable and (widget.records_length > 1)" class="oe_edit_subtype fa fa-pencil oe_hidden" title="Edit subscription" t-att-data-id="record.id"/>
|
||||
<i t-if="widget.view_is_editable" class="oe_remove_follower fa fa-remove" title="Remove this follower" t-att-data-id="record.id"/>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
followers.subtype template
|
||||
Template used to display message subtypes of a follower subscription
|
||||
<!--
|
||||
followers main template
|
||||
Template used to display the followers, the actions and the subtypes in a record.
|
||||
-->
|
||||
<t t-name="mail.followers.subtype">
|
||||
<table class='oe_subtype'>
|
||||
<tr>
|
||||
<td width="10%">
|
||||
<input type="checkbox" t-att-checked="record.followed" t-att-id="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')" t-att-data-id="record.id" t-att-name="record.name" class="oe_msg_subtype_check"/>
|
||||
</td>
|
||||
<td>
|
||||
<label t-att-for="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')"><t t-raw="record.name"/></label>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</t>
|
||||
<div t-name="mail_followers" class="o_timeline_followers">
|
||||
<div t-attf-class="btn-group-sm o_timeline_follower_title_box">
|
||||
<button t-attf-class="btn btn-default dropdown-toggle o_timeline_partner_list"
|
||||
data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="o_timeline_follower_title">Followers</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu o_timeline_follower_list" role="menu"></ul>
|
||||
</div>
|
||||
<div class="oe_actions">
|
||||
<div t-attf-class="btn-group-sm o_timeline_subtype_list">
|
||||
<button class="btn btn-default o_timeline_follower o_timeline_notfollow">
|
||||
<span class="o_timeline_follow">Follow</span>
|
||||
<span class="glyphicon glyphicon-remove o_timeline_unfollow" aria-hidden="true"> Unfollow</span>
|
||||
<span class="glyphicon glyphicon-ok o_timeline_following" aria-hidden="true"> Following</span>
|
||||
</button>
|
||||
<button t-attf-class="btn btn-default dropdown-toggle o_timeline_sbtp_list"
|
||||
data-toggle="dropdown" aria-expanded="false">
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu oe_subtype" role="menu"></ul>
|
||||
</div>
|
||||
|
||||
<t t-name="mail.followers.show_more">
|
||||
<div class="oe_partner oe_show_more">And <t t-esc="number"/> more.</div>
|
||||
</t>
|
||||
<!--<t t-if="widget.comment">
|
||||
<h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
|
||||
</t>-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<!--
|
||||
followers.partner template
|
||||
Template used to display a partner following the record
|
||||
-->
|
||||
<li t-name="mail_followers_partner" class='oe_partner'>
|
||||
<span> <img class="oe_mail_thumbnail oe_mail_frame" t-att-src="record.avatar_url"/> </span>
|
||||
<span class="oe_partner_name">
|
||||
<a t-attf-href="#model=res.partner&id=#{record.id}"
|
||||
t-att-title="record.name" t-att-data-partner="record.id">
|
||||
<t t-esc="record.name"/> </a>
|
||||
</span>
|
||||
<i t-if="record.is_editable and (widget.records_length > 1)"
|
||||
class="oe_edit_subtype fa fa-pencil oe_hidden" title="Edit subscription"
|
||||
t-att-data-id="record.id"/>
|
||||
<i t-if="widget.view_is_editable" class="oe_remove_follower fa fa-remove"
|
||||
title="Remove this follower" t-att-data-id="record.id"/>
|
||||
</li>
|
||||
|
||||
<t t-name="mail_followers_add_more">
|
||||
<li t-if="widget.view_is_editable" class="oe_invite">
|
||||
<a href="#"> Add followers </a>
|
||||
</li>
|
||||
<li t-if="widget.view_is_editable and (widget.followers.length > 0)" class="divider"></li>
|
||||
</t>
|
||||
|
||||
<!--
|
||||
followers.subtype template
|
||||
Template used to display message subtypes of a follower subscription
|
||||
-->
|
||||
<t t-name="mail_followers_subtype">
|
||||
<li>
|
||||
<input type="checkbox" t-att-checked="record.followed"
|
||||
t-att-id="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')"
|
||||
t-att-data-id="record.id" t-att-name="record.name" class="o_timeline_msg_subtype_check"/>
|
||||
|
||||
<label t-att-for="'input_mail_followers_subtype_'+record.id+(dialog ? '_in_dialog': '')">
|
||||
<t t-raw="record.name"/>
|
||||
</label>
|
||||
</li>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
@@ -2,18 +2,17 @@
|
||||
<template>
|
||||
|
||||
<!-- Groups placeholder in sidebar -->
|
||||
<t t-extend="mail.wall.sidebar">
|
||||
<t t-jquery=".oe_mail_wall_sidebar" t-operation="append">
|
||||
<t t-extend="TimelineSidebar">
|
||||
<t t-jquery=".o_timeline_sidebar" t-operation="append">
|
||||
<div class="oe_suggestions_groups"></div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
|
||||
<!-- Suggested groups -->
|
||||
<div t-name="mail.suggestions.groups" class="oe_sidebar_suggestion oe_suggestion_group">
|
||||
<div t-name="TimelineSuggestionGroups" class="oe_sidebar_suggestion oe_suggestion_group">
|
||||
<div class="oe_suggest_title">
|
||||
<i class="oe_suggestion_remove oe_suggestion_group fa fa-remove"/>
|
||||
<h2>Suggested Groups</h2>
|
||||
<span>Suggested Groups</span>
|
||||
</div>
|
||||
<div class="oe_suggest_items">
|
||||
<t t-foreach="widget.groups" t-as="result">
|
||||
@@ -27,12 +26,13 @@
|
||||
<a class="oe_suggestion_item_name" t-attf-href="#model=mail.group&id=#{result.id}"><t t-esc="result.name"/></a>
|
||||
<i class="oe_suggestion_remove_item oe_suggestion_group fa fa-remove" t-att-id="result.id"/>
|
||||
<br/>
|
||||
<button t-att-id="result.id" class="oe_suggestion_join">Join Group</button>
|
||||
<div class="btn-group btn-group-sm">
|
||||
<button type="button" class="btn btn-default oe_suggestion_join" t-att-id="result.id">Join Group</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
</template>
|
||||
@@ -67,12 +67,12 @@ class TestMailFeatures(TestMail):
|
||||
inbox_act_id = self.ref('mail.action_mail_inbox_feeds')
|
||||
action = self.env['mail.thread'].with_context({'params': {}}).sudo(self.user_employee).message_redirect_action()
|
||||
self.assertEqual(
|
||||
action.get('type'), 'ir.actions.client',
|
||||
'URL redirection: action without parameters should redirect to client action Inbox'
|
||||
action.get('type'), 'ir.actions.act_window',
|
||||
'URL redirection: action without parameters should redirect to action act window Inbox'
|
||||
)
|
||||
self.assertEqual(
|
||||
action.get('id'), inbox_act_id,
|
||||
'URL redirection: action without parameters should redirect to client action Inbox'
|
||||
'URL redirection: action without parameters should redirect to action act window Inbox'
|
||||
)
|
||||
|
||||
def test_inbox_redirection_document(self):
|
||||
@@ -114,12 +114,12 @@ class TestMailFeatures(TestMail):
|
||||
'params': {'message_id': message.id}
|
||||
}).sudo(self.user_public).message_redirect_action()
|
||||
self.assertEqual(
|
||||
action.get('type'), 'ir.actions.client',
|
||||
'URL redirection: action without parameters should redirect to client action Inbox'
|
||||
action.get('type'), 'ir.actions.act_window',
|
||||
'URL redirection: action without parameters should redirect to action act window Inbox'
|
||||
)
|
||||
self.assertEqual(
|
||||
action.get('id'), inbox_act_id,
|
||||
'URL redirection: action without parameters should redirect to client action Inbox'
|
||||
'URL redirection: action without parameters should redirect to action act window Inbox'
|
||||
)
|
||||
|
||||
@mute_logger('openerp.models')
|
||||
@@ -130,12 +130,12 @@ class TestMailFeatures(TestMail):
|
||||
'params': {'model': 'mail.group', 'res_id': self.group_pigs.id}
|
||||
}).sudo(self.user_public).message_redirect_action()
|
||||
self.assertEqual(
|
||||
action.get('type'), 'ir.actions.client',
|
||||
'URL redirection: action without parameters should redirect to client action Inbox'
|
||||
action.get('type'), 'ir.actions.act_window',
|
||||
'URL redirection: action without parameters should redirect to action act window Inbox'
|
||||
)
|
||||
self.assertEqual(
|
||||
action.get('id'), inbox_act_id,
|
||||
'URL redirection: action without parameters should redirect to client action Inbox'
|
||||
'URL redirection: action without parameters should redirect to action act window Inbox'
|
||||
)
|
||||
|
||||
@mute_logger('openerp.addons.mail.models.mail_mail')
|
||||
|
||||
@@ -17,68 +17,72 @@ class TestMessageRead(TestMail):
|
||||
self.msg_4 = self.group_pigs.message_post(body='2-1', subtype='mt_comment', parent_id=self.msg_2.id)
|
||||
self.msg_5 = self.group_pigs.message_post(body='1-2', subtype='mt_comment', parent_id=self.msg_1.id)
|
||||
self.msg_6 = self.group_pigs.message_post(body='2-2', subtype='mt_comment', parent_id=self.msg_2.id)
|
||||
self.msg_7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=self.msg_3.id)
|
||||
self.msg_8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=self.msg_4.id)
|
||||
self.msg_9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=self.msg_3.id)
|
||||
self.msg_10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=self.msg_4.id)
|
||||
self.msg_7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=self.msg_1.id)
|
||||
self.msg_8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=self.msg_2.id)
|
||||
self.msg_9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=self.msg_1.id)
|
||||
self.msg_10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=self.msg_2.id)
|
||||
self.msg_ids = [self.msg_10.id, self.msg_9.id, self.msg_8.id, self.msg_7.id, self.msg_6.id, self.msg_5.id, self.msg_4.id, self.msg_3.id, self.msg_2.id, self.msg_1.id, self.msg_0.id]
|
||||
self.ordered_msg_ids = [self.msg_2.id, self.msg_4.id, self.msg_6.id, self.msg_8.id, self.msg_10.id, self.msg_1.id, self.msg_3.id, self.msg_5.id, self.msg_7.id, self.msg_9.id, self.msg_0.id]
|
||||
self.ordered_msg_ids = [self.msg_10.id, self.msg_8.id, self.msg_6.id, self.msg_4.id, self.msg_2.id, self.msg_9.id, self.msg_7.id, self.msg_5.id, self.msg_3.id, self.msg_1.id, self.msg_0.id]
|
||||
|
||||
# TODO: with_context({'mail_read_set_read': True})
|
||||
|
||||
def test_message_read_ids(self):
|
||||
""" message_read: test reading specific ids """
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).browse(self.msg_ids[2:4]).message_read(domain=[('body', 'like', 'dummy')])
|
||||
read_msg_ids = [msg.get('id') for msg in messages]
|
||||
self.assertEqual(read_msg_ids, self.msg_ids[2:4], 'message_read with direct ids should read only the requested ids')
|
||||
read_msg_list = messages['threads'][0]
|
||||
msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
self.assertEqual(msg_ids, self.msg_ids[2:4], 'message_read with direct ids should read only the requested ids')
|
||||
|
||||
def test_message_read_domain(self):
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], limit=200)
|
||||
read_msg_ids = [msg.get('id') for msg in messages]
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], limit=200, child_limit=200)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
self.assertEqual(read_msg_ids, self.msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
|
||||
|
||||
def test_message_read_domain_thread(self):
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], limit=200, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in messages]
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], thread_level=1, limit=200, child_limit=200)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
self.assertEqual(read_msg_ids, self.ordered_msg_ids, 'message_read threaded with domain on Pigs should equal all messages of Pigs, and sort them with newer thread first, last message last in thread')
|
||||
|
||||
def test_message_read_expandable(self):
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], limit=1, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
# TDE TODO: test expandables order
|
||||
type_list = map(lambda item: item.get('type'), messages)
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], thread_level=1, limit=1, child_limit=1)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
read_ancestor_ids = [msg.get('parent_id') if msg.get('parent_id') != 0 else msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
type_list = [msg.get('message_type') for msg in read_msg_list]
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(messages), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
|
||||
self.assertEqual(len(type_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
|
||||
self.assertEqual(set([self.msg_2.id, self.msg_10.id]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
|
||||
self.assertEqual(messages[1].get('parent_id'), messages[0].get('id'), 'message_read should set the ancestor to the thread header')
|
||||
self.assertEqual(read_ancestor_ids[0], read_ancestor_ids[1], 'message_read should set the ancestor to the thread header')
|
||||
# Data: get expandables
|
||||
new_threads_exp, new_msg_exp = None, None
|
||||
for msg in messages:
|
||||
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
|
||||
new_threads_exp = msg
|
||||
elif msg.get('type') == 'expandable':
|
||||
new_threads_exp = read_msg_list[-1]
|
||||
for msg in read_msg_list:
|
||||
if msg.get('parent_id') and msg.get('message_type') == 'expandable':
|
||||
new_msg_exp = msg
|
||||
|
||||
# Do: fetch new messages in first thread, domain from expandable
|
||||
self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable')
|
||||
domain = new_msg_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
self.assertIn(('id', 'child_of', self.msg_2.id), domain, 'new messages expandable domain should contain a child_of condition')
|
||||
self.assertIn(('id', '>=', self.msg_4.id), domain, 'new messages expandable domain should contain an id greater than condition')
|
||||
self.assertIn(('id', '<=', self.msg_8.id), domain, 'new messages expandable domain should contain an id less than condition')
|
||||
self.assertIn(('parent_id', '=', self.msg_2.id), domain, 'new messages expandable domain should contain a child_of condition')
|
||||
self.assertIn(('id', '>', self.msg_2.id), domain, 'new messages expandable domain should contain an id greater than condition')
|
||||
self.assertIn(('id', '<', self.msg_10.id), domain, 'new messages expandable domain should contain an id less than condition')
|
||||
self.assertEqual(new_msg_exp.get('parent_id'), self.msg_2.id, 'new messages expandable should have parent_id set to the thread header')
|
||||
# Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS), 2 messages
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, limit=2, thread_level=0, parent_id=self.msg_2.id)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
new_msg_exp = [msg for msg in messages if msg.get('type') == 'expandable'][0]
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, thread_level=0, limit=2)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
new_msg_exp = [msg for msg in read_msg_list if msg.get('message_type') == 'expandable'][0]
|
||||
# Test: structure content, 2 messages and 1 thread expandable
|
||||
self.assertEqual(len(messages), 3, 'message_read in Pigs thread should return 2 messages and 1 expandables')
|
||||
self.assertEqual(len(read_msg_list), 3, 'message_read in Pigs thread should return 2 messages and 1 expandables')
|
||||
self.assertEqual(set([self.msg_6.id, self.msg_8.id]), set(read_msg_ids), 'message_read in Pigs thread should return 2 more previous messages in thread')
|
||||
# Do: read the last message
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=new_msg_exp.get('domain'), limit=2, thread_level=0, parent_id=self.msg_2.id)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=new_msg_exp.get('domain'), thread_level=0, limit=2)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list]
|
||||
# Test: structure content, 1 message
|
||||
self.assertEqual(len(messages), 1, 'message_read in Pigs thread should return 1 message')
|
||||
self.assertEqual(len(read_msg_list), 1, 'message_read in Pigs thread should return 1 message')
|
||||
self.assertEqual(set([self.msg_4.id]), set(read_msg_ids), 'message_read in Pigs thread should return the last message in thread')
|
||||
# Do: fetch a new thread, domain from expandable
|
||||
self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
|
||||
@@ -88,33 +92,35 @@ class TestMessageRead(TestMail):
|
||||
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
|
||||
self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an parent_id')
|
||||
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, limit=1, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, thread_level=1, limit=1, child_limit=1)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
read_ancestor_ids = [msg.get('parent_id') if msg.get('parent_id') != 0 else msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
type_list = [msg.get('message_type') for msg in read_msg_list]
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(messages), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
|
||||
self.assertEqual(len(type_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
|
||||
self.assertEqual(set([self.msg_1.id, self.msg_9.id]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent')
|
||||
self.assertEqual(messages[1].get('parent_id'), messages[0].get('id'), 'message_read should set the ancestor to the thread header')
|
||||
self.assertEqual(read_ancestor_ids[0], read_ancestor_ids[1], 'message_read should set the ancestor to the thread header')
|
||||
# Data: get expandables
|
||||
new_threads_exp, new_msg_exp = None, None
|
||||
for msg in messages:
|
||||
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
|
||||
new_threads_exp = msg
|
||||
elif msg.get('type') == 'expandable':
|
||||
new_threads_exp = read_msg_list[-1]
|
||||
for msg in read_msg_list:
|
||||
if msg.get('parent_id') and msg.get('message_type') == 'expandable':
|
||||
new_msg_exp = msg
|
||||
# Do: fetch new messages in second thread, domain from expandable
|
||||
self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable')
|
||||
domain = new_msg_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
self.assertIn(('id', 'child_of', self.msg_1.id), domain, 'new messages expandable domain should contain a child_of condition')
|
||||
self.assertIn(('id', '>=', self.msg_3.id), domain, 'new messages expandable domain should contain an id greater than condition')
|
||||
self.assertIn(('id', '<=', self.msg_7.id), domain, 'new messages expandable domain should contain an id less than condition')
|
||||
self.assertIn(('parent_id', '=', self.msg_1.id), domain, 'new messages expandable domain should contain a child_of condition')
|
||||
self.assertIn(('id', '>', self.msg_1.id), domain, 'new messages expandable domain should contain an id greater than condition')
|
||||
self.assertIn(('id', '<', self.msg_9.id), domain, 'new messages expandable domain should contain an id less than condition')
|
||||
self.assertEqual(new_msg_exp.get('parent_id'), self.msg_1.id, 'new messages expandable should have ancestor_id set to the thread header')
|
||||
# Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS)
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, limit=200, thread_level=0, parent_id=self.msg_1.id)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, thread_level=0, limit=200)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
# Test: other message in thread have been fetch
|
||||
self.assertEqual(set([self.msg_3.id, self.msg_5.id, self.msg_7.id]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
|
||||
|
||||
# Test: fetch a new thread, domain from expandable
|
||||
self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable')
|
||||
domain = new_threads_exp.get('domain', [])
|
||||
@@ -122,10 +128,12 @@ class TestMessageRead(TestMail):
|
||||
for condition in [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)]:
|
||||
self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter')
|
||||
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, limit=1, thread_level=1)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, thread_level=1, limit=1, child_limit=1)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
type_list = [msg.get('message_type') for msg in read_msg_list]
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(messages), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
|
||||
self.assertEqual(len(type_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
|
||||
self.assertEqual([self.msg_0.id], read_msg_ids, 'message_read after 2 More should return only 1 last message')
|
||||
|
||||
# ----------------------------------------
|
||||
@@ -133,28 +141,30 @@ class TestMessageRead(TestMail):
|
||||
# ----------------------------------------
|
||||
|
||||
# Do: read 2 lasts message, flat
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], limit=2, thread_level=0)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)], thread_level=0, limit=2)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable
|
||||
self.assertEqual(len(messages), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
|
||||
self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
|
||||
self.assertEqual(set([self.msg_9.id, self.msg_10.id]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages')
|
||||
self.assertFalse(messages[0].get('parent_id'), 'message_read flat should set the ancestor as False')
|
||||
self.assertFalse(messages[1].get('parent_id'), 'message_read flat should set the ancestor as False')
|
||||
self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False')
|
||||
self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False')
|
||||
# Data: get expandables
|
||||
new_threads_exp, new_msg_exp = None, None
|
||||
for msg in messages:
|
||||
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('max_limit'):
|
||||
new_threads_exp = msg
|
||||
new_msg_exp = None
|
||||
for msg in read_msg_list:
|
||||
if msg.get('message_type') == 'expandable':
|
||||
new_msg_exp = msg
|
||||
# Do: fetch new messages, domain from expandable
|
||||
self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
|
||||
domain = new_threads_exp.get('domain', [])
|
||||
self.assertIsNotNone(new_msg_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
|
||||
domain = new_msg_exp.get('domain', [])
|
||||
# Test: expandable, conditions in domain
|
||||
for condition in [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs.id)]:
|
||||
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
|
||||
# Do: message_read with domain, thread_level=0 (should be imposed by JS)
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, limit=20, thread_level=0)
|
||||
read_msg_ids = [msg.get('id') for msg in messages if msg.get('type') != 'expandable']
|
||||
messages = self.env['mail.message'].sudo(self.user_employee).message_read(domain=domain, thread_level=0, limit=20)
|
||||
read_msg_list = [msg for thread in messages['threads'] for msg in thread]
|
||||
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('message_type') != 'expandable']
|
||||
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
|
||||
self.assertEqual(len(messages), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
|
||||
self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
|
||||
self.assertEqual([self.msg_8.id, self.msg_7.id, self.msg_6.id, self.msg_5.id, self.msg_4.id, self.msg_3.id, self.msg_2.id, self.msg_1.id, self.msg_0.id], read_msg_ids,
|
||||
'message_read, More on flat, should return all remaning messages')
|
||||
|
||||
@@ -10,21 +10,60 @@ class TestTracking(TestMail):
|
||||
should probably be. """
|
||||
def _strip_string_spaces(body):
|
||||
return body.replace(' ', '').replace('\n', '')
|
||||
self.group_pigs.message_subscribe_users(user_ids=[self.user_employee.id])
|
||||
# Data: res.users.group, to test group_public_id automatic logging
|
||||
group_system_id = self.env['ir.model.data'].xmlid_to_res_id('base.group_system') or False
|
||||
|
||||
# Data: custom subtypes
|
||||
Subtype = self.env['mail.message.subtype']
|
||||
Data = self.env['ir.model.data']
|
||||
mt_private = Subtype.create({'name': 'private', 'description': 'Private public'})
|
||||
Data.create({'name': 'mt_private', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_private.id})
|
||||
mt_name_supername = Subtype.create({'name': 'name_supername', 'description': 'Supername name'})
|
||||
Data.create({'name': 'mt_name_supername', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_name_supername.id})
|
||||
mt_group_public_set = Subtype.create({'name': 'group_public_set', 'description': 'Group set'})
|
||||
Data.create({'name': 'mt_group_public_set', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public_set.id})
|
||||
mt_group_public = Subtype.create({'name': 'group_public', 'description': 'Group changed'})
|
||||
Data.create({'name': 'mt_group_public', 'model': 'mail.message.subtype', 'module': 'mail', 'res_id': mt_group_public.id})
|
||||
|
||||
group_system = self.env.ref('base.group_system')
|
||||
group_user = self.env.ref('base.group_user')
|
||||
self.group_pigs.message_subscribe_users(user_ids=[self.user_employee.id])
|
||||
|
||||
# mt_private: public field (tracked as onchange) set to 'private' (selection)
|
||||
mt_private = Subtype.create({
|
||||
'name': 'private',
|
||||
'description': 'Public field set to private'
|
||||
})
|
||||
Data.create({
|
||||
'name': 'mt_private',
|
||||
'model': 'mail.message.subtype',
|
||||
'module': 'mail',
|
||||
'res_id': mt_private.id
|
||||
})
|
||||
|
||||
# mt_name_supername: name field (tracked as always) set to 'supername' (char)
|
||||
mt_name_supername = Subtype.create({
|
||||
'name': 'name_supername',
|
||||
'description': 'Name field set to supername'
|
||||
})
|
||||
Data.create({
|
||||
'name': 'mt_name_supername',
|
||||
'model': 'mail.message.subtype',
|
||||
'module': 'mail',
|
||||
'res_id': mt_name_supername.id
|
||||
})
|
||||
|
||||
# mt_group_public_set: group_public field (tracked as onchange) set to something (m2o)
|
||||
mt_group_public_set = Subtype.create({
|
||||
'name': 'group_public_set',
|
||||
'description': 'Group_public field set'
|
||||
})
|
||||
Data.create({
|
||||
'name': 'mt_group_public_set',
|
||||
'model': 'mail.message.subtype',
|
||||
'module': 'mail',
|
||||
'res_id': mt_group_public_set.id
|
||||
})
|
||||
|
||||
# mt_group_public_set: group_public field (tracked as onchange) set to nothing (m2o)
|
||||
mt_group_public_unset = Subtype.create({
|
||||
'name': 'group_public_unset',
|
||||
'description': 'Group_public field unset'
|
||||
})
|
||||
Data.create({
|
||||
'name': 'mt_group_public_unset',
|
||||
'model': 'mail.message.subtype',
|
||||
'module': 'mail',
|
||||
'res_id': mt_group_public_unset.id
|
||||
})
|
||||
|
||||
def _track_subtype(self, cr, uid, ids, init_values, context=None):
|
||||
record = self.browse(cr, uid, ids[0], context=context)
|
||||
@@ -35,11 +74,15 @@ class TestTracking(TestMail):
|
||||
elif 'group_public_id' in init_values and record.group_public_id:
|
||||
return 'mail.mt_group_public_set'
|
||||
elif 'group_public_id' in init_values and not record.group_public_id:
|
||||
return 'mail.mt_group_public'
|
||||
return 'mail.mt_group_public_unset'
|
||||
return False
|
||||
self.registry('mail.group')._patch_method('_track_subtype', _track_subtype)
|
||||
|
||||
visibility = {'public': 'onchange', 'name': 'always', 'group_public_id': 'onchange'}
|
||||
visibility = {
|
||||
'public': 'onchange',
|
||||
'name': 'always',
|
||||
'group_public_id': 'onchange'
|
||||
}
|
||||
cls = type(self.env['mail.group'])
|
||||
for key in visibility:
|
||||
self.assertFalse(hasattr(getattr(cls, key), 'track_visibility'))
|
||||
@@ -51,44 +94,38 @@ class TestTracking(TestMail):
|
||||
del getattr(cls, key).track_visibility
|
||||
|
||||
# Test: change name -> always tracked, not related to a subtype
|
||||
self.group_pigs.sudo(self.user_employee).write({'public': 'public'})
|
||||
self.assertEqual(len(self.group_pigs.message_ids.ids), 1, 'tracked: a message should have been produced')
|
||||
# Test: first produced message: no subtype, name change tracked
|
||||
self.group_pigs.sudo(self.user_employee).write({'name': 'my_name'})
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 1)
|
||||
last_msg = self.group_pigs.message_ids[-1]
|
||||
self.assertFalse(last_msg.subtype_id.id, 'tracked: message should not have been linked to a subtype')
|
||||
self.assertIn(u"Selectedgroupofusers\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
|
||||
self.assertIn('Pigs', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
|
||||
# Test: change name as supername, public as private -> 2 subtypes
|
||||
self.assertFalse(last_msg.subtype_id)
|
||||
self.assertEqual(len(last_msg.tracking_value_ids), 1)
|
||||
self.assertEqual(last_msg.tracking_value_ids.field, 'name')
|
||||
self.assertEqual(last_msg.tracking_value_ids.field_desc, 'Name')
|
||||
self.assertEqual(last_msg.tracking_value_ids.old_value_char, 'Pigs')
|
||||
self.assertEqual(last_msg.tracking_value_ids.new_value_char, 'my_name')
|
||||
|
||||
# Test: change name as supername, public as private -> 1 subtype, private
|
||||
self.group_pigs.sudo(self.user_employee).write({'name': 'supername', 'public': 'private'})
|
||||
self.group_pigs.invalidate_cache()
|
||||
self.assertEqual(len(self.group_pigs.message_ids.ids), 2, 'tracked: one new message should have been produced')
|
||||
# Test: first produced message: mt_name_supername
|
||||
last_msg = self.group_pigs.message_ids[-2]
|
||||
self.assertEqual(last_msg.subtype_id.id, mt_private.id, 'tracked: message should be linked to mt_private subtype')
|
||||
self.assertIn('Private public', last_msg.body, 'tracked: message body does not hold the subtype description')
|
||||
self.assertIn(u'Pigs\u2192supername', _strip_string_spaces(last_msg.body), 'tracked: message body incorrect')
|
||||
self.assertEqual(len(self.group_pigs.message_ids.ids), 2)
|
||||
last_msg = self.group_pigs.message_ids[0]
|
||||
self.assertEqual(last_msg.subtype_id, mt_private)
|
||||
self.assertEqual(len(last_msg.tracking_value_ids), 2)
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('field')), set(['name', 'public']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('field_desc')), set(['Name', 'Privacy']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('old_value_char')), set(['my_name', 'Selected group of users']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('new_value_char')), set(['supername', 'Invited people only']))
|
||||
|
||||
# Test: change public as public, group_public_id -> 1 subtype, name always tracked
|
||||
self.group_pigs.sudo(self.user_employee).write({'public': 'public', 'group_public_id': group_system_id})
|
||||
# Test: change public as public, group_public_id -> 1 subtype, group public set
|
||||
self.group_pigs.sudo(self.user_employee).write({'public': 'public', 'group_public_id': group_system.id})
|
||||
self.group_pigs.invalidate_cache()
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 3, 'tracked: one message should have been produced')
|
||||
# Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
|
||||
last_msg = self.group_pigs.message_ids[-3]
|
||||
self.assertEqual(last_msg.subtype_id, mt_group_public_set, 'tracked: message should be linked to mt_group_public_set_id')
|
||||
self.assertIn('Group set', last_msg.body, 'tracked: message body does not hold the subtype description')
|
||||
self.assertIn(u"Invitedpeopleonly\u2192Everyone", _strip_string_spaces(last_msg.body), 'tracked: message body does not hold changed tracked field')
|
||||
self.assertIn(u'HumanResources/Employee\u2192Administration/Settings', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
|
||||
|
||||
# Test: change group_public_id to False -> 1 subtype, name always tracked
|
||||
self.group_pigs.sudo(self.user_employee).write({'group_public_id': False})
|
||||
self.group_pigs.invalidate_cache()
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: one message should have been produced')
|
||||
# Test: first produced message: mt_group_public_set_id, with name always tracked, public tracked on change
|
||||
last_msg = self.group_pigs.message_ids[-4]
|
||||
self.assertEqual(last_msg.subtype_id, mt_group_public, 'tracked: message should be linked to mt_group_public_id')
|
||||
self.assertIn('Group changed', last_msg.body, 'tracked: message body does not hold the subtype description')
|
||||
self.assertIn(u'Administration/Settings\u2192', _strip_string_spaces(last_msg.body), 'tracked: message body does not hold always tracked field')
|
||||
# Test: change not tracked field, no tracking message
|
||||
self.group_pigs.sudo(self.user_employee).write({'description': 'Dummy'})
|
||||
self.group_pigs.invalidate_cache()
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 4, 'tracked: No message should have been produced')
|
||||
self.assertEqual(len(self.group_pigs.message_ids), 3)
|
||||
last_msg = self.group_pigs.message_ids[0]
|
||||
self.assertEqual(last_msg.subtype_id, mt_group_public_set)
|
||||
self.assertEqual(len(last_msg.tracking_value_ids), 2)
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('field')), set(['group_public_id', 'public']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('field_desc')), set(['Authorized Group', 'Privacy']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('old_value_char')), set([group_user.name_get()[0][1], 'Invited people only']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('new_value_char')), set([group_system.name_get()[0][1], 'Everyone']))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('old_value_integer')), set([0, group_user.id]))
|
||||
self.assertEqual(set(last_msg.tracking_value_ids.mapped('new_value_integer')), set([0, group_system.id]))
|
||||
|
||||
@@ -2,20 +2,11 @@
|
||||
<openerp>
|
||||
<data>
|
||||
<!-- this record will be dynamically adapted to each new menu created -->
|
||||
<record id="action_mail_group_feeds" model="ir.actions.client">
|
||||
<record model="ir.actions.act_window" id="action_mail_group_feeds">
|
||||
<field name="name">Discussion Group</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context">{
|
||||
}</field>
|
||||
<field name="params">{
|
||||
'read_action': 'read'
|
||||
}</field>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
No message in this group.
|
||||
</p>
|
||||
</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">timeline</field>
|
||||
</record>
|
||||
|
||||
<!-- Group Kanban View !-->
|
||||
|
||||
@@ -126,7 +126,291 @@
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="mail_message_view_timeline">
|
||||
<field name="name">mail.message.view.timeline.</field>
|
||||
<field name="model">mail.message</field>
|
||||
<field name="arch" type="xml">
|
||||
<timeline string="Mail Timeline">
|
||||
<field name="date"/>
|
||||
<field name="author_id"/>
|
||||
<field name="subject"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
<field name="body"/>
|
||||
<field name="attachment_ids"/>
|
||||
<field name="tracking_value_ids"/>
|
||||
<field name="subtype_id"/>
|
||||
<field name="message_type"/>
|
||||
<field name="vote_user_ids"/>
|
||||
<field name="to_read"/>
|
||||
<field name="starred"/>
|
||||
<templates>
|
||||
<t t-name="timeline-message">
|
||||
<div t-attf-class="row o_timeline_msg o_timeline_msg_#{record.message_type}
|
||||
#{record.subtype ? '' : 'o_timeline_msg_nobody'}">
|
||||
<t t-if="record.message_type == 'expandable'">
|
||||
<div class="col-md-2"> </div>
|
||||
<span> - - - - - - - - - - - See more messages - - - - - - - - - - - </span>
|
||||
</t>
|
||||
|
||||
<t t-if="record.message_type != 'expandable'">
|
||||
<div class="col-md-2">
|
||||
<div class="o_timeline_msg_date">
|
||||
<span> <t t-raw="record.date"/> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-10 o_timeline_msg_message">
|
||||
<div class='o_timeline_msg_left'> <!-- *** avatar *** -->
|
||||
<a t-if="record.options.show_link"
|
||||
t-attf-href="#model=res.partner&id=#{record.author_id[0]}"
|
||||
t-att-title="record.author_id[1]">
|
||||
<img class="o_timeline_msg_icon"
|
||||
t-att-src="record.author_avatar"/>
|
||||
</a>
|
||||
<img t-if="!record.options.show_link" class="o_timeline_msg_icon"
|
||||
t-att-src="record.author_avatar"/>
|
||||
</div> <!-- *** avatar *** -->
|
||||
|
||||
<div class="o_timeline_msg_center"> <!-- *** msg_center *** -->
|
||||
<div class="o_timeline_msg_content"> <!-- *** msg_content *** -->
|
||||
|
||||
<div class="o_timeline_header"> <!-- *** msg_header *** -->
|
||||
<!-- *** header when tracking values *** -->
|
||||
<div t-if="record.tracking_values" class="o_timeline_tracking_value_header">
|
||||
<span t-if="!record.subtype" class="text-muted o_timeline_no_subtype">
|
||||
Update </span>
|
||||
<span t-if="record.subtype" class="o_timeline_subtype">
|
||||
<t t-raw="record.subtype"/>
|
||||
</span>
|
||||
<span t-attf-class="text-muted #{!record.subtype ? 'o_timeline_no_subtype' : ''}">
|
||||
by </span>
|
||||
<span t-attf-class="#{!record.subtype ? 'text-muted o_timeline_no_subtype' : ''}">
|
||||
<a t-if="record.options.show_link"
|
||||
class="o_timeline_action_author"
|
||||
t-attf-href="#model=res.partner&id=#{record.author_id[0]}"
|
||||
t-att-data-partner="record.author_id[0]">
|
||||
<t t-raw="record.author_id[2]"/>
|
||||
</a>
|
||||
<a t-if="!record.options.show_link"
|
||||
class="o_timeline_action_author">
|
||||
<t t-raw="record.author_id[2]"/>
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- *** header when no tracking value *** -->
|
||||
<span t-if="!record.tracking_values" class="o_timeline_msg_header">
|
||||
<span t-if="record.author_id and !(record.message_type == 'notification')"
|
||||
class="text-muted">
|
||||
<t t-if="record.subtype and record.message_type == 'comment'"> Message </t>
|
||||
<t t-if="record.subtype and record.message_type == 'email'"> Email </t>
|
||||
<t t-if="!record.subtype"> Note </t>
|
||||
from
|
||||
<a t-if="record.options.show_link and record.author_id[0]"
|
||||
t-attf-href="#model=res.partner&id=#{record.author_id[0]}"
|
||||
t-att-data-partner="record.author_id[0]" t-att-data-record="record.record_name"
|
||||
class="o_timeline_action_author">
|
||||
<t t-raw="record.author_id[2]"/>
|
||||
</a>
|
||||
<a t-if="record.author_id and (!record.options.show_link or !record.author_id[0])"
|
||||
class="o_timeline_action_author">
|
||||
<t t-raw="record.author_id[2]"/>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
<span t-if="!record.options.readonly and !(record.message_type == 'notification')"
|
||||
class='oe_subtle oe_sep text-muted'>•</span>
|
||||
<span t-if="!record.options.readonly and !(record.message_type == 'notification')"
|
||||
class="o_timeline_vote" t-att-data-id="record.id">
|
||||
<span class="o_timeline_vote_count" t-att-data-id="record.id" t-if='record.vote_nb > 0'>
|
||||
<t t-esc='record.vote_nb'/>
|
||||
<i class="fa fa-thumbs-o-up" t-att-data-id="record.id"></i>
|
||||
</span>
|
||||
<t t-if="!record.has_voted">Like</t>
|
||||
<t t-if="record.has_voted">Unlike</t>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div t-attf-class="o_timeline_msg_icons #{record.tracking_values ?
|
||||
'o_timeline_msg_icons_track' : ''}"
|
||||
t-if="!record.options.readonly"> <!-- *** icons *** -->
|
||||
<span>
|
||||
<i t-if="record.options.show_read" title="Done"
|
||||
class="oe_read fa fa-check" t-att-data-id="record.id"/>
|
||||
<i t-if="record.is_favorite and record.options.show_unread" title="Set back to Todo"
|
||||
class="oe_unread fa fa-inbox" t-att-data-id="record.id"/>
|
||||
<i t-if="!record.is_favorite and record.options.show_unread" title="Move to Inbox"
|
||||
class="oe_unread fa fa-inbox" t-att-data-id="record.id"/>
|
||||
</span>
|
||||
<span>
|
||||
<i title="Mark as Todo" t-attf-class="oe_star #{record.is_favorite ? 'oe_starred':''} fa fa-star"
|
||||
t-att-data-id="record.id"/>
|
||||
</span>
|
||||
</div> <!-- *** icons *** -->
|
||||
</div> <!-- *** msg_header *** -->
|
||||
|
||||
<div t-attf-class="o_timeline_msg_body"> <!-- *** msg_body *** -->
|
||||
<t t-if="record.tracking_values">
|
||||
<ul class="o_timeline_tracking_value_list">
|
||||
<t t-foreach='record.tracking_value_ids' t-as='value'>
|
||||
<li>
|
||||
<t t-esc="value.changed_field"/>
|
||||
<span> : </span>
|
||||
<t t-if="value.old_value">
|
||||
<span class="o_timeline_tracking_value"> <t t-esc="value.old_value"/> </span>
|
||||
<span> <i class="fa fa-long-arrow-right"></i> </span>
|
||||
</t>
|
||||
<span class="o_timeline_tracking_value"> <t t-esc="value.new_value"/> </span>
|
||||
</li>
|
||||
</t>
|
||||
</ul>
|
||||
</t>
|
||||
<t t-if="!record.tracking_values">
|
||||
<t t-raw="record.body"/>
|
||||
<t t-if="!record.body and record.subject">
|
||||
<t t-raw="record.subject"/>
|
||||
</t>
|
||||
</t>
|
||||
</div> <!-- *** msg_body *** -->
|
||||
|
||||
<div class="o_timeline_msg_footer"> <!-- *** msg_footer *** -->
|
||||
<t t-if="record.attachments">
|
||||
<div class="o_timeline_msg_attachment_list">
|
||||
<t t-raw="record.attachments_content"/>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
<t t-if="(record.message_type == 'comment' or record.message_type == 'email') and
|
||||
(record.subtype and record.partners)">
|
||||
<div t-if="record.partners" class="o_timeline_partners_followers">
|
||||
<span class="text-muted">
|
||||
To </span>
|
||||
<span class="o_timeline_partners_list">
|
||||
<t t-foreach="record.partner_ids.slice(0, 3)" t-as="partner">
|
||||
<span t-attf-class="o_timeline_partner_follower">
|
||||
<a t-if="record.options.show_link"
|
||||
t-attf-href="#model=res.partner&id=#{partner[0]}"
|
||||
t-att-data-partner="partner[0]"
|
||||
class="o_timeline_action_author">
|
||||
<t t-esc="partner[1]"/>
|
||||
</a>
|
||||
<t t-if="!record.options.show_link" t-esc="partner[1]"/>
|
||||
</span>
|
||||
<t t-if="!partner_last">,</t>
|
||||
</t>
|
||||
<t t-if="record.partner_ids.length > 3">
|
||||
<span t-att-title="record.partners_str">
|
||||
and <t t-esc="record.partners_nb"/> more
|
||||
</span>
|
||||
</t>
|
||||
</span>
|
||||
</div>
|
||||
</t>
|
||||
</div> <!-- *** msg_footer *** -->
|
||||
|
||||
</div> <!-- *** msg_content *** -->
|
||||
</div> <!-- *** msg_center *** -->
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="timeline-thread">
|
||||
<t t-if="options.view_inbox">
|
||||
<div t-attf-class="o_timeline_thread_content o_timeline_parent_#{widget.thread_type}">
|
||||
<t t-if="widget.thread_type == 'expandable'">
|
||||
<div class="col-md-2"> </div>
|
||||
<span> - - - - - - - - - - - See more messages - - - - - - - - - - - </span>
|
||||
</t>
|
||||
|
||||
<t t-if="widget.thread_type != 'expandable'">
|
||||
<span class="col-md-2">
|
||||
<t t-if="options.is_folded">
|
||||
<div class="o_timeline_msg_date">
|
||||
<span> <t t-raw="record.date"/> </span>
|
||||
</div>
|
||||
</t>
|
||||
</span>
|
||||
|
||||
<div class="o_timeline_parent_message">
|
||||
|
||||
<div class="o_timeline_msg_icons" t-if="!options.readonly"> <!-- *** read *** -->
|
||||
<i t-if="options.show_read_unread_button" title="Done" class="oe_read fa fa-check"/>
|
||||
</div> <!-- *** read *** -->
|
||||
|
||||
<span t-attf-class="o_timeline_parent_subject #{widget.model and widget.res_id
|
||||
and options.show_link ? 'o_timeline_small_subject' : ''}"> <!-- *** subject *** -->
|
||||
<span class="o_timeline_subject">
|
||||
<t t-if="widget.res_id and widget.subject">
|
||||
<t t-raw="widget.record_name"/> : <t t-raw="widget.subject"/>
|
||||
</t>
|
||||
<t t-if="widget.res_id and !widget.subject">
|
||||
<t t-raw="widget.record_name"/>
|
||||
</t>
|
||||
<t t-if="!widget.res_id and widget.subject">
|
||||
<t t-raw="widget.subject"/>
|
||||
</t>
|
||||
<t t-if="!widget.res_id and !widget.subject">
|
||||
No subject
|
||||
</t>
|
||||
</span>
|
||||
</span> <!-- *** subject *** -->
|
||||
|
||||
<span class="o_timeline_go_to_doc" t-if="record.res_id and record.model and options.show_link"> <!-- *** Go to doc *** -->
|
||||
<a t-attf-href="#action=mail.action_mail_redirect&model=#{record.model}&res_id=#{record.res_id}">
|
||||
<i class="fa fa-arrow-circle-right" title="Go to document"></i>
|
||||
</a>
|
||||
</span> <!-- *** Go to doc *** -->
|
||||
|
||||
<span class="o_timeline_parent_avatar"> <!-- *** avatar *** -->
|
||||
<a t-if="options.show_link"
|
||||
t-attf-href="#model=res.partner&id=#{record.author_id[0]}"
|
||||
t-att-title="record.author_id[1]">
|
||||
<img class="o_timeline_msg_icon"
|
||||
t-att-src="record.author_avatar"/>
|
||||
</a>
|
||||
<img t-if="!options.show_link" class="o_timeline_msg_icon"
|
||||
t-att-src="record.author_avatar"/>
|
||||
</span> <!-- *** avatar *** -->
|
||||
|
||||
<span class="o_timeline_parent_author"> <!-- *** author *** -->
|
||||
<a t-if="options.show_link and record.author_id[0]"
|
||||
t-attf-href="#model=res.partner&id=#{record.author_id[0]}"
|
||||
t-att-data-partner="record.author_id[0]" class="o_timeline_action_author"
|
||||
title="Last sender">
|
||||
<t t-raw="record.author_id[2]"/>
|
||||
</a>
|
||||
<a t-if="record.author_id and (!options.show_link or !record.author_id[0])"
|
||||
class="o_timeline_action_author"
|
||||
title="Last sender">
|
||||
<t t-raw="record.author_id[2]"/>
|
||||
</a>
|
||||
</span> <!-- *** author *** -->
|
||||
|
||||
<span class="o_timeline_msg_icons" t-if="!options.readonly"> <!-- *** repy *** -->
|
||||
<i t-if="options.show_reply_button" title="Reply" class="oe_reply fa fa-reply"/>
|
||||
</span> <!-- *** reply *** -->
|
||||
|
||||
<span class="o_timeline_parent_followers" t-if="widget.nb_followers > 0"> <!-- *** followers *** -->
|
||||
<span title="Number of followers"> <t t-raw="widget.nb_followers"/> </span>
|
||||
<i class="oe_follwrs fa fa-users"></i>
|
||||
</span> <!-- *** followers *** -->
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="!options.is_folded">
|
||||
<t t-foreach="widget.messages" t-as="message">
|
||||
<t t-call="timeline-message">
|
||||
<t t-set="record" t-value="message"/>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</templates>
|
||||
</timeline>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
||||
@@ -1,168 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="action_mail_inbox_feeds" model="ir.actions.client">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'needaction_menu_ref': ['mail.mail_tomefeeds', 'mail.mail_starfeeds']
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('to_read', '=', True),
|
||||
('starred', '=', False),
|
||||
],
|
||||
'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'read_action': 'read',
|
||||
'show_compose_message': False
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
<b>Good Job!</b> Your inbox is empty.
|
||||
</p><p>
|
||||
Your inbox contains private messages or emails sent to you
|
||||
as well as information related to documents or people you
|
||||
follow.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<data>
|
||||
|
||||
<menuitem id="mail.mail_feeds_main" name="Chat" icon="fa-envelope"
|
||||
groups="base.group_user" sequence="1"/>
|
||||
|
||||
<record id="action_mail_to_me_feeds" model="ir.actions.client">
|
||||
<field name="name">To: me</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'search_default_message_unread': True,
|
||||
'needaction_menu_ref': ['mail.mail_starfeeds', 'mail.mail_inboxfeeds']
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('partner_ids.user_ids', 'in', [uid])
|
||||
],
|
||||
'view_mailbox': True,
|
||||
'read_action': 'read',
|
||||
'show_compose_message': False
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
<b>No private message.</b>
|
||||
</p><p>
|
||||
This list contains messages sent to you.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<record model="ir.actions.act_window" id="action_mail_inbox_feeds">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">timeline</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'options' : {'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'show_write_to_followers': False}
|
||||
}</field>
|
||||
<field name="domain">[
|
||||
'|',
|
||||
('notification_ids.partner_id.user_ids', 'in', [uid]),
|
||||
('author_id.user_ids', 'in', [uid]),
|
||||
]</field>
|
||||
</record>
|
||||
|
||||
<record id="action_mail_star_feeds" model="ir.actions.client">
|
||||
<field name="name">To-do</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'search_default_message_unread': True
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('starred', '=', True),
|
||||
],
|
||||
'view_mailbox': True,
|
||||
'read_action': 'read',
|
||||
'compose_as_todo': True,
|
||||
'show_compose_message': False
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
<b>No todo.</b>
|
||||
</p><p>
|
||||
When you process messages in your inbox, you can mark some
|
||||
as <i>todo</i>. From this menu, you can process all your todo.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem id="mail.mail_inbox" name="Inbox" action="action_mail_inbox_feeds"
|
||||
parent="mail.mail_feeds_main" groups="base.group_user" sequence="1"/>
|
||||
|
||||
<record id="action_mail_redirect" model="ir.actions.server">
|
||||
<field name="name">Mail Redirection (Document / Inbox)</field>
|
||||
<field name="condition">True</field>
|
||||
<field name="model_id" ref="model_mail_thread"/>
|
||||
<field name="code">action = pool.get('mail.thread').message_redirect_action(cr, uid, context)</field>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
</record>
|
||||
|
||||
<record id="action_mail_archives_feeds" model="ir.actions.client">
|
||||
<field name="name">Archives</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'needaction_menu_ref': ['mail.mail_tomefeeds', 'mail.mail_starfeeds', 'mail.mail_inboxfeeds']
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
'|',
|
||||
('notification_ids.partner_id.user_ids', 'in', [uid]),
|
||||
('author_id.user_ids', 'in', [uid]),
|
||||
],
|
||||
'view_mailbox': True,
|
||||
'show_compose_message': False
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
No message found and no message sent yet.
|
||||
</p><p>
|
||||
Click on the top-right icon to compose a message. This
|
||||
message will be sent by email if it's an internal contact.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_mail_redirect" model="ir.actions.server">
|
||||
<field name="name">Mail Redirection (Document / Inbox)</field>
|
||||
<field name="condition">True</field>
|
||||
<field name="model_id" ref="model_mail_thread"/>
|
||||
<field name="code">action = pool.get('mail.thread').message_redirect_action(cr, uid, context)</field>
|
||||
<field name="state">code</field>
|
||||
<field name="type">ir.actions.server</field>
|
||||
</record>
|
||||
|
||||
<!-- MENU -->
|
||||
|
||||
<!-- Top menu item -->
|
||||
<menuitem name="Chat"
|
||||
id="mail.mail_feeds_main"
|
||||
groups="base.group_user"
|
||||
icon="fa-envelope"
|
||||
sequence="1"/>
|
||||
|
||||
<!-- Left-side menu: Feeds -->
|
||||
<menuitem id="mail.mail_inbox" name="Inbox" parent="mail.mail_feeds_main" groups="base.group_user" sequence="1" action="action_mail_inbox_feeds"/>
|
||||
|
||||
<!-- <record id="mail_inboxfeeds" model="ir.ui.menu">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="sequence" eval="10"/>
|
||||
<field name="action" ref="action_mail_inbox_feeds"/>
|
||||
<field name="parent_id" ref="mail.mail_inbox"/>
|
||||
</record>
|
||||
-->
|
||||
<!-- <record id="mail_tomefeeds" model="ir.ui.menu">
|
||||
<field name="name">To: me</field>
|
||||
<field name="sequence" eval="11"/>
|
||||
<field name="action" ref="action_mail_to_me_feeds"/>
|
||||
<field name="parent_id" ref="mail.mail_feeds"/>
|
||||
</record>
|
||||
<record id="mail_starfeeds" model="ir.ui.menu">
|
||||
<field name="name">To-do</field>
|
||||
<field name="sequence" eval="14"/>
|
||||
<field name="action" ref="action_mail_star_feeds"/>
|
||||
<field name="parent_id" ref="mail.mail_feeds"/>
|
||||
</record>
|
||||
<record id="mail_archivesfeeds" model="ir.ui.menu">
|
||||
<field name="name">Archives</field>
|
||||
<field name="sequence" eval="16"/>
|
||||
<field name="action" ref="action_mail_archives_feeds"/>
|
||||
<field name="parent_id" ref="mail.mail_feeds"/>
|
||||
</record>
|
||||
-->
|
||||
</data>
|
||||
</openerp>
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -0,0 +1,67 @@
|
||||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_tracking_value_tree">
|
||||
<field name="name">mail.tracking.value.tree</field>
|
||||
<field name="model">mail.tracking.value</field>
|
||||
<field name="priority">12</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Tracking Value">
|
||||
<field name="field"/>
|
||||
<field name="old_value_integer"/>
|
||||
<field name="old_value_float"/>
|
||||
<field name="old_value_char"/>
|
||||
<field name="old_value_text"/>
|
||||
<field name="old_value_datetime"/>
|
||||
<field name="new_value_integer"/>
|
||||
<field name="new_value_float"/>
|
||||
<field name="new_value_char"/>
|
||||
<field name="new_value_text"/>
|
||||
<field name="new_value_datetime"/>
|
||||
<field name="mail_message_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_tracking_value_form">
|
||||
<field name="name">mail.tracking.value.form</field>
|
||||
<field name="model">mail.tracking.value</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Tracking Value">
|
||||
<sheet>
|
||||
<group>
|
||||
<group string='Change'>
|
||||
<field name="field"/>
|
||||
<field name="field_desc"/>
|
||||
<field name="field_type"/>
|
||||
<field name="old_value_integer"/>
|
||||
<field name="old_value_float"/>
|
||||
<field name="old_value_char"/>
|
||||
<field name="old_value_text"/>
|
||||
<field name="old_value_datetime"/>
|
||||
<field name="new_value_integer"/>
|
||||
<field name="new_value_float"/>
|
||||
<field name="new_value_char"/>
|
||||
<field name="new_value_text"/>
|
||||
<field name="new_value_datetime"/>
|
||||
</group>
|
||||
<group string='Related Message'>
|
||||
<field name="mail_message_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_view_mail_tracking_value">
|
||||
<field name="name">Tracking Values</field>
|
||||
<field name="res_model">mail.tracking.value</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
<menuitem name="Tracking Values" id="menu_mail_tracking_value" parent="base.menu_email" action="action_view_mail_tracking_value"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
@@ -4,7 +4,7 @@ odoo.define('mail_tip.mail_tip', function (require) {
|
||||
var mail = require('mail.mail');
|
||||
var core = require('web.core');
|
||||
|
||||
mail.Thread.include({
|
||||
mail.MailThread.include({
|
||||
message_fetch: function() {
|
||||
return this._super.apply(this, arguments).done(function() {
|
||||
core.bus.trigger('chatter_messages_fetched');
|
||||
|
||||
@@ -11,101 +11,29 @@
|
||||
|
||||
<menuitem name="Messaging" id="portal_messages" parent="portal_menu" sequence="10" groups="base.group_portal"/>
|
||||
|
||||
<record id="action_mail_inbox_feeds_portal" model="ir.actions.client">
|
||||
<record model="ir.actions.act_window" id="action_mail_inbox_feeds_portal">
|
||||
<field name="name">Inbox</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">timeline</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'options' : {'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'show_link': 'False',
|
||||
'show_write_to_followers': False}
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('to_read', '=', True),
|
||||
],
|
||||
'show_compose_message': False,
|
||||
'show_link': False,
|
||||
'view_mailbox': True,
|
||||
'view_inbox': True,
|
||||
'read_action': 'read'
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
<b>Good Job!</b> Your inbox is empty.
|
||||
</p><p>
|
||||
Your inbox contains private messages or emails sent to you
|
||||
as well as information related to documents or people you
|
||||
follow.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- TODO: remove me in 8.0 -->
|
||||
<record id="action_mail_star_feeds_portal" model="ir.actions.client">
|
||||
<field name="name">To-do</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="res_model">mail.message</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
'search_default_message_unread': True
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
('starred', '=', True),
|
||||
],
|
||||
'show_compose_message': False,
|
||||
'show_link': False,
|
||||
'view_mailbox': True,
|
||||
'read_action': 'read'
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
<b>No todo.</b>
|
||||
</p><p>
|
||||
When you process messages in your inbox, you can mark some
|
||||
as <i>todo</i>. From this menu, you can process all your todo.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
<menuitem name="To-do" id="portal_mail_starfeeds" parent="portal_messages"
|
||||
action="action_mail_star_feeds_portal" sequence="20" groups="base.group_no_one"/>
|
||||
<!-- end -->
|
||||
|
||||
<record id="action_mail_archives_feeds_portal" model="ir.actions.client">
|
||||
<field name="name">Archives</field>
|
||||
<field name="tag">mail.wall</field>
|
||||
<field name="context">{
|
||||
'default_model': 'res.users',
|
||||
'default_res_id': uid,
|
||||
'thread_model': 'res.partner',
|
||||
}</field>
|
||||
<field name="params" eval=""{
|
||||
'domain': [
|
||||
'|',
|
||||
('notification_ids.partner_id.user_ids', 'in', [uid]),
|
||||
('author_id.user_ids', 'in', [uid]),
|
||||
],
|
||||
'show_compose_message': False,
|
||||
'show_link': False,
|
||||
'view_mailbox': True,
|
||||
}""/>
|
||||
<field name="help" type="html">
|
||||
<p>
|
||||
No message found and no message sent yet.
|
||||
</p><p>
|
||||
Click on the top-right icon to compose a message. This
|
||||
message will be sent by email if it's an internal contact.
|
||||
</p>
|
||||
</field>
|
||||
<field name="domain">[
|
||||
'|',
|
||||
('notification_ids.partner_id.user_ids', 'in', [uid]),
|
||||
('author_id.user_ids', 'in', [uid]),
|
||||
]</field>
|
||||
</record>
|
||||
|
||||
<menuitem name="Inbox" id="portal_inbox" parent="portal_messages"
|
||||
action="action_mail_inbox_feeds_portal" sequence="10" groups="base.group_portal"/>
|
||||
<menuitem name="Archives" id="portal_mail_archivesfeeds" parent="portal_messages"
|
||||
action="action_mail_archives_feeds_portal" sequence="30" groups="base.group_portal"/>
|
||||
|
||||
<!--
|
||||
Create menu items that we'll leave empty for now - they'll be
|
||||
@@ -142,4 +70,4 @@
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
</openerp>
|
||||
@@ -91,7 +91,7 @@ class test_portal(TestMail):
|
||||
msg = group_pigs.message_post(body='My body', partner_ids=[self.user_employee.partner_id.id, self.user_portal.partner_id.id], type='comment', subtype='mail.mt_comment')
|
||||
# Chell has no read access to pigs -> should redirect to Portal Inbox
|
||||
action = self.env['mail.thread'].with_context({'params': {'message_id': msg.id}}).sudo(self.user_portal).message_redirect_action()
|
||||
self.assertEqual(action.get('type'), 'ir.actions.client',
|
||||
'URL redirection: action without parameters should redirect to client action Inbox')
|
||||
self.assertEqual(action.get('type'), 'ir.actions.act_window',
|
||||
'URL redirection: action without parameters should redirect to act_window Inbox')
|
||||
self.assertEqual(action.get('id'), port_act_id,
|
||||
'URL redirection: action without parameters should redirect to client action Inbox')
|
||||
'URL redirection: action without parameters should redirect to act_window Inbox')
|
||||
|
||||
@@ -93,7 +93,7 @@ Sidebar.include({
|
||||
* an action. We do this because 'invite' is based on the share
|
||||
* mechanism, and it tries to share an action.
|
||||
*/
|
||||
mail.RecordThread.include( {
|
||||
mail.TimelineRecordThread.include( {
|
||||
start: function() {
|
||||
var start_res = this._super.apply(this, arguments);
|
||||
if (has_action_id) {
|
||||
|
||||
@@ -1710,6 +1710,7 @@
|
||||
max-width: 892px;
|
||||
margin: 0 auto;
|
||||
padding: 16px 16px 48px;
|
||||
background-color: #f0eeee;
|
||||
}
|
||||
.openerp .oe_form div.oe_form_configuration p, .openerp .oe_form div.oe_form_configuration ul, .openerp .oe_form div.oe_form_configuration ol {
|
||||
color: #aaa;
|
||||
|
||||
@@ -1439,6 +1439,7 @@ $sheet-padding: 16px
|
||||
max-width: $sheet-max-width + 2* $sheet-padding
|
||||
margin: 0 auto
|
||||
padding: 16px 16px 48px
|
||||
background-color: #f0eeee
|
||||
div.oe_form_configuration
|
||||
p, ul, ol
|
||||
color: #aaa
|
||||
|
||||
@@ -192,6 +192,7 @@ class view(osv.osv):
|
||||
('form','Form'),
|
||||
('graph', 'Graph'),
|
||||
('pivot', 'Pivot'),
|
||||
('timeline', 'Timeline'),
|
||||
('calendar', 'Calendar'),
|
||||
('diagram','Diagram'),
|
||||
('gantt', 'Gantt'),
|
||||
|
||||
@@ -696,6 +696,20 @@
|
||||
</rng:element>
|
||||
</rng:define>
|
||||
|
||||
<rng:define name="timeline">
|
||||
<rng:element name="timeline">
|
||||
<rng:optional><rng:attribute name="string" /></rng:optional>
|
||||
<rng:zeroOrMore>
|
||||
<rng:ref name="field"/>
|
||||
</rng:zeroOrMore>
|
||||
<rng:element name="templates">
|
||||
<rng:oneOrMore>
|
||||
<rng:ref name="any"/>
|
||||
</rng:oneOrMore>
|
||||
</rng:element>
|
||||
</rng:element>
|
||||
</rng:define>
|
||||
|
||||
<rng:define name="button">
|
||||
<rng:element name="button">
|
||||
<rng:ref name="overload"/>
|
||||
@@ -796,6 +810,7 @@
|
||||
<rng:ref name="notebook" />
|
||||
<rng:ref name="graph" />
|
||||
<rng:ref name="pivot" />
|
||||
<rng:ref name="timeline" />
|
||||
<rng:ref name="calendar" />
|
||||
<rng:ref name="data" />
|
||||
<rng:ref name="gantt" />
|
||||
|
||||
Reference in New Issue
Block a user