[IMP] [FIX] website_forum: cleaned and improved the management of available actions based on the user's karma. The karma levels are now stored on the forum itself, allowing customization. The various check of karma are done at model level and not at controller level anymore, leading to a more secure and easier to inherit behavior.

This commit is contained in:
Thibault Delavallée 2014-07-01 09:19:22 +02:00
parent 9132b1d306
commit 2a4ec367a9
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"]}'