[MERGE] Merged vote system in Chatter.

Adds a +1 / -1 on displayed messages in Chatter. Those links allow to vote
or unvote for a given messages.
Purpose: being able to build a Chatter, using a ranking based on votes instgead
of date or ids. This will be used in a future vote-based FAQ.

bzr revid: tde@openerp.com-20120918152401-z6m38b1vllz4ckgi
This commit is contained in:
Thibault Delavallée 2012-09-18 17:24:01 +02:00
commit 389a7fc364
8 changed files with 169 additions and 3 deletions

View File

@ -25,6 +25,7 @@ import mail_message
import mail_mail
import mail_thread
import mail_group
import mail_vote
import res_partner
import res_users
import report

View File

@ -130,6 +130,8 @@ class mail_message(osv.Model):
'unread': fields.function(_get_unread, fnct_search=_search_unread,
type='boolean', string='Unread',
help='Functional field to search for unread messages linked to uid'),
'vote_user_ids': fields.many2many('res.users', 'mail_vote', 'message_id', 'user_id', string='Votes',
help='Users that voted for this message'),
}
def _needaction_domain_get(self, cr, uid, context=None):
@ -147,12 +149,35 @@ class mail_message(osv.Model):
'body': '',
}
#------------------------------------------------------
# Vote/Like
#------------------------------------------------------
def vote_toggle(self, cr, uid, ids, user_ids=None, context=None):
''' Toggles voting '''
if not user_ids:
user_ids = [uid]
for message in self.read(cr, uid, ids, ['vote_user_ids'], context=context):
for user_id in user_ids:
has_voted = user_id in message.get('vote_user_ids')
if not has_voted:
self.write(cr, uid, message.get('id'), {'vote_user_ids': [(4, user_id)]}, context=context)
else:
self.write(cr, uid, message.get('id'), {'vote_user_ids': [(3, user_id)]}, context=context)
return True
#------------------------------------------------------
# Message loading for web interface
#------------------------------------------------------
def _message_dict_get(self, cr, uid, msg, context=None):
""" Return a dict representation of the message browse record. """
has_voted = False
vote_ids = self.pool.get('res.users').name_get(cr, uid, [user.id for user in msg.vote_user_ids], context=context)
for vote in vote_ids:
if vote[0] == uid:
has_voted = True
break
attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, [x.id for x in msg.attachment_ids], context=context)]
author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0]
author_user_id = self.pool.get('res.users').name_get(cr, uid, [msg.author_id.user_ids[0].id], context=context)[0]
@ -171,6 +196,8 @@ class mail_message(osv.Model):
'author_user_id': author_user_id,
'partner_ids': partner_ids,
'child_ids': [],
'vote_user_ids': vote_ids,
'has_voted': has_voted
}
def message_read_tree_flatten(self, cr, uid, messages, current_level, level, context=None):

39
addons/mail/mail_vote.py Normal file
View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2012-Today OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv, fields
class mail_vote(osv.Model):
''' Mail vote feature allow users to like and unlike messages attached
to a document. This allows for example to build a ranking-based
displaying of messages, for FAQ. '''
_name = 'mail.vote'
_description = 'Mail Vote'
_columns = {
'message_id': fields.many2one('mail.message', 'Message', select=1,
ondelete='cascade', required=True),
'user_id': fields.many2one('res.users', 'User', select=1,
ondelete='cascade', required=True),
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -10,3 +10,4 @@ access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_manager,mail.mail,model_mail_mail,group_mail_manager,1,1,1,1
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
10 access_mail_alias_system mail.alias model_mail_alias base.group_system 1 1 1 1
11 access_mail_mail_user mail.mail model_mail_mail base.group_user 1 1 1 0
12 access_mail_mail_manager mail.mail model_mail_mail group_mail_manager 1 1 1 1
13 access_mail_vote_all mail.vote.all model_mail_vote 1 1 1 1

View File

@ -260,6 +260,31 @@
display: none;
}
/*--------------------------------------------------------------*/
/* mail.vote
/*--------------------------------------------------------------*/
.openerp .oe_mail_msg_content button.oe_mail_msg_vote {
height:21px;
width: 30px;
padding: 1px;
background: #8A89BA;
margin-top: -4px;
}
.openerp .oe_mail_msg_content button.oe_mail_msg_vote_true {
background:#DC5F59;
}
.openerp .oe_mail_msg_content button.oe_mail_msg_vote span {
color: white;
}
.openerp .oe_mail_msg_content span.oe_mail_vote_count{
color: #807FB4;
}
/* ------------------------------------------------------------ */
/* mail.compose.message form view & OpenERP hacks
/* ------------------------------------------------------------ */

View File

@ -291,6 +291,7 @@ openerp.mail = function(session) {
truncate_limit: options.truncate_limit || 250,
}
// datasets and internal vars
this.records = {};
this.ds_thread = new session.web.DataSetSearch(this, this.context.default_model);
this.ds_notification = new session.web.DataSetSearch(this, 'mail.notification');
this.ds_message = new session.web.DataSetSearch(this, 'mail.message');
@ -355,6 +356,8 @@ openerp.mail = function(session) {
'default_parent_id': parseInt(msg_id),
'default_content_subtype': 'html'} );
});
// event: click on 'Vote' button
this.$el.on('click', 'button.oe_mail_msg_vote', this.on_vote);
},
on_message_delete: function (event) {
@ -373,6 +376,16 @@ openerp.mail = function(session) {
return this.ds_notification.call('set_message_read', [parseInt(msg_id)]);
},
on_vote: function (event) {
event.stopPropagation();
var self = this;
var message_id = $(event.srcElement).parent().data().msg_id;
var vote_node = $(event.srcElement).parents('li').eq(0);
if (! message_id) { return false; }
return this.ds_message.call('vote_toggle', [[parseInt(message_id)]]).pipe(
self.toggle_vote(message_id, vote_node));
},
/**
* Override-hack of do_action: automatically reload the chatter.
* Normally it should be called only when clicking on 'Post/Send'
@ -492,6 +505,8 @@ openerp.mail = function(session) {
attach['url'] = mail.ChatterUtils.get_attachment_url(this.session, attach);
}
record.is_author = mail.ChatterUtils.is_author(this, record.author_user_id[0]);
// add to internal storage
this.records[record.id] = record;
// render, add the expand feature
var rendered = session.web.qweb.render('mail.thread.message', {'record': record, 'thread': this, 'options': this.options});
$(rendered).appendTo(this.$el.children('div.oe_mail_thread_display:first'));
@ -505,6 +520,23 @@ openerp.mail = function(session) {
});
},
// Render vote Display template.
toggle_vote: function (message_id, vote_node) {
var self = this;
var record = this.records[message_id];
if (record.has_voted) {
var idx = _.map(record.vote_user_ids, function (x) { return x[0]; }).indexOf(message_id);
record.vote_user_ids.splice(idx, 1);
}
else {
record.vote_user_ids.push([this.session.uid, 'You']);
}
record.has_voted = ! record.has_voted;
var vote_element = session.web.qweb.render('mail.thread.message.vote', {'record': record});
vote_node.empty();
vote_node.html(vote_element);
},
/** Display 'show more' button */
update_fetch_more: function (new_value) {
if (new_value) {

View File

@ -142,11 +142,9 @@
</li>
<li><a t-attf-href="#model=res.partner&amp;id=#{record.author_id[0]}"><t t-raw="record.author_id[1]"/></a></li>
<li><span t-att-title="record.date"><t t-raw="record.timerelative"/></span></li>
<t t-call="mail.thread.message.vote"/>
<li t-if="options.show_reply"><a class="oe_mail_msg_reply">Reply</a></li>
<li t-if="options.show_reply_by_email"><a class="oe_mail_msg_reply_by_email" t-attf-data-msg_id="{record.id}">Reply</a></li>
<!-- uncomment when merging vote
<li><a href="#">Like</a></li>
-->
<li t-if="record.attachment_ids.length > 0">
<a class="oe_mail_msg_view_attachments">
<t t-if="record.attachment_ids.length == 1">1 Attachment</t>
@ -180,4 +178,17 @@
</ul>
</t>
<!-- mail.thread.message.vote
Template used to display Like/Unlike in a mail.message
-->
<li t-name="mail.thread.message.vote">
<t t-if='record.vote_user_ids.length > 0'>
<span class="oe_left oe_mail_vote_count"><t t-esc="record.vote_user_ids.length"/> votes</span>
</t>
<button t-attf-class="oe_mail_msg_vote oe_mail_msg_vote_#{record.has_voted}" t-attf-data-msg_id="{record.id}">
<t t-if="! record.has_voted"><span>+1</span></t>
<t t-if="record.has_voted"><span>-1</span></t>
</button>
</li>
</template>

View File

@ -647,3 +647,33 @@ class test_mail(TestMailMockups):
msg1.refresh()
self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')
def test_60_vote(self):
""" Test designed for the vote/unvote feature. """
cr, uid = self.cr, self.uid
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
user_admin = self.res_users.browse(cr, uid, uid)
msg1 = group_pigs.message_post(body='My Body', subject='1')
msg1 = self.mail_message.browse(cr, uid, msg1)
# Create user Bert Tartopoils
user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert', 'login': 'bert'})
user_bert = self.res_users.browse(cr, uid, user_bert_id)
# Test: msg1 and msg2 have void vote_user_ids
self.assertFalse(msg1.vote_user_ids, 'newly created message msg1 has not void vote_user_ids')
# Do: Admin vote for msg1
self.mail_message.vote_toggle(cr, uid, [msg1.id])
msg1.refresh()
# Test: msg1 has Admin as voter
self.assertEqual(set(msg1.vote_user_ids), set([user_admin]), 'after voting, Admin is not the voter')
# Do: Bert vote for msg1
self.mail_message.vote_toggle(cr, uid, [msg1.id], [user_bert_id])
msg1.refresh()
# Test: msg1 has Admin and Bert as voters
self.assertEqual(set(msg1.vote_user_ids), set([user_admin, user_bert]), 'after voting, Admin and Bert are not the voters')
# Do: Admin unvote for msg1
self.mail_message.vote_toggle(cr, uid, [msg1.id])
msg1.refresh()
# Test: msg1 has Bert as voter
self.assertEqual(set(msg1.vote_user_ids), set([user_bert]), 'after unvoting for Admin, Bert is not the voter')