diff --git a/addons/mail/__init__.py b/addons/mail/__init__.py index da517ee17ad..b384613d174 100644 --- a/addons/mail/__init__.py +++ b/addons/mail/__init__.py @@ -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 diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 44ce76b94b0..1d5d85e36e0 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -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): diff --git a/addons/mail/mail_vote.py b/addons/mail/mail_vote.py new file mode 100644 index 00000000000..1e439fe0aeb --- /dev/null +++ b/addons/mail/mail_vote.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-Today OpenERP SA (). +# +# 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 +# +############################################################################## + +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: diff --git a/addons/mail/security/ir.model.access.csv b/addons/mail/security/ir.model.access.csv index dbc84c86ded..2ec74086a34 100644 --- a/addons/mail/security/ir.model.access.csv +++ b/addons/mail/security/ir.model.access.csv @@ -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 diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css index 48a178732b0..2f9b35f1c95 100644 --- a/addons/mail/static/src/css/mail.css +++ b/addons/mail/static/src/css/mail.css @@ -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 /* ------------------------------------------------------------ */ diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js index 71665bc69fb..57c777441e7 100644 --- a/addons/mail/static/src/js/mail.js +++ b/addons/mail/static/src/js/mail.js @@ -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) { diff --git a/addons/mail/static/src/xml/mail.xml b/addons/mail/static/src/xml/mail.xml index bf339f21f1c..e3586e723e4 100644 --- a/addons/mail/static/src/xml/mail.xml +++ b/addons/mail/static/src/xml/mail.xml @@ -142,11 +142,9 @@
  • +
  • Reply
  • Reply
  • -
  • 1 Attachment @@ -180,4 +178,17 @@ + +
  • + + votes + + +
  • + diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py index 1fab5dd1732..57a59e2595f 100644 --- a/addons/mail/tests/test_mail.py +++ b/addons/mail/tests/test_mail.py @@ -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')