[IMP] [FIX] website_forum: cleaned and improved the management of available actions...
authorThibault Delavallée <tde@openerp.com>
Tue, 1 Jul 2014 07:19:22 +0000 (09:19 +0200)
committerThibault Delavallée <tde@openerp.com>
Tue, 1 Jul 2014 08:29:37 +0000 (10:29 +0200)
addons/website_forum/controllers/main.py
addons/website_forum/data/forum_data.xml
addons/website_forum/models/forum.py
addons/website_forum/static/src/css/website_forum.css
addons/website_forum/static/src/css/website_forum.sass
addons/website_forum/static/src/js/website_forum.js
addons/website_forum/views/forum.xml
addons/website_forum/views/website_forum.xml

index 0d27f70..2d2e5f2 100644 (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)
index a3c9b47..61bb7ee 100644 (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>
index 0151270..900eb4e 100644 (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
index 54a7f47..d3af430 100644 (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;
 }
index 4a16f48..c14e91e 100644 (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
index 5db211a..aac2ba8 100644 (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) {
index d6a2484..a2d7c09 100644 (file)
                     <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">
index e4f9e0c..73fcbef 100644 (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>
                 <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>
 
 <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
                         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>
                             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>
                             </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>
                         <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>
                     <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>
                                 <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>
                         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"]}'