Merge pull request #771 from odoo-dev/master-forum-imp-tde

[IMP] forum: better management of karma-based actions, using calculated fields and view simplification
This commit is contained in:
Thibault Delavallée 2014-07-01 11:35:33 +02:00
commit 691fc394af
8 changed files with 411 additions and 185 deletions

View File

@ -12,7 +12,6 @@ from openerp.addons.web.controllers.main import login_redirect
from openerp.addons.web.http import request
from openerp.addons.website.controllers.main import Website as controllers
from openerp.addons.website.models.website import slug
from openerp.tools import html2plaintext
controllers = controllers()
@ -33,20 +32,12 @@ class WebsiteForum(http.Controller):
return msg
def _prepare_forum_values(self, forum=None, **kwargs):
Forum = request.registry['forum.forum']
user = request.registry['res.users'].browse(request.cr, request.uid, request.uid, context=request.context)
values = {'user': user,
'is_public_user': user.id == request.website.user_id.id,
'notifications': self._get_notifications(),
'header': kwargs.get('header', dict()),
'searches': kwargs.get('searches', dict()),
'can_edit_own': True,
'can_edit_all': user.karma > Forum._karma_modo_edit_all,
'can_close_own': user.karma > Forum._karma_modo_close_own,
'can_close_all': user.karma > Forum._karma_modo_close_all,
'can_unlink_own': user.karma > Forum._karma_modo_unlink_own,
'can_unlink_all': user.karma > Forum._karma_modo_unlink_all,
'can_unlink_comment': user.karma > Forum._karma_modo_unlink_comment,
}
if forum:
values['forum'] = forum
@ -55,14 +46,6 @@ class WebsiteForum(http.Controller):
values.update(kwargs)
return values
def _has_enough_karma(self, karma_name, uid=None):
Forum = request.registry['forum.forum']
karma = hasattr(Forum, karma_name) and getattr(Forum, karma_name) or 0
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, uid or request.uid, context=request.context)
if user.karma < karma:
return False, {'error': 'not_enough_karma', 'karma': karma}
return True, {}
# Forum
# --------------------------------------------------
@ -244,10 +227,6 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/ask_for_close', type='http', auth="user", methods=['POST'], website=True)
def question_ask_for_close(self, forum, question, **post):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_close_own' or '_karma_modo_close_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
cr, uid, context = request.cr, request.uid, request.context
Reason = request.registry['forum.post.reason']
reason_ids = Reason.search(cr, uid, [], context=context)
@ -272,42 +251,21 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/close', type='http', auth="user", methods=['POST'], website=True)
def question_close(self, forum, question, **post):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_close_own' or '_karma_modo_close_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {
'state': 'close',
'closed_uid': request.uid,
'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
'closed_reason_id': int(post.get('reason_id', False)),
}, context=request.context)
request.registry['forum.post'].close(request.cr, request.uid, [question.id], reason_id=int(post.get('reason_id', False)), context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/reopen', type='http', auth="user", methods=['POST'], website=True)
def question_reopen(self, forum, question, **kwarg):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_close_own' or '_karma_modo_close_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'state': 'active'}, context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/delete', type='http', auth="user", methods=['POST'], website=True)
def question_delete(self, forum, question, **kwarg):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_unlink_own' or '_karma_modo_unlink_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'active': False}, context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/question/<model("forum.post"):question>/undelete', type='http', auth="user", methods=['POST'], website=True)
def question_undelete(self, forum, question, **kwarg):
check_res = self._has_enough_karma(question.create_uid.id == request.uid and '_karma_modo_unlink_own' or '_karma_modo_unlink_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
request.registry['forum.post'].write(request.cr, request.uid, [question.id], {'active': True}, context=request.context)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@ -349,11 +307,6 @@ class WebsiteForum(http.Controller):
return request.redirect('/')
if not request.session.uid:
return {'error': 'anonymous_user'}
user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
if post.parent_id.create_uid.id != uid and user.karma < request.registry['forum.forum']._karma_answer_accept_all:
return {'error': 'not_enough_karma', 'karma': request.registry['forum.forum']._karma_answer_accept_all}
if post.create_uid.id == user.id and user.karma < request.registry['forum.forum']._karma_answer_accept_own:
return {'error': 'not_enough_karma', 'karma': request.registry['forum.forum']._karma_answer_accept_own}
# set all answers to False, only one can be accepted
request.registry['forum.post'].write(cr, uid, [c.id for c in post.parent_id.child_ids], {'is_correct': False}, context=context)
@ -362,10 +315,6 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/delete', type='http', auth="user", methods=['POST'], website=True)
def post_delete(self, forum, post, **kwargs):
check_res = self._has_enough_karma(post.create_uid.id == request.uid and '_karma_modo_unlink_own' or '_karma_modo_unlink_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
question = post.parent_id
request.registry['forum.post'].unlink(request.cr, request.uid, [post.id], context=request.context)
if question:
@ -374,10 +323,6 @@ class WebsiteForum(http.Controller):
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/edit', type='http', auth="user", website=True)
def post_edit(self, forum, post, **kwargs):
check_res = self._has_enough_karma(post.create_uid.id == request.uid and '_karma_modo_edit_own' or '_karma_modo_edit_all')
if not check_res[0]:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
tags = ""
for tag_name in post.tag_ids:
tags += tag_name.name + ","
@ -419,9 +364,6 @@ class WebsiteForum(http.Controller):
return {'error': 'anonymous_user'}
if request.uid == post.create_uid.id:
return {'error': 'own_post'}
check_res = self._has_enough_karma('_karma_upvote')
if not check_res[0]:
return check_res[1]
upvote = True if not post.user_vote > 0 else False
return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=upvote, context=request.context)
@ -431,9 +373,6 @@ class WebsiteForum(http.Controller):
return {'error': 'anonymous_user'}
if request.uid == post.create_uid.id:
return {'error': 'own_post'}
check_res = self._has_enough_karma('_karma_downvote')
if not check_res[0]:
return check_res[1]
upvote = True if post.user_vote < 0 else False
return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=upvote, context=request.context)
@ -625,26 +564,25 @@ class WebsiteForum(http.Controller):
# Messaging
# --------------------------------------------------
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/comment/<model("mail.message"):comment>/convert_to_answer', type='http', auth="public", methods=['POST'], website=True)
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/comment/<model("mail.message"):comment>/convert_to_answer', type='http', auth="user", methods=['POST'], website=True)
def convert_comment_to_answer(self, forum, post, comment, **kwarg):
body = comment.body
request.registry['mail.message'].unlink(request.cr, request.uid, [comment.id], context=request.context)
new_post_id = request.registry['forum.post'].convert_comment_to_answer(request.cr, request.uid, comment.id, context=request.context)
if not new_post_id:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
post = request.registry['forum.post'].browse(request.cr, request.uid, new_post_id, context=request.context)
question = post.parent_id if post.parent_id else post
for answer in question.child_ids:
if answer.create_uid.id == request.uid:
return self.post_comment(forum, answer, comment=html2plaintext(body))
return self.post_new(forum, question, content=body)
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/convert_to_comment', type='http', auth="user", methods=['POST'], website=True)
def convert_answer_to_comment(self, forum, post, **kwarg):
values = {
'comment': html2plaintext(post.content),
}
question = post.parent_id
request.registry['forum.post'].unlink(request.cr, SUPERUSER_ID, [post.id], context=request.context)
return self.post_comment(forum, question, **values)
new_msg_id = request.registry['forum.post'].convert_answer_to_comment(request.cr, request.uid, post.id, context=request.context)
if not new_msg_id:
return werkzeug.utils.redirect("/forum/%s" % slug(forum))
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/comment/<model("mail.message"):comment>/delete', type='json', auth="user", website=True)
def delete_comment(self, forum, post, comment, **kwarg):
request.registry['mail.message'].unlink(request.cr, SUPERUSER_ID, [comment.id], context=request.context)
return True
if not request.session.uid:
return {'error': 'anonymous_user'}
return request.registry['forum.post'].unlink(request.cr, request.uid, post.id, comment.id, context=request.context)

View File

@ -44,7 +44,7 @@
<record id="mt_question_new" model="mail.message.subtype">
<field name="name">New Question</field>
<field name="res_model">forum.post</field>
<field name="default" eval="False"/>
<field name="default" eval="True"/>
<field name="description">New Question</field>
</record>
<record id="mt_question_edit" model="mail.message.subtype">
@ -57,7 +57,7 @@
<record id="mt_forum_answer_new" model="mail.message.subtype">
<field name="name">New Answer</field>
<field name="res_model">forum.forum</field>
<field name="default" eval="False"/>
<field name="default" eval="True"/>
<field name="hidden" eval="False"/>
<field name="parent_id" eval="ref('mt_answer_new')"/>
<field name="relation_field">forum_id</field>

View File

@ -1,50 +1,63 @@
# -*- coding: utf-8 -*-
from datetime import datetime
import openerp
from openerp import tools
from openerp import SUPERUSER_ID
from openerp.addons.website.models.website import slug
from openerp.osv import osv, fields
from openerp.tools import html2plaintext
from openerp.tools.translate import _
class KarmaError(ValueError):
""" Karma-related error, used for forum and posts. """
pass
class Forum(osv.Model):
"""TDE TODO: set karma values for actions dynamic for a given forum"""
_name = 'forum.forum'
_description = 'Forums'
_inherit = ['mail.thread', 'website.seo.metadata']
# karma values
_karma_upvote = 5 # done
_karma_downvote = 50 # done
_karma_answer_accept_own = 20 # done
_karma_answer_accept_own_now = 50
_karma_answer_accept_all = 500
_karma_editor_link_files = 30 # done
_karma_editor_clickable_link = 50
_karma_comment = 1
_karma_modo_retag = 75
_karma_modo_flag = 100
_karma_modo_flag_see_all = 300
_karma_modo_unlink_comment = 750
_karma_modo_edit_own = 1 # done
_karma_modo_edit_all = 300 # done
_karma_modo_close_own = 100 # done
_karma_modo_close_all = 900 # done
_karma_modo_unlink_own = 500 # done
_karma_modo_unlink_all = 1000 # done
# karma generation
_karma_gen_quest_new = 2 # done
_karma_gen_upvote_quest = 5 # done
_karma_gen_downvote_quest = -2 # done
_karma_gen_upvote_ans = 10 # done
_karma_gen_downvote_ans = -2 # done
_karma_gen_ans_accept = 2 # done
_karma_gen_ans_accepted = 15 # done
_karma_gen_ans_flagged = -100
_columns = {
'name': fields.char('Name', required=True, translate=True),
'faq': fields.html('Guidelines'),
'description': fields.html('Description'),
# karma generation
'karma_gen_question_new': fields.integer('Karma earned for new questions'),
'karma_gen_question_upvote': fields.integer('Karma earned for upvoting a question'),
'karma_gen_question_downvote': fields.integer('Karma earned for downvoting a question'),
'karma_gen_answer_upvote': fields.integer('Karma earned for upvoting an answer'),
'karma_gen_answer_downvote': fields.integer('Karma earned for downvoting an answer'),
'karma_gen_answer_accept': fields.integer('Karma earned for accepting an anwer'),
'karma_gen_answer_accepted': fields.integer('Karma earned for having an answer accepted'),
'karma_gen_answer_flagged': fields.integer('Karma earned for having an answer flagged'),
# karma-based actions
'karma_ask': fields.integer('Karma to ask a new question'),
'karma_answer': fields.integer('Karma to answer a question'),
'karma_edit_own': fields.integer('Karma to edit its own posts'),
'karma_edit_all': fields.integer('Karma to edit all posts'),
'karma_close_own': fields.integer('Karma to close its own posts'),
'karma_close_all': fields.integer('Karma to close all posts'),
'karma_unlink_own': fields.integer('Karma to delete its own posts'),
'karma_unlink_all': fields.integer('Karma to delete all posts'),
'karma_upvote': fields.integer('Karma to upvote'),
'karma_downvote': fields.integer('Karma to downvote'),
'karma_answer_accept_own': fields.integer('Karma to accept an answer on its own questions'),
'karma_answer_accept_all': fields.integer('Karma to accept an answers to all questions'),
'karma_editor_link_files': fields.integer('Karma for linking files (Editor)'),
'karma_editor_clickable_link': fields.integer('Karma for clickable links (Editor)'),
'karma_comment_own': fields.integer('Karma to comment its own posts'),
'karma_comment_all': fields.integer('Karma to comment all posts'),
'karma_comment_convert_own': fields.integer('Karma to convert its own answers to comments and vice versa'),
'karma_comment_convert_all': fields.integer('Karma to convert all answers to answers and vice versa'),
'karma_comment_unlink_own': fields.integer('Karma to unlink its own comments'),
'karma_comment_unlink_all': fields.integer('Karma to unlinnk all comments'),
'karma_retag': fields.integer('Karma to change question tags'),
'karma_flag': fields.integer('Karma to flag a post as offensive'),
}
def _get_default_faq(self, cr, uid, context=None):
@ -56,6 +69,36 @@ class Forum(osv.Model):
_defaults = {
'description': 'This community is for professionals and enthusiasts of our products and services.',
'faq': _get_default_faq,
'karma_gen_question_new': 2,
'karma_gen_question_upvote': 5,
'karma_gen_question_downvote': -2,
'karma_gen_answer_upvote': 10,
'karma_gen_answer_downvote': -2,
'karma_gen_answer_accept': 2,
'karma_gen_answer_accepted': 15,
'karma_gen_answer_flagged': -100,
'karma_ask': 0,
'karma_answer': 0,
'karma_edit_own': 1,
'karma_edit_all': 300,
'karma_close_own': 100,
'karma_close_all': 500,
'karma_unlink_own': 500,
'karma_unlink_all': 1000,
'karma_upvote': 5,
'karma_downvote': 50,
'karma_answer_accept_own': 20,
'karma_answer_accept_all': 500,
'karma_editor_link_files': 20,
'karma_editor_clickable_link': 20,
'karma_comment_own': 1,
'karma_comment_all': 1,
'karma_comment_convert_own': 50,
'karma_comment_convert_all': 500,
'karma_comment_unlink_own': 50,
'karma_comment_unlink_all': 500,
'karma_retag': 75,
'karma_flag': 500,
}
def create(self, cr, uid, values, context=None):
@ -139,6 +182,36 @@ class Post(osv.Model):
res[post.id] = post.parent_id and post.parent_id.create_uid == post.create_uid or False
return res
def _get_post_karma_rights(self, cr, uid, ids, field_name, arg, context=None):
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
res = dict.fromkeys(ids, False)
for post in self.browse(cr, uid, ids, context=context):
res[post.id] = {
'karma_ask': post.forum_id.karma_ask,
'karma_answer': post.forum_id.karma_answer,
'karma_accept': post.parent_id and post.parent_id.create_uid.id == uid and post.forum_id.karma_answer_accept_own or post.forum_id.karma_answer_accept_all,
'karma_edit': post.create_uid.id == uid and post.forum_id.karma_edit_own or post.forum_id.karma_edit_all,
'karma_close': post.create_uid.id == uid and post.forum_id.karma_close_own or post.forum_id.karma_close_all,
'karma_unlink': post.create_uid.id == uid and post.forum_id.karma_unlink_own or post.forum_id.karma_unlink_all,
'karma_upvote': post.forum_id.karma_upvote,
'karma_downvote': post.forum_id.karma_downvote,
'karma_comment': post.create_uid.id == uid and post.forum_id.karma_comment_own or post.forum_id.karma_comment_all,
'karma_comment_convert': post.create_uid.id == uid and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all,
}
res[post.id].update({
'can_ask': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_ask'],
'can_answer': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_answer'],
'can_accept': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_accept'],
'can_edit': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_edit'],
'can_close': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_close'],
'can_unlink': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_unlink'],
'can_upvote': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_upvote'],
'can_downvote': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_downvote'],
'can_comment': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_comment'],
'can_comment_convert': uid == SUPERUSER_ID or user.karma >= res[post.id]['karma_comment_convert'],
})
return res
_columns = {
'name': fields.char('Title'),
'forum_id': fields.many2one('forum.forum', 'Forum', required=True),
@ -151,7 +224,7 @@ class Post(osv.Model):
'website_message_ids': fields.one2many(
'mail.message', 'res_id',
domain=lambda self: [
'&', ('model', '=', self._name), ('type', '=', 'comment')
'&', ('model', '=', self._name), ('type', 'in', ['email', 'comment'])
],
string='Post Messages', help="Comments on forum post",
),
@ -203,6 +276,26 @@ class Post(osv.Model):
'closed_reason_id': fields.many2one('forum.post.reason', 'Reason'),
'closed_uid': fields.many2one('res.users', 'Closed by', select=1),
'closed_date': fields.datetime('Closed on', readonly=True),
# karma
'karma_ask': fields.function(_get_post_karma_rights, string='Karma to ask', type='integer', multi='_get_post_karma_rights'),
'karma_accept': fields.function(_get_post_karma_rights, string='Karma to accept this answer', type='integer', multi='_get_post_karma_rights'),
'karma_edit': fields.function(_get_post_karma_rights, string='Karma to edit', type='integer', multi='_get_post_karma_rights'),
'karma_close': fields.function(_get_post_karma_rights, string='Karma to close', type='integer', multi='_get_post_karma_rights'),
'karma_unlink': fields.function(_get_post_karma_rights, string='Karma to unlink', type='integer', multi='_get_post_karma_rights'),
'karma_upvote': fields.function(_get_post_karma_rights, string='Karma to upvote', type='integer', multi='_get_post_karma_rights'),
'karma_downvote': fields.function(_get_post_karma_rights, string='Karma to downvote', type='integer', multi='_get_post_karma_rights'),
'karma_comment': fields.function(_get_post_karma_rights, string='Karma to comment', type='integer', multi='_get_post_karma_rights'),
'karma_comment_convert': fields.function(_get_post_karma_rights, string='karma to convert as a comment', type='integer', multi='_get_post_karma_rights'),
# access rights
'can_ask': fields.function(_get_post_karma_rights, string='Can Ask', type='boolean', multi='_get_post_karma_rights'),
'can_accept': fields.function(_get_post_karma_rights, string='Can Accept', type='boolean', multi='_get_post_karma_rights'),
'can_edit': fields.function(_get_post_karma_rights, string='Can Edit', type='boolean', multi='_get_post_karma_rights'),
'can_close': fields.function(_get_post_karma_rights, string='Can Close', type='boolean', multi='_get_post_karma_rights'),
'can_unlink': fields.function(_get_post_karma_rights, string='Can Unlink', type='boolean', multi='_get_post_karma_rights'),
'can_upvote': fields.function(_get_post_karma_rights, string='Can Upvote', type='boolean', multi='_get_post_karma_rights'),
'can_downvote': fields.function(_get_post_karma_rights, string='Can Downvote', type='boolean', multi='_get_post_karma_rights'),
'can_comment': fields.function(_get_post_karma_rights, string='Can Comment', type='boolean', multi='_get_post_karma_rights'),
'can_comment_convert': fields.function(_get_post_karma_rights, string='Can Convert to Comment', type='boolean', multi='_get_post_karma_rights'),
}
_defaults = {
@ -219,41 +312,93 @@ class Post(osv.Model):
context = {}
create_context = dict(context, mail_create_nolog=True)
post_id = super(Post, self).create(cr, uid, vals, context=create_context)
# post message + subtype depending on parent_id
if vals.get("parent_id"):
parent = self.browse(cr, SUPERUSER_ID, vals['parent_id'], context=context)
body = _('<p><a href="forum/%s/question/%s">New Answer Posted</a></p>' % (slug(parent.forum_id), slug(parent)))
self.message_post(cr, uid, parent.id, subject=_('Re: %s') % parent.name, body=body, subtype='website_forum.mt_answer_new', context=context)
post = self.browse(cr, SUPERUSER_ID, post_id, context=context) # SUPERUSER_ID to avoid read access rights issues when creating
# karma-based access
if post.parent_id and not post.can_ask:
raise KarmaError('Not enough karma to create a new question')
elif not post.parent_id and not post.can_answer:
raise KarmaError('Not enough karma to answer to a question')
# messaging and chatter
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
if post.parent_id:
body = _(
'<p>A new answer for <i>%s</i> has been posted. <a href="%s/forum/%s/question/%s">Click here to access the post.</a></p>' %
(post.parent_id.name, base_url, slug(post.parent_id.forum_id), slug(post.parent_id))
)
self.message_post(cr, uid, post.parent_id.id, subject=_('Re: %s') % post.parent_id.name, body=body, subtype='website_forum.mt_answer_new', context=context)
else:
self.message_post(cr, uid, post_id, subject=vals.get('name', ''), body=_('New Question Created'), subtype='website_forum.mt_question_new', context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], self.pool['forum.forum']._karma_gen_quest_new, context=context)
body = _(
'<p>A new question <i>%s</i> has been asked on %s. <a href="%s/forum/%s/question/%s">Click here to access the question.</a></p>' %
(post.name, post.forum_id.name, base_url, slug(post.forum_id), slug(post))
)
self.message_post(cr, uid, post_id, subject=post.name, body=body, subtype='website_forum.mt_question_new', context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_question_new, context=context)
return post_id
def write(self, cr, uid, ids, vals, context=None):
Forum = self.pool['forum.forum']
# update karma when accepting/rejecting answers
posts = self.browse(cr, uid, ids, context=context)
if 'state' in vals:
if vals['state'] in ['active', 'close'] and any(not post.can_close for post in posts):
raise KarmaError('Not enough karma to close or reopen a post.')
if 'active' in vals:
if any(not post.can_unlink for post in posts):
raise KarmaError('Not enough karma to delete or reactivate a post')
if 'is_correct' in vals:
if any(not post.can_accept for post in posts):
raise KarmaError('Not enough karma to accept or refuse an answer')
# update karma except for self-acceptance
mult = 1 if vals['is_correct'] else -1
for post in self.browse(cr, uid, ids, context=context):
if vals['is_correct'] != post.is_correct:
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], Forum._karma_gen_ans_accepted * mult, context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], Forum._karma_gen_ans_accept * mult, context=context)
if vals['is_correct'] != post.is_correct and post.create_uid.id != uid:
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_answer_accepted * mult, context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_answer_accept * mult, context=context)
if any(key not in ['state', 'active', 'is_correct', 'closed_uid', 'closed_date', 'closed_reason_id'] for key in vals.keys()) and any(not post.can_edit for post in posts):
raise KarmaError('Not enough karma to edit a post.')
res = super(Post, self).write(cr, uid, ids, vals, context=context)
# if post content modify, notify followers
if 'content' in vals or 'name' in vals:
for post in self.browse(cr, uid, ids, context=context):
for post in posts:
if post.parent_id:
body, subtype = _('Answer Edited'), 'website_forum.mt_answer_edit'
obj_id = post.parent_id.id
else:
body, subtype = _('Question Edited'), 'website_forum.mt_question_edit'
obj_id = post.id
self.message_post(cr, uid, obj_id, body=_(body), subtype=subtype, context=context)
self.message_post(cr, uid, obj_id, body=body, subtype=subtype, context=context)
return res
def close(self, cr, uid, ids, reason_id, context=None):
if any(post.parent_id for post in self.browse(cr, uid, ids, context=context)):
return False
return self.pool['forum.post'].write(cr, uid, ids, {
'state': 'close',
'closed_uid': uid,
'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
'closed_reason_id': reason_id,
}, context=context)
def unlink(self, cr, uid, ids, context=None):
posts = self.browse(cr, uid, ids, context=context)
if any(not post.can_unlink for post in posts):
raise KarmaError('Not enough karma to unlink a post')
# if unlinking an answer with accepted answer: remove provided karma
for post in posts:
if post.is_correct:
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], post.forum_id.karma_gen_answer_accepted * -1, context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], post.forum_id.karma_gen_answer_accept * -1, context=context)
return super(Post, self).unlink(cr, uid, ids, context=context)
def vote(self, cr, uid, ids, upvote=True, context=None):
posts = self.browse(cr, uid, ids, context=context)
if upvote and any(not post.can_upvote for post in posts):
raise KarmaError('Not enough karma to upvote.')
elif not upvote and any(not post.can_downvote for post in posts):
raise KarmaError('Not enough karma to downvote.')
Vote = self.pool['forum.post.vote']
vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], context=context)
vote_ids = Vote.search(cr, uid, [('post_id', 'in', ids), ('user_id', '=', uid)], limit=1, context=context)
if vote_ids:
for vote in Vote.browse(cr, uid, vote_ids, context=context):
if upvote:
@ -267,6 +412,89 @@ class Post(osv.Model):
Vote.create(cr, uid, {'post_id': post_id, 'vote': new_vote}, context=context)
return {'vote_count': self._get_vote_count(cr, uid, ids, None, None, context=context)[ids[0]]}
def convert_answer_to_comment(self, cr, uid, id, context=None):
""" Tools to convert an answer (forum.post) to a comment (mail.message).
The original post is unlinked and a new comment is posted on the question
using the post create_uid as the comment's author. """
post = self.browse(cr, uid, id, context=context)
if not post.parent_id:
return False
# karma-based action check: use the post field that computed own/all value
if not post.can_comment_convert:
raise KarmaError('Not enough karma to convert an answer to a comment')
# post the message
question = post.parent_id
values = {
'author_id': post.create_uid.partner_id.id,
'body': html2plaintext(post.content),
'type': 'comment',
'subtype': 'mail.mt_comment',
'date': post.create_date,
}
message_id = self.pool['forum.post'].message_post(
cr, uid, question.id,
context=dict(context, mail_create_nosubcribe=True),
**values)
# unlink the original answer, using SUPERUSER_ID to avoid karma issues
self.pool['forum.post'].unlink(cr, SUPERUSER_ID, [post.id], context=context)
return message_id
def convert_comment_to_answer(self, cr, uid, message_id, default=None, context=None):
""" Tool to convert a comment (mail.message) into an answer (forum.post).
The original comment is unlinked and a new answer from the comment's author
is created. Nothing is done if the comment's author already answered the
question. """
comment = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
post = self.pool['forum.post'].browse(cr, uid, comment.res_id, context=context)
user = self.pool['res.users'].browse(cr, uid, uid, context=context)
if not comment.author_id or not comment.author_id.user_ids: # only comment posted by users can be converted
return False
# karma-based action check: must check the message's author to know if own / all
karma_convert = comment.author_id.id == user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all
can_convert = uid == SUPERUSER_ID or user.karma >= karma_convert
if not can_convert:
raise KarmaError('Not enough karma to convert a comment to an answer')
# check the message's author has not already an answer
question = post.parent_id if post.parent_id else post
post_create_uid = comment.author_id.user_ids[0]
if any(answer.create_uid.id == post_create_uid.id for answer in question.child_ids):
return False
# create the new post
post_values = {
'forum_id': question.forum_id.id,
'content': comment.body,
'parent_id': question.id,
}
# done with the author user to have create_uid correctly set
new_post_id = self.pool['forum.post'].create(cr, post_create_uid.id, post_values, context=context)
# delete comment
self.pool['mail.message'].unlink(cr, SUPERUSER_ID, [comment.id], context=context)
return new_post_id
def unlink_comment(self, cr, uid, id, message_id, context=None):
comment = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
post = self.pool['forum.post'].browse(cr, uid, id, context=context)
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context)
if not comment.model == 'forum.post' or not comment.res_id == id:
return False
# karma-based action check: must check the message's author to know if own or all
karma_unlink = comment.author_id.id == user.partner_id.id and post.forum_id.karma_comment_unlink_own or post.forum_id.karma_comment_unlink_all
can_unlink = uid == SUPERUSER_ID or user.karma >= karma_unlink
if not can_unlink:
raise KarmaError('Not enough karma to unlink a comment')
return self.pool['mail.message'].unlink(cr, SUPERUSER_ID, [message_id], context=context)
def set_viewed(self, cr, uid, ids, context=None):
cr.execute("""UPDATE forum_post SET views = views+1 WHERE id IN %s""", (tuple(ids),))
return True
@ -300,31 +528,31 @@ class Vote(osv.Model):
'vote': lambda *args: '1',
}
def _get_karma_value(self, old_vote, new_vote, up_karma, down_karma):
_karma_upd = {
'-1': {'-1': 0, '0': -1 * down_karma, '1': -1 * down_karma + up_karma},
'0': {'-1': 1 * down_karma, '0': 0, '1': up_karma},
'1': {'-1': -1 * up_karma + down_karma, '0': -1 * up_karma, '1': 0}
}
return _karma_upd[old_vote][new_vote]
def create(self, cr, uid, vals, context=None):
vote_id = super(Vote, self).create(cr, uid, vals, context=context)
if vals.get('vote', '1') == '1':
karma = self.pool['forum.forum']._karma_upvote
elif vals.get('vote', '1') == '-1':
karma = self.pool['forum.forum']._karma_downvote
post = self.pool['forum.post'].browse(cr, uid, vals['post_id'], context=context)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], karma, context=context)
vote = self.browse(cr, uid, vote_id, context=context)
if vote.post_id.parent_id:
karma_value = self._get_karma_value('0', vote.vote, vote.post_id.forum_id.karma_gen_answer_upvote, vote.post_id.forum_id.karma_gen_answer_downvote)
else:
karma_value = self._get_karma_value('0', vote.vote, vote.post_id.forum_id.karma_gen_question_upvote, vote.post_id.forum_id.karma_gen_question_downvote)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [vote.post_id.create_uid.id], karma_value, context=context)
return vote_id
def write(self, cr, uid, ids, values, context=None):
def _get_karma_value(old_vote, new_vote, up_karma, down_karma):
_karma_upd = {
'-1': {'-1': 0, '0': -1 * down_karma, '1': -1 * down_karma + up_karma},
'0': {'-1': 1 * down_karma, '0': 0, '1': up_karma},
'1': {'-1': -1 * up_karma + down_karma, '0': -1 * up_karma, '1': 0}
}
return _karma_upd[old_vote][new_vote]
if 'vote' in values:
Forum = self.pool['forum.forum']
for vote in self.browse(cr, uid, ids, context=context):
if vote.post_id.parent_id:
karma_value = _get_karma_value(vote.vote, values['vote'], Forum._karma_gen_upvote_ans, Forum._karma_gen_downvote_ans)
karma_value = self._get_karma_value(vote.vote, values['vote'], vote.post_id.forum_id.karma_gen_answer_upvote, vote.post_id.forum_id.karma_gen_answer_downvote)
else:
karma_value = _get_karma_value(vote.vote, values['vote'], Forum._karma_gen_upvote_quest, Forum._karma_gen_downvote_quest)
karma_value = self._get_karma_value(vote.vote, values['vote'], vote.post_id.forum_id.karma_gen_question_upvote, vote.post_id.forum_id.karma_gen_question_downvote)
self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [vote.post_id.create_uid.id], karma_value, context=context)
res = super(Vote, self).write(cr, uid, ids, values, context=context)
return res

View File

@ -104,6 +104,14 @@ a.no-decoration {
height: 1.2em !important;
}
.oe_forum_alert {
position: absolute;
margin-top: -30px;
margin-left: 90px;
width: 300px;
z-index: 9999;
}
button.btn-link.text-muted {
color: #999999;
}

View File

@ -84,5 +84,12 @@ a.no-decoration
font: 1.2em "Helvetica Neue", Helvetica, Arial, sans-serif !important
height: 1.2em !important
.oe_forum_alert
position: absolute
margin-top: -30px
margin-left: 90px
width: 300px
z-index: 9999
button.btn-link.text-muted
color: #999

View File

@ -1,28 +1,37 @@
$(document).ready(function () {
$('.vote_up ,.vote_down').on('click', function (ev) {
$('.karma_required').on('click', function (ev) {
var karma = $(ev.currentTarget).data('karma');
if (karma) {
ev.preventDefault();
var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="karma_alert">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
karma + ' karma is required to perform this action. You can earn karma by answering questions or having '+
'your answers upvoted by the community.</div>');
var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
if (vote_alert.length == 0) {
$(ev.currentTarget).parent().append($warning);
}
}
});
$('.vote_up,.vote_down').not('.karma_required').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
openerp.jsonRpc($link.data('href'), 'call', {})
.then(function (data) {
if (data['error']){
if (data['error'] == 'own_post'){
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="position:absolute; margin-top: -30px; margin-left: 90px;">'+
var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="vote_alert">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, you cannot vote for your own posts'+
'</div>');
} else if (data['error'] == 'anonymous_user'){
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="position:absolute; margin-top: -30px; margin-left: 90px;">'+
var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="vote_alert">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry you must be logged to vote'+
'</div>');
}
else if (data['error'] == 'not_enough_karma') {
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="max-width: 500px; position:absolute; margin-top: -30px; margin-left: 90px;">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, at least ' + data['karma'] + ' karma is required to vote. You can gain karma by answering questions and receiving votes.'+
'</div>');
}
vote_alert = $link.parent().find("#vote_alert");
if (vote_alert.length == 0) {
$link.parent().append($warning);
@ -44,21 +53,16 @@ $(document).ready(function () {
return true;
});
$('.accept_answer').on('click', function (ev) {
$('.accept_answer').not('.karma_required').on('click', function (ev) {
ev.preventDefault();
var $link = $(ev.currentTarget);
openerp.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
if (data['error']) {
if (data['error'] == 'anonymous_user'){
if (data['error'] == 'anonymous_user') {
var $warning = $('<div class="alert alert-danger alert-dismissable" id="correct_answer_alert" style="position:absolute; margin-top: -30px; margin-left: 90px;">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, anonymous users cannot choose correct answer.'+
'</div>');
} else if (data['error'] == 'not_enough_karma') {
var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_alert" style="max-width: 500px; position:absolute; margin-top: -30px; margin-left: 90px;">'+
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">&times;</button>'+
'Sorry, at least ' + data['karma'] + ' karma is required to accept this answer. You can gain karma by answering questions and receiving votes.'+
'</div>');
}
correct_answer_alert = $link.parent().find("#correct_answer_alert");
if (correct_answer_alert.length == 0) {

View File

@ -24,6 +24,27 @@
<sheet>
<group>
<field name="name"/>
<field name="karma_ask"/>
<field name="karma_edit_own"/>
<field name="karma_edit_all"/>
<field name="karma_close_own"/>
<field name="karma_close_all"/>
<field name="karma_unlink_own"/>
<field name="karma_unlink_all"/>
</group>
<group>
<field name="karma_upvote"/>
<field name="karma_downvote"/>
<field name="karma_answer_accept_own"/>
<field name="karma_answer_accept_all"/>
<field name="karma_editor_link_files"/>
<field name="karma_editor_clickable_link"/>
<field name="karma_comment_own"/>
<field name="karma_comment_all"/>
<field name="karma_comment_convert_own"/>
<field name="karma_comment_convert_all"/>
<field name="karma_comment_unlink_own"/>
<field name="karma_comment_unlink_all"/>
</group>
</sheet>
<div class="oe_chatter">

View File

@ -25,7 +25,7 @@
<!-- helper -->
<template id="link_button">
<form method="POST" t-att-action="url">
<button t-attf-class="fa btn-link #{classes}">
<button t-attf-class="fa btn-link #{classes} #{karma and 'karma_required text-muted' or ''}" t-attf-data-karma="#{karma}">
<t t-esc="label"/></button>
</form>
</template>
@ -163,7 +163,7 @@
<div t-if="question.child_count&lt;=1" class="subtitle">Answer</div>
</div>
</div>
<div class="col-md-10">
<div class="col-md-10 clearfix">
<div class="question-name">
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
<span t-if="not question.active"><b> [Deleted]</b></span>
@ -419,10 +419,12 @@
<template id="vote">
<div t-attf-class="box oe_grey">
<a t-attf-class="vote_up fa fa-thumbs-up no-decoration #{post.user_vote == 1 and 'text-success' or ''}"
<a t-attf-class="vote_up fa fa-thumbs-up no-decoration #{post.user_vote == 1 and 'text-success' or ''} #{((post.user_vote == 1 and not post.can_downvote) or not post.can_upvote) and 'karma_required' or ''}"
t-attf-data-karma="#{post.user_vote == 1 and post.karma_downvote or post.karma_upvote}"
t-attf-data-href="/forum/#{slug(post.forum_id)}/post/#{slug(post)}/upvote"/>
<span id="vote_count" t-esc="post.vote_count"/>
<a t-attf-class="vote_down fa fa-thumbs-down no-decoration #{post.user_vote == -1 and 'text-warning' or ''}"
<a t-attf-class="vote_down fa fa-thumbs-down no-decoration #{post.user_vote == -1 and 'text-warning' or ''} #{((post.user_vote == -1 and not post.can_upvote) or not post.can_downvote) and 'karma_required' or ''}"
t-attf-data-karma="#{post.user_vote == -1 and post.karma_uovote or post.karma_downvote}"
t-attf-data-href="/forum/#{slug(post.forum_id)}/post/#{slug(post)}/downvote"/>
<div t-if="vote_count &gt; 1" class="subtitle">
votes
@ -449,7 +451,7 @@
t-attf-class="favourite_question no-decoration fa fa-2x fa-star #{question.user_favourite and 'forum_favourite_question' or ''}"/>
</div>
</div>
<div style="col-md-10">
<div class="col-md-10">
<h1 class="mt0">
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
<span t-if="not question.active"><b> [Deleted]</b></span>
@ -466,11 +468,12 @@
style="display: inline-block;"/></b>
</t>
<b>on <span t-field="question.closed_date"/></b>
<div t-if="question.state == 'close' and user.karma&gt;=500" class="mt16 mb24 text-center">
<div class="mt16 mb24 text-center">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/question/' + slug(question) + '/reopen'"/>
<t t-set="label" t-value="'Reopen'"/>
<t t-set="classes" t-value="'fa-arrow-right'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
</t>
</div>
</div>
@ -483,45 +486,52 @@
</t>
</div>
<ul class="list-inline" id="options">
<li t-if="user.id == question.create_uid.id or user.karma&gt;=50">
<a style="cursor: pointer" data-toggle="collapse" class="text-muted fa fa-comment-o"
t-attf-data-target="#comment#{ question._name.replace('.','') + '-' + str(question.id) }">
<li>
<a style="cursor: pointer" data-toggle="collapse"
t-attf-class="fa fa-comment-o #{not question.can_comment and 'karma_required text-muted' or ''}"
t-attf-data-karma="#{not question.can_comment and question.karma_comment or 0}"
t-attf-data-target="#comment#{ question._name.replace('.','') + '-' + str(question.id) }">
Comment
</a>
</li>
<li t-if="question.state != 'close' and ((user.id == question.create_uid.id and can_close_own) or can_close_all)">
<li t-if="question.state != 'close'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/ask_for_close'"/>
<t t-set="label" t-value="'Close'"/>
<t t-set="classes" t-vaoue="'text-muted fa-times'"/>
<t t-set="classes" t-value="'fa-times'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
</t>
</li>
<li t-if="question.state == 'close' and ((user.id == question.create_uid.id and can_close_own) or can_close_all)">
<li t-if="question.state == 'close'">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/reopen'"/>
<t t-set="label" t-value="'Reopen'"/>
<t t-set="classes" t-value="'text-muted fa-undo'"/>
<t t-set="classes" t-value="'fa-undo'"/>
<t t-set="karma" t-value="not question.can_close and question.karma_close or 0"/>
</t>
</li>
<li t-if="(user.id == question.create_uid.id and can_edit_own) or can_edit_all">
<li>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/post/' + slug(question) + '/edit'"/>
<t t-set="label" t-value="'Edit'"/>
<t t-set="classes" t-value="'text-muted fa-edit'"/>
<t t-set="classes" t-value="'fa-edit'"/>
<t t-set="karma" t-value="not question.can_edit and question.karma_edit or 0"/>
</t>
</li>
<li t-if="question.active and ((user.id == question.create_uid.id and can_unlink_own) or can_unlink_all)">
<li t-if="question.active">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/delete'"/>
<t t-set="label" t-value="'Delete'"/>
<t t-set="classes" t-value="'text-muted fa-trash-o'"/>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
</t>
</li>
<li t-if="not question.active and ((user.id == question.create_uid.id and can_unlink_own) or can_unlink_all)">
<li t-if="not question.active">
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) +'/question/' + slug(question) + '/undelete'"/>
<t t-set="label" t-value="'Undelete'"/>
<t t-set="classes" t-value="'text-muted fa-trash-o'"/>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
</t>
</li>
</ul>
@ -565,7 +575,8 @@
<t t-set="post" t-value="answer"/>
</t>
<div class="text-muted mt8">
<a t-attf-class="accept_answer fa fa-2x fa-check-circle no-decoration #{answer.is_correct and 'oe_answer_true' or 'oe_answer_false'}"
<a t-attf-class="accept_answer fa fa-2x fa-check-circle no-decoration #{answer.is_correct and 'oe_answer_true' or 'oe_answer_false'} #{not answer.can_accept and 'karma_required' or ''}"
t-attf-data-karma="#{answer.karma_accept}"
t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct"/>
</div>
</div>
@ -573,26 +584,32 @@
<t t-raw="answer.content"/>
<div class="mt16">
<ul class="list-inline pull-right">
<li t-if="user.id == answer.create_uid.id or user.karma&gt;=50">
<a style="cursor: pointer" data-toggle="collapse" class="text-muted fa fa-comment-o"
t-attf-data-target="#comment#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Comment
<li>
<a t-attf-class="fa fa-comment-o #{not answer.can_comment and 'karma_required text-muted' or ''}"
t-attf-data-karma="#{not answer.can_comment and answer.karma_comment or 0}"
style="cursor: pointer" data-toggle="collapse"
t-attf-data-target="#comment#{ answer._name.replace('.','') + '-' + str(answer.id) }"> Comment
</a>
</li>
<li t-if="(user.id == answer.create_uid.id and can_edit_own) or can_edit_all">
<a class="text-muted fa fa-edit" t-attf-href="/forum/#{slug(forum)}/post/#{slug(answer)}/edit"> Edit</a>
<li>
<a t-attf-class="fa fa-edit #{not answer.can_edit and 'karma_required text-muted' or ''}"
t-attf-data-karma="#{not answer.can_edit and answer.karma_edit or 0}"
t-attf-href="/forum/#{slug(forum)}/post/#{slug(answer)}/edit"> Edit</a>
</li>
<li t-if="(user.id == answer.create_uid.id and can_unlink_own) or can_unlink_all">
<li>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/delete'"/>
<t t-set="label" t-value="'Delete'"/>
<t t-set="classes" t-value="'text-muted fa-trash-o'"/>
<t t-set="classes" t-value="'fa-trash-o'"/>
<t t-set="karma" t-value="not answer.can_unlink and answer.karma_unlink or 0"/>
</t>
</li>
<li t-if="user.id == answer.create_uid.id">
<li>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/convert_to_comment'"/>
<t t-set="label" t-value="'Convert as a comment'"/>
<t t-set="classes" t-value="'text-muted fa-magic'"/>
<t t-set="classes" t-value="'fa-magic'"/>
<t t-set="karma" t-value="not answer.can_comment_convert and answer.karma_comment_convert or 0"/>
</t>
</li>
</ul>
@ -610,7 +627,8 @@
<t t-set="post" t-value="answer"/>
</t>
<div class="text-muted mt8">
<a t-attf-class="accept_answer fa fa-2x fa-check-circle no-decoration #{answer.is_correct and 'oe_answer_true' or 'oe_answer_false'}"
<a t-attf-class="accept_answer fa fa-2x fa-check-circle no-decoration #{answer.is_correct and 'oe_answer_true' or 'oe_answer_false'} #{not answer.can_accept and 'karma_required' or ''}"
t-attf-data-karma="#{answer.karma_accept}"
t-attf-data-href="/forum/#{slug(question.forum_id)}/post/#{slug(answer)}/toggle_correct"/>
</div>
</div>
@ -641,10 +659,12 @@
t-attf-data-href="/forum/#{slug(forum)}/post/#{slug(object)}/comment/#{slug(message)}/delete"
class="close comment_delete">&amp;times;</button>
<span t-field="message.body"/>
<t t-set="required_karma" t-value="message.author_id.id == user.partner_id.id and object.forum_id.karma_comment_convert_own or object.forum_id.karma_comment_convert_all"/>
<t t-call="website_forum.link_button">
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(object) + '/comment/' + slug(message) + '/convert_to_answer'"/>
<t t-set="label" t-value="'Convert as an answer'"/>
<t t-set="classes" t-value="'text-muted fa-magic pull-right'"/>
<t t-set="karma" t-value="user.karma&lt;required_karma and required_karma or 0"/>
<t t-set="classes" t-value="'fa-magic pull-right'"/>
</t>
<a t-attf-href="/forum/#{slug(forum)}/partner/#{message.author_id.id}"
t-field="message.author_id" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}'