[IMP] [FIX] website_forum: better karma management for votes
authorThibault Delavallée <tde@openerp.com>
Fri, 18 Apr 2014 13:29:05 +0000 (15:29 +0200)
committerThibault Delavallée <tde@openerp.com>
Fri, 18 Apr 2014 13:29:05 +0000 (15:29 +0200)
and answers. Also some fixes in answer tagging and edition closing.

bzr revid: tde@openerp.com-20140418132905-ja2rqcc2edqzu3jv

addons/website_forum/controllers/main.py
addons/website_forum/models/forum.py
addons/website_forum/models/res_users.py
addons/website_forum/static/src/js/website_forum.js
addons/website_forum/views/website_forum.xml

index 354aa52..4d91b36 100644 (file)
@@ -208,7 +208,7 @@ class WebsiteForum(http.Controller):
 
         values = self._prepare_forum_values(**post)
         values.update({
-            'post': question,
+            'question': question,
             'forum': forum,
             'reasons': reasons,
         })
@@ -283,15 +283,19 @@ class WebsiteForum(http.Controller):
     @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
     def post_toggle_correct(self, forum, post, **kwargs):
         cr, uid, context = request.cr, request.uid, request.context
+        if post.parent_id is False:
+            return request.redirect('/')
         if not request.session.uid:
             return {'error': 'anonymous_user'}
-        # if user have not access to accept answer then reise warning
-        if post.parent_id is False or post.parent_id.create_uid.id != uid:
+        user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
+        if post.parent_id.create_uid.id != uid:
             return {'error': 'own_post'}
+        if post.create_uid.id == user.id and user.karma < request.registry['forum.forum']._karma_answer_accept_own:
+            return {'error': 'not_enough_karma', 'karma': 20}
 
         # 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)
-        request.registry['forum.post'].write(cr, uid, [post.id, post.parent_id.id], {'is_correct': not post.is_correct}, context=context)
+        request.registry['forum.post'].write(cr, uid, [post.id], {'is_correct': not post.is_correct}, context=context)
         return not post.is_correct
 
     @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/delete', type='http', auth="user", multilang=True, website=True)
@@ -341,15 +345,15 @@ class WebsiteForum(http.Controller):
 
     @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/upvote', type='json', auth="public", multilang=True, website=True)
     def post_upvote(self, forum, post, **kwargs):
-        # check for karma and not self vote
         if not request.session.uid:
             return {'error': 'anonymous_user'}
         if request.uid == post.create_uid.id:
             return {'error': 'own_post'}
         user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
-        if user.karma <= 5:
-            return {'error': 'not_enough_karma', 'karma': 1}
-        return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=True, context=request.context)
+        if user.karma < request.registry['forum.forum']._karma_upvote:
+            return {'error': 'not_enough_karma', 'karma': request.registry['forum.forum']._karma_upvote}
+        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)
 
     @http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/downvote', type='json', auth="public", multilang=True, website=True)
     def post_downvote(self, forum, post, **kwargs):
@@ -358,9 +362,10 @@ class WebsiteForum(http.Controller):
         if request.uid == post.create_uid.id:
             return {'error': 'own_post'}
         user = request.registry['res.users'].browse(request.cr, SUPERUSER_ID, request.uid, context=request.context)
-        if user.karma <= 50:
-            return {'error': 'not_enough_karma', 'karma': 50}
-        return request.registry['forum.post'].vote(request.cr, request.uid, [post.id], upvote=False, context=request.context)
+        if user.karma < request.registry['forum.forum']._karma_downvote:
+            return {'error': 'not_enough_karma', 'karma': request.registry['forum.forum']._karma_downvote}
+        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)
 
     # User
     # --------------------------------------------------
index 031ae4b..88755e6 100644 (file)
@@ -9,9 +9,32 @@ from openerp.tools.translate import _
 
 
 class Forum(osv.Model):
+    """TDE TODO: set karma values for actions dynamic for a given forum"""
     _name = 'forum.forum'
     _description = 'Forums'
     _inherit = ['website.seo.metadata']
+    # karma values
+    _karma_upvote = 5  # done
+    _karma_downvote = 50  # done
+    _karma_answer_accept_own = 20  # done
+    _karma_answer_own_question_immediately = 50
+    _karma_editor_link_files = 30  # done
+    _karma_editor_clickable_link = 50
+    _karma_modo_retag = 75
+    _karma_modo_close_own = 100
+    _karma_modo_edit_all = 300
+    _karma_modo_unlink_comment = 400
+    _karma_modo_close_all = 900
+    _karma_modo_unlink_all = 1000
+    # 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),
@@ -97,6 +120,13 @@ class Post(osv.Model):
             res[post.id] = any(answer.create_uid.id == uid for answer in post.child_ids)
         return res
 
+    def _get_has_validated_answer(self, cr, uid, ids, field_name, arg, context=None):
+        res = dict.fromkeys(ids, False)
+        ans_ids = self.search(cr, uid, [('parent_id', 'in', ids), ('is_correct', '=', True)], context=context)
+        for answer in self.browse(cr, uid, ans_ids, context=context):
+            res[answer.parent_id.id] = True
+        return res
+
     def _is_self_reply(self, cr, uid, ids, field_name, arg, context=None):
         res = dict.fromkeys(ids, False)
         for post in self.browse(cr, uid, ids, context=context):
@@ -143,7 +173,8 @@ class Post(osv.Model):
             }),
         # hierarchy
         'parent_id': fields.many2one('forum.post', 'Question', ondelete='cascade'),
-        'self_reply': fields.function(_is_self_reply, 'Reply to own question', type='boolean',
+        'self_reply': fields.function(
+            _is_self_reply, 'Reply to own question', type='boolean',
             store={
                 'forum.post': (lambda self, cr, uid, ids, c={}: ids, ['parent_id', 'create_uid'], 10),
             }),
@@ -156,6 +187,12 @@ class Post(osv.Model):
         'uid_has_answered': fields.function(
             _get_uid_answered, string='Has Answered', type='boolean',
         ),
+        'has_validated_answer': fields.function(
+            _get_has_validated_answer, string='Has a Validated Answered', type='boolean',
+            store={
+                'forum.post': (_get_post_from_hierarchy, ['parent_id', 'child_ids', 'is_correct'], 10),
+            }
+        ),
         # closing
         'closed_reason_id': fields.many2one('forum.post.reason', 'Reason'),
         'closed_uid': fields.many2one('res.users', 'Closed by', select=1),
@@ -183,10 +220,18 @@ class Post(osv.Model):
             self.message_post(cr, uid, parent.id, subject=_('Re: %s') % parent.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], 2, context=context)
+            self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [uid], self.pool['forum.forum']._karma_gen_quest_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
+        if 'is_correct' in vals:
+            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)
         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:
@@ -198,11 +243,6 @@ class Post(osv.Model):
                     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)
-        # update karma of related user when any answer accepted
-        if 'correct' in vals:
-            for post in self.browse(cr, uid, ids, context=context):
-                karma_value = 15 if vals.get('correct') else -15
-                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, [post.create_uid.id], {'karma': karma_value}, context=context)
         return res
 
     def vote(self, cr, uid, ids, upvote=True, context=None):
@@ -251,16 +291,30 @@ class Vote(osv.Model):
 
     def create(self, cr, uid, vals, context=None):
         vote_id = super(Vote, self).create(cr, uid, vals, context=context)
-        karma_value = int(vals.get('vote', '1')) * 10
-        post = self.pool['forum.post'].browse(cr, uid, vals.get('post_id'), context=context)
-        self.pool['res.users'].add_karma(cr, SUPERUSER_ID, post.create_uid.id, karma_value, 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)
         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):
-                karma_value = (int(values.get('vote')) - int(vote.vote)) * 10
-                self.pool['res.users'].add_karma(cr, SUPERUSER_ID, vote.post_id.create_uid.id, karma_value, 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)
+                else:
+                    karma_value = _get_karma_value(vote.vote, values['vote'], Forum._karma_gen_upvote_quest, Forum._karma_gen_downvote_quest)
+                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 58d43a5..f3632f3 100644 (file)
@@ -32,8 +32,6 @@ class Users(osv.Model):
     }
 
     def add_karma(self, cr, uid, ids, karma, context=None):
-        if isinstance(ids, (int, long)):
-            ids = [ids]
         for user in self.browse(cr, uid, ids, context=context):
             self.write(cr, uid, [user.id], {'karma': user.karma + karma}, context=context)
         return True
index 2c42874..3eda8a2 100644 (file)
@@ -57,7 +57,12 @@ $(document).ready(function () {
                 } else if (data['error'] == 'own_post'){
                     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, the user who asked this question can only accept the answer as correct.'+
+                        'Sorry, only the user who asked this question can accept the answer as correct.'+
+                        '</div>');
+                } else if (data['error'] == 'not_enough_karma') {
+                    var $warning = $('<div class="alert alert-danger alert-dismissable" id="vote_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, at least ' + data['karma'] + ' karma is required to accept your own answers'+
                         '</div>');
                 }
                 correct_answer_alert = $link.parent().find("#correct_answer_alert");
index 2d1787a..9dc6d8f 100644 (file)
         <div class="pull-left text-center">
             <div t-attf-class="box #{question.child_count and 'oe_green' or 'oe_grey'}">
                 <span t-esc="question.child_count"/>
-                <span t-if="question.is_correct" class="fa fa-2x fa-check-circle"/>
+                <span t-if="question.has_validated_answer" class="fa fa-2x fa-check-circle"/>
                 <div t-if="question.child_count&gt;1">Answers</div>
                 <div t-if="question.child_count&lt;=1">Answer</div>
             </div>
 <template id="close_question">
     <t t-call="website_forum.header">
         <h3 class=""><b>Close question</b></h3><br/>
-        <form t-attf-action="/forum/#{ slug(forum) }/question/#{slug(question)}/close" method="post" role="form">
-            <input name="post_id" t-att-value="post.id" type="hidden"/>
+        <form t-attf-action="/forum/#{slug(forum)}/question/#{slug(question)}/close" method="post" role="form">
+            <input name="post_id" t-att-value="question.id" type="hidden"/>
             <span class="pull-left">Close the question:</span>
-            <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(post) }" t-field="post.name"/>
+            <a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }" t-field="question.name"/>
             <div class="mt16">
                 <label class="col-md-2 control-label mb16" for="reason">Reasons:</label>
                 <div class="col-md-9 mb16">
                     <select class="form-control" name="reason">
                         <t t-foreach="reasons or []" t-as="reason">
-                            <option t-att-value="reason.id" t-att-selected="reason.id == post.closed_reason_id.id"><t t-esc="reason.name"/></option>
+                            <option t-att-value="reason.id" t-att-selected="reason.id == question.closed_reason_id.id"><t t-esc="reason.name"/></option>
                         </t>
                     </select>
                 </div>