A widget based on website share widget has been added that allows to share questions and answers on social networks. Old posts can also be bumped through sharing. New fields have been added on forum model allowing to disable the old question bump and social sharing modals. Courtesy of RD India: Ajay Patel (apa@odoo.com), Jaysinh Shukla (jsh@odoo.com) Courtesy of use Merge: Damien Bouvy (dbo@odoo.com)
669 lines
33 KiB
Python
669 lines
33 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from datetime import datetime
|
|
import logging
|
|
import math
|
|
import re
|
|
import uuid
|
|
from werkzeug.exceptions import Forbidden
|
|
|
|
from openerp import _
|
|
from openerp import api, fields, models
|
|
from openerp import modules
|
|
from openerp import tools
|
|
from openerp import SUPERUSER_ID
|
|
from openerp.addons.website.models.website import slug
|
|
from openerp.exceptions import UserError
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class KarmaError(Forbidden):
|
|
""" Karma-related error, used for forum and posts. """
|
|
pass
|
|
|
|
|
|
class Forum(models.Model):
|
|
_name = 'forum.forum'
|
|
_description = 'Forum'
|
|
_inherit = ['mail.thread', 'website.seo.metadata']
|
|
|
|
def init(self, cr):
|
|
""" Add forum uuid for user email validation.
|
|
|
|
TDE TODO: move me somewhere else, auto_init ? """
|
|
forum_uuids = self.pool['ir.config_parameter'].search(cr, SUPERUSER_ID, [('key', '=', 'website_forum.uuid')])
|
|
if not forum_uuids:
|
|
self.pool['ir.config_parameter'].set_param(cr, SUPERUSER_ID, 'website_forum.uuid', str(uuid.uuid4()), ['base.group_system'])
|
|
|
|
@api.model
|
|
def _get_default_faq(self):
|
|
fname = modules.get_module_resource('website_forum', 'data', 'forum_default_faq.html')
|
|
with open(fname, 'r') as f:
|
|
return f.read()
|
|
return False
|
|
|
|
# description and use
|
|
name = fields.Char('Forum Name', required=True, translate=True)
|
|
faq = fields.Html('Guidelines', default=_get_default_faq, translate=True)
|
|
description = fields.Html(
|
|
'Description',
|
|
translate=True,
|
|
default='<p> This community is for professionals and enthusiasts of our products and services.'
|
|
'Share and discuss the best content and new marketing ideas,'
|
|
'build your professional profile and become a better marketer together.</p>')
|
|
default_order = fields.Selection([
|
|
('create_date desc', 'Newest'),
|
|
('write_date desc', 'Last Updated'),
|
|
('vote_count desc', 'Most Voted'),
|
|
('relevancy desc', 'Relevancy'),
|
|
('child_count desc', 'Answered')],
|
|
string='Default Order', required=True, default='write_date desc')
|
|
relevancy_post_vote = fields.Float('First Relevancy Parameter', default=0.8)
|
|
relevancy_time_decay = fields.Float('Second Relevancy Parameter', default=1.8)
|
|
default_post_type = fields.Selection([
|
|
('question', 'Question'),
|
|
('discussion', 'Discussion'),
|
|
('link', 'Link')],
|
|
string='Default Post', required=True, default='question')
|
|
allow_question = fields.Boolean('Questions', help="Users can answer only once per question. Contributors can edit answers and mark the right ones.", default=True)
|
|
allow_discussion = fields.Boolean('Discussions', default=True)
|
|
allow_link = fields.Boolean('Links', help="When clicking on the post, it redirects to an external link", default=True)
|
|
allow_bump = fields.Boolean('Allow Bump', default=True,
|
|
help='Posts without answers older than 10 days will display a popup'
|
|
'when displayed on the website to propose users to share them '
|
|
'on social networks, bumping the question at top of the thread.')
|
|
allow_share = fields.Boolean('Sharing Options', default=True,
|
|
help='After posting the user will be proposed to share its question'
|
|
'or answer on social networks, enabling social network propagation'
|
|
'of the forum content.')
|
|
# karma generation
|
|
karma_gen_question_new = fields.Integer(string='Asking a question', default=2)
|
|
karma_gen_question_upvote = fields.Integer(string='Question upvoted', default=5)
|
|
karma_gen_question_downvote = fields.Integer(string='Question downvoted', default=-2)
|
|
karma_gen_answer_upvote = fields.Integer(string='Answer upvoted', default=10)
|
|
karma_gen_answer_downvote = fields.Integer(string='Answer downvoted', default=-2)
|
|
karma_gen_answer_accept = fields.Integer(string='Accepting an answer', default=2)
|
|
karma_gen_answer_accepted = fields.Integer(string='Answer accepted', default=15)
|
|
karma_gen_answer_flagged = fields.Integer(string='Answer flagged', default=-100)
|
|
# karma-based actions
|
|
karma_ask = fields.Integer(string='Ask a new question', default=3)
|
|
karma_answer = fields.Integer(string='Answer a question', default=3)
|
|
karma_edit_own = fields.Integer(string='Edit its own posts', default=1)
|
|
karma_edit_all = fields.Integer(string='Edit all posts', default=300)
|
|
karma_close_own = fields.Integer(string='Close its own posts', default=100)
|
|
karma_close_all = fields.Integer(string='Close all posts', default=500)
|
|
karma_unlink_own = fields.Integer(string='Delete its own posts', default=500)
|
|
karma_unlink_all = fields.Integer(string='Delete all posts', default=1000)
|
|
karma_upvote = fields.Integer(string='Upvote', default=5)
|
|
karma_downvote = fields.Integer(string='Downvote', default=50)
|
|
karma_answer_accept_own = fields.Integer(string='Accept an answer on its own questions', default=20)
|
|
karma_answer_accept_all = fields.Integer(string='Accept an answers to all questions', default=500)
|
|
karma_editor_link_files = fields.Integer(string='Linking files (Editor)', default=20)
|
|
karma_editor_clickable_link = fields.Integer(string='Add clickable links (Editor)', default=20)
|
|
karma_comment_own = fields.Integer(string='Comment its own posts', default=1)
|
|
karma_comment_all = fields.Integer(string='Comment all posts', default=1)
|
|
karma_comment_convert_own = fields.Integer(string='Convert its own answers to comments and vice versa', default=50)
|
|
karma_comment_convert_all = fields.Integer(string='Convert all answers to answers and vice versa', default=500)
|
|
karma_comment_unlink_own = fields.Integer(string='Unlink its own comments', default=50)
|
|
karma_comment_unlink_all = fields.Integer(string='Unlinnk all comments', default=500)
|
|
karma_retag = fields.Integer(string='Change question tags', default=75)
|
|
karma_flag = fields.Integer(string='Flag a post as offensive', default=500)
|
|
karma_dofollow = fields.Integer(string='Disabled links', help='If the author has not enough karma, a nofollow attribute is added to links', default=500)
|
|
|
|
@api.model
|
|
def create(self, values):
|
|
return super(Forum, self.with_context(mail_create_nolog=True)).create(values)
|
|
|
|
@api.model
|
|
def _tag_to_write_vals(self, tags=''):
|
|
User = self.env['res.users']
|
|
Tag = self.env['forum.tag']
|
|
post_tags = []
|
|
existing_keep = []
|
|
for tag in filter(None, tags.split(',')):
|
|
if tag.startswith('_'): # it's a new tag
|
|
# check that not arleady created meanwhile or maybe excluded by the limit on the search
|
|
tag_ids = Tag.search([('name', '=', tag[1:])])
|
|
if tag_ids:
|
|
existing_keep.append(int(tag_ids[0]))
|
|
else:
|
|
# check if user have Karma needed to create need tag
|
|
user = User.sudo().browse(self._uid)
|
|
if user.exists() and user.karma >= self.karma_retag:
|
|
post_tags.append((0, 0, {'name': tag[1:], 'forum_id': self.id}))
|
|
else:
|
|
existing_keep.append(int(tag))
|
|
post_tags.insert(0, [6, 0, existing_keep])
|
|
return post_tags
|
|
|
|
|
|
class Post(models.Model):
|
|
_name = 'forum.post'
|
|
_description = 'Forum Post'
|
|
_inherit = ['mail.thread', 'website.seo.metadata']
|
|
_order = "is_correct DESC, vote_count DESC, write_date DESC"
|
|
|
|
name = fields.Char('Title')
|
|
forum_id = fields.Many2one('forum.forum', string='Forum', required=True)
|
|
content = fields.Html('Content')
|
|
plain_content = fields.Text('Plain Content', compute='_get_plain_content', store=True)
|
|
|
|
@api.one
|
|
@api.depends('content')
|
|
def _get_plain_content(self):
|
|
self.plain_content = tools.html2plaintext(self.content)[0:500]
|
|
|
|
content_link = fields.Char('URL', help="URL of Link Articles")
|
|
tag_ids = fields.Many2many('forum.tag', 'forum_tag_rel', 'forum_id', 'forum_tag_id', string='Tags')
|
|
state = fields.Selection([('active', 'Active'), ('close', 'Close'), ('offensive', 'Offensive')], string='Status', default='active')
|
|
views = fields.Integer('Number of Views', default=0)
|
|
active = fields.Boolean('Active', default=True)
|
|
post_type = fields.Selection([
|
|
('question', 'Question'),
|
|
('link', 'Article'),
|
|
('discussion', 'Discussion')],
|
|
string='Type', default='question')
|
|
website_message_ids = fields.One2many(
|
|
'mail.message', 'res_id',
|
|
domain=lambda self: ['&', ('model', '=', self._name), ('type', 'in', ['email', 'comment'])],
|
|
string='Post Messages', help="Comments on forum post",
|
|
)
|
|
# history
|
|
create_date = fields.Datetime('Asked on', select=True, readonly=True)
|
|
create_uid = fields.Many2one('res.users', string='Created by', select=True, readonly=True)
|
|
write_date = fields.Datetime('Update on', select=True, readonly=True)
|
|
bump_date = fields.Datetime('Bumped on', readonly=True,
|
|
help="Technical field allowing to bump a question. Writing on this field will trigger"
|
|
"a write on write_date and therefore bump the post. Directly writing on write_date"
|
|
"is currently not supported and this field is a workaround.")
|
|
write_uid = fields.Many2one('res.users', string='Updated by', select=True, readonly=True)
|
|
relevancy = fields.Float('Relevancy', compute="_compute_relevancy", store=True)
|
|
|
|
@api.one
|
|
@api.depends('vote_count', 'forum_id.relevancy_post_vote', 'forum_id.relevancy_time_decay')
|
|
def _compute_relevancy(self):
|
|
if self.create_date:
|
|
days = (datetime.today() - datetime.strptime(self.create_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days
|
|
self.relevancy = math.copysign(1, self.vote_count) * (abs(self.vote_count - 1) ** self.forum_id.relevancy_post_vote / (days + 2) ** self.forum_id.relevancy_time_decay)
|
|
else:
|
|
self.relevancy = 0
|
|
|
|
# vote
|
|
vote_ids = fields.One2many('forum.post.vote', 'post_id', string='Votes')
|
|
user_vote = fields.Integer('My Vote', compute='_get_user_vote')
|
|
vote_count = fields.Integer('Votes', compute='_get_vote_count', store=True)
|
|
|
|
@api.multi
|
|
def _get_user_vote(self):
|
|
votes = self.env['forum.post.vote'].search_read([('post_id', 'in', self._ids), ('user_id', '=', self._uid)], ['vote', 'post_id'])
|
|
mapped_vote = dict([(v['post_id'][0], v['vote']) for v in votes])
|
|
for vote in self:
|
|
vote.user_vote = mapped_vote.get(vote.id, 0)
|
|
|
|
@api.multi
|
|
@api.depends('vote_ids')
|
|
def _get_vote_count(self):
|
|
read_group_res = self.env['forum.post.vote'].read_group([('post_id', 'in', self._ids)], ['post_id', 'vote'], ['post_id', 'vote'], lazy=False)
|
|
result = dict.fromkeys(self._ids, 0)
|
|
for data in read_group_res:
|
|
result[data['post_id'][0]] += data['__count'] * int(data['vote'])
|
|
for post in self:
|
|
post.vote_count = result[post.id]
|
|
|
|
# favorite
|
|
favourite_ids = fields.Many2many('res.users', string='Favourite')
|
|
user_favourite = fields.Boolean('Is Favourite', compute='_get_user_favourite')
|
|
favourite_count = fields.Integer('Favorite Count', compute='_get_favorite_count', store=True)
|
|
|
|
@api.one
|
|
def _get_user_favourite(self):
|
|
self.user_favourite = self._uid in self.favourite_ids.ids
|
|
|
|
@api.one
|
|
@api.depends('favourite_ids')
|
|
def _get_favorite_count(self):
|
|
self.favourite_count = len(self.favourite_ids)
|
|
|
|
# hierarchy
|
|
is_correct = fields.Boolean('Correct', help='Correct answer or answer accepted')
|
|
parent_id = fields.Many2one('forum.post', string='Question', ondelete='cascade')
|
|
self_reply = fields.Boolean('Reply to own question', compute='_is_self_reply', store=True)
|
|
child_ids = fields.One2many('forum.post', 'parent_id', string='Answers')
|
|
child_count = fields.Integer('Number of answers', compute='_get_child_count', store=True)
|
|
uid_has_answered = fields.Boolean('Has Answered', compute='_get_uid_has_answered')
|
|
has_validated_answer = fields.Boolean('Is answered', compute='_get_has_validated_answer', store=True)
|
|
|
|
@api.one
|
|
@api.depends('create_uid', 'parent_id')
|
|
def _is_self_reply(self):
|
|
self.self_reply = self.parent_id.create_uid.id == self._uid
|
|
|
|
@api.one
|
|
@api.depends('child_ids.create_uid', 'website_message_ids')
|
|
def _get_child_count(self):
|
|
def process(node):
|
|
total = len(node.website_message_ids) + len(node.child_ids)
|
|
for child in node.child_ids:
|
|
total += process(child)
|
|
return total
|
|
self.child_count = process(self)
|
|
|
|
@api.one
|
|
def _get_uid_has_answered(self):
|
|
self.uid_has_answered = any(answer.create_uid.id == self._uid for answer in self.child_ids)
|
|
|
|
@api.one
|
|
@api.depends('child_ids.is_correct')
|
|
def _get_has_validated_answer(self):
|
|
self.has_validated_answer = any(answer.is_correct for answer in self.child_ids)
|
|
|
|
# closing
|
|
closed_reason_id = fields.Many2one('forum.post.reason', string='Reason')
|
|
closed_uid = fields.Many2one('res.users', string='Closed by', select=1)
|
|
closed_date = fields.Datetime('Closed on', readonly=True)
|
|
# karma calculation and access
|
|
karma_accept = fields.Integer('Convert comment to answer', compute='_get_post_karma_rights')
|
|
karma_edit = fields.Integer('Karma to edit', compute='_get_post_karma_rights')
|
|
karma_close = fields.Integer('Karma to close', compute='_get_post_karma_rights')
|
|
karma_unlink = fields.Integer('Karma to unlink', compute='_get_post_karma_rights')
|
|
karma_comment = fields.Integer('Karma to comment', compute='_get_post_karma_rights')
|
|
karma_comment_convert = fields.Integer('Karma to convert comment to answer', compute='_get_post_karma_rights')
|
|
can_ask = fields.Boolean('Can Ask', compute='_get_post_karma_rights')
|
|
can_answer = fields.Boolean('Can Answer', compute='_get_post_karma_rights')
|
|
can_accept = fields.Boolean('Can Accept', compute='_get_post_karma_rights')
|
|
can_edit = fields.Boolean('Can Edit', compute='_get_post_karma_rights')
|
|
can_close = fields.Boolean('Can Close', compute='_get_post_karma_rights')
|
|
can_unlink = fields.Boolean('Can Unlink', compute='_get_post_karma_rights')
|
|
can_upvote = fields.Boolean('Can Upvote', compute='_get_post_karma_rights')
|
|
can_downvote = fields.Boolean('Can Downvote', compute='_get_post_karma_rights')
|
|
can_comment = fields.Boolean('Can Comment', compute='_get_post_karma_rights')
|
|
can_comment_convert = fields.Boolean('Can Convert to Comment', compute='_get_post_karma_rights')
|
|
|
|
@api.one
|
|
def _get_post_karma_rights(self):
|
|
user = self.env.user
|
|
|
|
self.karma_accept = self.parent_id and self.parent_id.create_uid.id == self._uid and self.forum_id.karma_answer_accept_own or self.forum_id.karma_answer_accept_all
|
|
self.karma_edit = self.create_uid.id == self._uid and self.forum_id.karma_edit_own or self.forum_id.karma_edit_all
|
|
self.karma_close = self.create_uid.id == self._uid and self.forum_id.karma_close_own or self.forum_id.karma_close_all
|
|
self.karma_unlink = self.create_uid.id == self._uid and self.forum_id.karma_unlink_own or self.forum_id.karma_unlink_all
|
|
self.karma_comment = self.create_uid.id == self._uid and self.forum_id.karma_comment_own or self.forum_id.karma_comment_all
|
|
self.karma_comment_convert = self.create_uid.id == self._uid and self.forum_id.karma_comment_convert_own or self.forum_id.karma_comment_convert_all
|
|
|
|
self.can_ask = user.karma >= self.forum_id.karma_ask
|
|
self.can_answer = user.karma >= self.forum_id.karma_answer
|
|
self.can_accept = user.karma >= self.karma_accept
|
|
self.can_edit = user.karma >= self.karma_edit
|
|
self.can_close = user.karma >= self.karma_close
|
|
self.can_unlink = user.karma >= self.karma_unlink
|
|
self.can_upvote = user.karma >= self.forum_id.karma_upvote
|
|
self.can_downvote = user.karma >= self.forum_id.karma_downvote
|
|
self.can_comment = user.karma >= self.karma_comment
|
|
self.can_comment_convert = user.karma >= self.karma_comment_convert
|
|
|
|
def name_get(self, cr, uid, ids, context=None):
|
|
result = []
|
|
for post in self.browse(cr, uid, ids, context=context):
|
|
if post.parent_id and not post.name:
|
|
result.append((post.id, '%s (%s)' % (post.parent_id.name, post.id)))
|
|
else:
|
|
result.append((post.id, '%s' % (post.name)))
|
|
return result
|
|
|
|
def _update_content(self, content, forum_id):
|
|
forum = self.env['forum.forum'].browse(forum_id)
|
|
if content and self.env.user.karma < forum.karma_dofollow:
|
|
for match in re.findall(r'<a\s.*href=".*?">', content):
|
|
content = re.sub(match, match[:3] + 'rel="nofollow" ' + match[3:], content)
|
|
return content
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
if 'content' in vals and vals.get('forum_id'):
|
|
vals['content'] = self._update_content(vals['content'], vals['forum_id'])
|
|
|
|
post = super(Post, self.with_context(mail_create_nolog=True)).create(vals)
|
|
# deleted or closed questions
|
|
if post.parent_id and (post.parent_id.state == 'close' or post.parent_id.active is False):
|
|
raise UserError(_('Posting answer on a [Deleted] or [Closed] question is not possible'))
|
|
# karma-based access
|
|
if not post.parent_id and not post.can_ask:
|
|
raise KarmaError('Not enough karma to create a new question')
|
|
elif post.parent_id and not post.can_answer:
|
|
raise KarmaError('Not enough karma to answer to a question')
|
|
# messaging and chatter
|
|
base_url = self.env['ir.config_parameter'].get_param('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))
|
|
)
|
|
post.parent_id.message_post(subject=_('Re: %s') % post.parent_id.name, body=body, subtype='website_forum.mt_answer_new')
|
|
else:
|
|
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))
|
|
)
|
|
post.message_post(subject=post.name, body=body, subtype='website_forum.mt_question_new')
|
|
self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
|
|
return post
|
|
|
|
@api.multi
|
|
@api.depends('name', 'post_type')
|
|
def name_get(self):
|
|
result = []
|
|
for post in self:
|
|
if post.post_type == 'discussion' and post.parent_id and not post.name:
|
|
result.append((post.id, '%s (%s)' % (post.parent_id.name, post.id)))
|
|
else:
|
|
result.append((post.id, '%s' % (post.name)))
|
|
return result
|
|
|
|
@api.multi
|
|
def write(self, vals):
|
|
if 'content' in vals:
|
|
vals['content'] = self._update_content(vals['content'], self.forum_id.id)
|
|
if 'state' in vals:
|
|
if vals['state'] in ['active', 'close'] and any(not post.can_close for post in self):
|
|
raise KarmaError('Not enough karma to close or reopen a post.')
|
|
if 'active' in vals:
|
|
if any(not post.can_unlink for post in self):
|
|
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 self):
|
|
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:
|
|
if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid:
|
|
post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * mult)
|
|
self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult)
|
|
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 self):
|
|
raise KarmaError('Not enough karma to edit a post.')
|
|
|
|
res = super(Post, self).write(vals)
|
|
# if post content modify, notify followers
|
|
if 'content' in vals or 'name' in vals:
|
|
for post in self:
|
|
if post.parent_id:
|
|
body, subtype = _('Answer Edited'), 'website_forum.mt_answer_edit'
|
|
obj_id = post.parent_id
|
|
else:
|
|
body, subtype = _('Question Edited'), 'website_forum.mt_question_edit'
|
|
obj_id = post
|
|
obj_id.message_post(body=body, subtype=subtype)
|
|
return res
|
|
|
|
@api.multi
|
|
def reopen(self):
|
|
if any(post.parent_id or post.state != 'close' for post in self):
|
|
return False
|
|
|
|
reason_offensive = self.env.ref('website_forum.reason_7')
|
|
reason_spam = self.env.ref('website_forum.reason_8')
|
|
for post in self:
|
|
if post.closed_reason_id in (reason_offensive, reason_spam):
|
|
_logger.info('Upvoting user <%s>, reopening spam/offensive question',
|
|
post.create_uid)
|
|
# TODO: in master, consider making this a tunable karma parameter
|
|
post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_downvote * -5)
|
|
|
|
self.sudo().write({'state': 'active'})
|
|
|
|
@api.multi
|
|
def close(self, reason_id):
|
|
if any(post.parent_id for post in self):
|
|
return False
|
|
|
|
reason_offensive = self.env.ref('website_forum.reason_7').id
|
|
reason_spam = self.env.ref('website_forum.reason_8').id
|
|
if reason_id in (reason_offensive, reason_spam):
|
|
for post in self:
|
|
_logger.info('Downvoting user <%s> for posting spam/offensive contents',
|
|
post.create_uid)
|
|
# TODO: in master, consider making this a tunable karma parameter
|
|
post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_downvote * 5)
|
|
|
|
self.write({
|
|
'state': 'close',
|
|
'closed_uid': self._uid,
|
|
'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
|
|
'closed_reason_id': reason_id,
|
|
})
|
|
return True
|
|
|
|
@api.multi
|
|
def unlink(self):
|
|
if any(not post.can_unlink for post in self):
|
|
raise KarmaError('Not enough karma to unlink a post')
|
|
# if unlinking an answer with accepted answer: remove provided karma
|
|
for post in self:
|
|
if post.is_correct:
|
|
post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
|
|
self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
|
|
return super(Post, self).unlink()
|
|
|
|
@api.multi
|
|
def bump(self):
|
|
""" Bump a question: trigger a write_date by writing on a dummy bump_date
|
|
field. One cannot bump a question more than once every 10 days. """
|
|
self.ensure_one()
|
|
if self.forum_id.allow_bump and not self.child_ids and (datetime.today() - datetime.strptime(self.write_date, tools.DEFAULT_SERVER_DATETIME_FORMAT)).days > 9:
|
|
# write through super to bypass karma; sudo to allow public user to bump any post
|
|
return self.sudo().write({'bump_date': fields.Datetime.now()})
|
|
return False
|
|
|
|
@api.multi
|
|
def vote(self, upvote=True):
|
|
Vote = self.env['forum.post.vote']
|
|
vote_ids = Vote.search([('post_id', 'in', self._ids), ('user_id', '=', self._uid)])
|
|
new_vote = '1' if upvote else '-1'
|
|
voted_forum_ids = set()
|
|
if vote_ids:
|
|
for vote in vote_ids:
|
|
if upvote:
|
|
new_vote = '0' if vote.vote == '-1' else '1'
|
|
else:
|
|
new_vote = '0' if vote.vote == '1' else '-1'
|
|
vote.vote = new_vote
|
|
voted_forum_ids.add(vote.post_id.id)
|
|
for post_id in set(self._ids) - voted_forum_ids:
|
|
for post_id in self._ids:
|
|
Vote.create({'post_id': post_id, 'vote': new_vote})
|
|
return {'vote_count': self.vote_count, 'user_vote': new_vote}
|
|
|
|
@api.one
|
|
def convert_answer_to_comment(self):
|
|
""" 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. """
|
|
if not self.parent_id:
|
|
return False
|
|
|
|
# karma-based action check: use the post field that computed own/all value
|
|
if not self.can_comment_convert:
|
|
raise KarmaError('Not enough karma to convert an answer to a comment')
|
|
|
|
# post the message
|
|
question = self.parent_id
|
|
values = {
|
|
'author_id': self.create_uid.partner_id.id,
|
|
'body': tools.html2plaintext(self.content),
|
|
'type': 'comment',
|
|
'subtype': 'mail.mt_comment',
|
|
'date': self.create_date,
|
|
}
|
|
new_message = self.browse(question.id).with_context(mail_create_nosubscribe=True).message_post(**values)
|
|
|
|
# unlink the original answer, using SUPERUSER_ID to avoid karma issues
|
|
self.sudo().unlink()
|
|
|
|
return new_message
|
|
|
|
@api.model
|
|
def convert_comment_to_answer(self, message_id, default=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.env['mail.message'].sudo().browse(message_id)
|
|
post = self.browse(comment.res_id)
|
|
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 == self.env.user.partner_id.id and post.forum_id.karma_comment_convert_own or post.forum_id.karma_comment_convert_all
|
|
can_convert = self.env.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 = self.sudo(post_create_uid.id).create(post_values)
|
|
|
|
# delete comment
|
|
comment.unlink()
|
|
|
|
return new_post
|
|
|
|
@api.one
|
|
def unlink_comment(self, message_id):
|
|
user = self.env.user
|
|
comment = self.env['mail.message'].sudo().browse(message_id)
|
|
if not comment.model == 'forum.post' or not comment.res_id == self.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 self.forum_id.karma_comment_unlink_own or self.forum_id.karma_comment_unlink_all
|
|
can_unlink = user.karma >= karma_unlink
|
|
if not can_unlink:
|
|
raise KarmaError('Not enough karma to unlink a comment')
|
|
return comment.unlink()
|
|
|
|
@api.multi
|
|
def set_viewed(self):
|
|
self._cr.execute("""UPDATE forum_post SET views = views+1 WHERE id IN %s""", (self._ids,))
|
|
return True
|
|
|
|
@api.model
|
|
def _get_access_link(self, mail, partner):
|
|
post = self.browse(mail.res_id)
|
|
res_id = post.parent_id and "%s#answer-%s" % (post.parent_id.id, post.id) or post.id
|
|
return "/forum/%s/question/%s" % (post.forum_id.id, res_id)
|
|
|
|
@api.cr_uid_ids_context
|
|
def message_post(self, cr, uid, thread_id, type='notification', subtype=None, context=None, **kwargs):
|
|
if thread_id and type == 'comment': # user comments have a restriction on karma
|
|
if isinstance(thread_id, (list, tuple)):
|
|
post_id = thread_id[0]
|
|
else:
|
|
post_id = thread_id
|
|
post = self.browse(cr, uid, post_id, context=context)
|
|
# TDE FIXME: trigger browse because otherwise the function field is not compted - check with RCO
|
|
tmp1, tmp2 = post.karma_comment, post.can_comment
|
|
user = self.pool['res.users'].browse(cr, uid, uid)
|
|
tmp3 = user.karma
|
|
# TDE END FIXME
|
|
if not post.can_comment:
|
|
raise KarmaError('Not enough karma to comment')
|
|
return super(Post, self).message_post(cr, uid, thread_id, type=type, subtype=subtype, context=context, **kwargs)
|
|
|
|
|
|
class PostReason(models.Model):
|
|
_name = "forum.post.reason"
|
|
_description = "Post Closing Reason"
|
|
_order = 'name'
|
|
|
|
name = fields.Char(string='Closing Reason', required=True, translate=True)
|
|
|
|
|
|
class Vote(models.Model):
|
|
_name = 'forum.post.vote'
|
|
_description = 'Vote'
|
|
|
|
post_id = fields.Many2one('forum.post', string='Post', ondelete='cascade', required=True)
|
|
user_id = fields.Many2one('res.users', string='User', required=True, default=lambda self: self._uid)
|
|
vote = fields.Selection([('1', '1'), ('-1', '-1'), ('0', '0')], string='Vote', required=True, default='1')
|
|
create_date = fields.Datetime('Create Date', select=True, readonly=True)
|
|
forum_id = fields.Many2one('forum.forum', string='Forum', related="post_id.forum_id", store=True)
|
|
recipient_id = fields.Many2one('res.users', string='To', related="post_id.create_uid", store=True)
|
|
|
|
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]
|
|
|
|
@api.model
|
|
def create(self, vals):
|
|
vote = super(Vote, self).create(vals)
|
|
|
|
# own post check
|
|
if vote.user_id.id == vote.post_id.create_uid.id:
|
|
raise UserError(_('Not allowed to vote for its own post'))
|
|
# karma check
|
|
if vote.vote == '1' and not vote.post_id.can_upvote:
|
|
raise KarmaError('Not enough karma to upvote.')
|
|
elif vote.vote == '-1' and not vote.post_id.can_downvote:
|
|
raise KarmaError('Not enough karma to downvote.')
|
|
|
|
if vote.post_id.parent_id:
|
|
karma_value = self._get_karma_value('0', vote.vote, vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
|
|
else:
|
|
karma_value = self._get_karma_value('0', vote.vote, vote.forum_id.karma_gen_question_upvote, vote.forum_id.karma_gen_question_downvote)
|
|
vote.recipient_id.sudo().add_karma(karma_value)
|
|
return vote
|
|
|
|
@api.multi
|
|
def write(self, values):
|
|
if 'vote' in values:
|
|
for vote in self:
|
|
# own post check
|
|
if vote.user_id.id == vote.post_id.create_uid.id:
|
|
raise UserError(_('Not allowed to vote for its own post'))
|
|
# karma check
|
|
if (values['vote'] == '1' or vote.vote == '-1' and values['vote'] == '0') and not vote.post_id.can_upvote:
|
|
raise KarmaError('Not enough karma to upvote.')
|
|
elif (values['vote'] == '-1' or vote.vote == '1' and values['vote'] == '0') and not vote.post_id.can_downvote:
|
|
raise KarmaError('Not enough karma to downvote.')
|
|
|
|
# karma update
|
|
if vote.post_id.parent_id:
|
|
karma_value = self._get_karma_value(vote.vote, values['vote'], vote.forum_id.karma_gen_answer_upvote, vote.forum_id.karma_gen_answer_downvote)
|
|
else:
|
|
karma_value = self._get_karma_value(vote.vote, values['vote'], vote.forum_id.karma_gen_question_upvote, vote.forum_id.karma_gen_question_downvote)
|
|
vote.recipient_id.sudo().add_karma(karma_value)
|
|
res = super(Vote, self).write(values)
|
|
return res
|
|
|
|
|
|
class Tags(models.Model):
|
|
_name = "forum.tag"
|
|
_description = "Forum Tag"
|
|
_inherit = ['website.seo.metadata']
|
|
|
|
name = fields.Char('Name', required=True)
|
|
create_uid = fields.Many2one('res.users', string='Created by', readonly=True)
|
|
forum_id = fields.Many2one('forum.forum', string='Forum', required=True)
|
|
post_ids = fields.Many2many('forum.post', 'forum_tag_rel', 'forum_tag_id', 'forum_id', string='Posts')
|
|
posts_count = fields.Integer('Number of Posts', compute='_get_posts_count', store=True)
|
|
|
|
@api.multi
|
|
@api.depends("post_ids.tag_ids")
|
|
def _get_posts_count(self):
|
|
for tag in self:
|
|
tag.posts_count = len(tag.post_ids)
|