diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index 0d27f70e486..2d2e5f24606 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -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//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//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//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//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//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//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//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//post//comment//convert_to_answer', type='http', auth="public", methods=['POST'], website=True) + @http.route('/forum//post//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//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//post//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) diff --git a/addons/website_forum/data/forum_data.xml b/addons/website_forum/data/forum_data.xml index a3c9b470537..61bb7ee0fb4 100644 --- a/addons/website_forum/data/forum_data.xml +++ b/addons/website_forum/data/forum_data.xml @@ -44,7 +44,7 @@ New Question forum.post - + New Question @@ -57,7 +57,7 @@ New Answer forum.forum - + forum_id diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py index 01512705be8..900eb4e0fe3 100644 --- a/addons/website_forum/models/forum.py +++ b/addons/website_forum/models/forum.py @@ -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 = _('

New Answer Posted

' % (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 = _( + '

A new answer for %s has been posted. Click here to access the post.

' % + (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 = _( + '

A new question %s has been asked on %s. Click here to access the question.

' % + (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 diff --git a/addons/website_forum/static/src/css/website_forum.css b/addons/website_forum/static/src/css/website_forum.css index 54a7f47380f..d3af4309b24 100644 --- a/addons/website_forum/static/src/css/website_forum.css +++ b/addons/website_forum/static/src/css/website_forum.css @@ -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; } diff --git a/addons/website_forum/static/src/css/website_forum.sass b/addons/website_forum/static/src/css/website_forum.sass index 4a16f48e77e..c14e91e2893 100644 --- a/addons/website_forum/static/src/css/website_forum.sass +++ b/addons/website_forum/static/src/css/website_forum.sass @@ -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 diff --git a/addons/website_forum/static/src/js/website_forum.js b/addons/website_forum/static/src/js/website_forum.js index 5db211ab9aa..aac2ba8f5bb 100644 --- a/addons/website_forum/static/src/js/website_forum.js +++ b/addons/website_forum/static/src/js/website_forum.js @@ -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 = $('
'+ + ''+ + karma + ' karma is required to perform this action. You can earn karma by answering questions or having '+ + 'your answers upvoted by the community.
'); + 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 = $('
'+ + var $warning = $('
'+ ''+ 'Sorry, you cannot vote for your own posts'+ '
'); } else if (data['error'] == 'anonymous_user'){ - var $warning = $('
'+ + var $warning = $('
'+ ''+ 'Sorry you must be logged to vote'+ '
'); } - else if (data['error'] == 'not_enough_karma') { - var $warning = $('
'+ - ''+ - 'Sorry, at least ' + data['karma'] + ' karma is required to vote. You can gain karma by answering questions and receiving votes.'+ - '
'); - } 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 = $('
'+ ''+ 'Sorry, anonymous users cannot choose correct answer.'+ '
'); - } else if (data['error'] == 'not_enough_karma') { - var $warning = $('
'+ - ''+ - 'Sorry, at least ' + data['karma'] + ' karma is required to accept this answer. You can gain karma by answering questions and receiving votes.'+ - '
'); } correct_answer_alert = $link.parent().find("#correct_answer_alert"); if (correct_answer_alert.length == 0) { diff --git a/addons/website_forum/views/forum.xml b/addons/website_forum/views/forum.xml index d6a2484bfd1..a2d7c093a7f 100644 --- a/addons/website_forum/views/forum.xml +++ b/addons/website_forum/views/forum.xml @@ -24,6 +24,27 @@ + + + + + + + + + + + + + + + + + + + + +
diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml index e4f9e0c04b9..73fcbef0fd6 100644 --- a/addons/website_forum/views/website_forum.xml +++ b/addons/website_forum/views/website_forum.xml @@ -25,7 +25,7 @@ @@ -163,7 +163,7 @@
Answer
-