[IMP] website_forum: moderation & anti spam
This commit is contained in:
committed by
Jeremy Kersten
parent
e301e0817e
commit
205ce98656
@@ -228,6 +228,11 @@ class WebsiteForum(http.Controller):
|
||||
if not question.can_view:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
# Hide pending posts from non-moderators and non-creator
|
||||
user = request.env.user
|
||||
if question.state == 'pending' and user.karma < forum.karma_post and question.create_uid != user:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
# increment view counter
|
||||
question.sudo().set_viewed()
|
||||
if question.parent_id:
|
||||
@@ -260,11 +265,10 @@ 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):
|
||||
reasons = request.env['forum.post.reason'].search([])
|
||||
reasons = request.env['forum.post.reason'].search([('reason_type', '=', 'basic')])
|
||||
|
||||
values = self._prepare_forum_values(**post)
|
||||
values.update({
|
||||
'question': question,
|
||||
'question': question,
|
||||
'forum': forum,
|
||||
'reasons': reasons,
|
||||
@@ -419,6 +423,107 @@ class WebsiteForum(http.Controller):
|
||||
return False
|
||||
return post.bump()
|
||||
|
||||
# Moderation Tools
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/validation_queue', type='http', auth="user", website=True)
|
||||
def validation_queue(self, forum):
|
||||
user = request.env.user
|
||||
if user.karma < forum.karma_moderate:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
Post = request.env['forum.post']
|
||||
domain = [('forum_id', '=', forum.id), ('state', '=', 'pending')]
|
||||
posts_to_validate_ids = Post.search(domain)
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values.update({
|
||||
'posts_ids': posts_to_validate_ids,
|
||||
'queue_type': 'validation',
|
||||
})
|
||||
|
||||
return request.website.render("website_forum.moderation_queue", values)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/flagged_queue', type='http', auth="user", website=True)
|
||||
def flagged_queue(self, forum):
|
||||
user = request.env.user
|
||||
if user.karma < forum.karma_moderate:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
Post = request.env['forum.post']
|
||||
domain = [('forum_id', '=', forum.id), ('state', '=', 'flagged')]
|
||||
flagged_posts_ids = Post.search(domain, order='write_date DESC')
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values.update({
|
||||
'posts_ids': flagged_posts_ids,
|
||||
'queue_type': 'flagged',
|
||||
})
|
||||
|
||||
return request.website.render("website_forum.moderation_queue", values)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/offensive_posts', type='http', auth="user", website=True)
|
||||
def offensive_posts(self, forum):
|
||||
user = request.env.user
|
||||
if user.karma < forum.karma_moderate:
|
||||
raise werkzeug.exceptions.NotFound()
|
||||
|
||||
Post = request.env['forum.post']
|
||||
domain = [('forum_id', '=', forum.id), ('state', '=', 'offensive'), ('active', '=', False)]
|
||||
offensive_posts_ids = Post.search(domain, order='write_date DESC')
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values.update({
|
||||
'posts_ids': offensive_posts_ids,
|
||||
'queue_type': 'offensive',
|
||||
})
|
||||
|
||||
return request.website.render("website_forum.moderation_queue", values)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/validate', type='http', auth="user", website=True)
|
||||
def post_accept(self, forum, post):
|
||||
url = "/forum/%s/validation_queue" % (slug(forum))
|
||||
if post.state == 'flagged':
|
||||
url = "/forum/%s/flagged_queue" % (slug(forum))
|
||||
elif post.state == 'offensive':
|
||||
url = "/forum/%s/offensive_posts" % (slug(forum))
|
||||
post.validate()
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/refuse', type='http', auth="user", website=True)
|
||||
def post_refuse(self, forum, post):
|
||||
post.refuse()
|
||||
return self.question_ask_for_close(forum, post)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/flag', type='json', auth="public", website=True)
|
||||
def post_flag(self, forum, post, **kwargs):
|
||||
if not request.session.uid:
|
||||
return {'error': 'anonymous_user'}
|
||||
return post.flag()[0]
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/ask_for_mark_as_offensive', type='http', auth="user", methods=['GET'], website=True)
|
||||
def post_ask_for_mark_as_offensive(self, forum, post):
|
||||
offensive_reasons = request.env['forum.post.reason'].search([('reason_type', '=', 'offensive')])
|
||||
|
||||
values = self._prepare_forum_values(forum=forum)
|
||||
values.update({
|
||||
'question': post,
|
||||
'forum': forum,
|
||||
'reasons': offensive_reasons,
|
||||
'offensive': True,
|
||||
})
|
||||
return request.website.render("website_forum.close_post", values)
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/mark_as_offensive', type='http', auth="user", methods=["POST"], website=True)
|
||||
def post_mark_as_offensive(self, forum, post, **kwargs):
|
||||
post.mark_as_offensive(reason_id=int(kwargs.get('reason_id', False)))
|
||||
url = ''
|
||||
if post.parent_id:
|
||||
url = "/forum/%s/question/%s/#answer-%s" % (slug(forum), post.parent_id.id, post.id)
|
||||
else:
|
||||
url = "/forum/%s/question/%s" % (slug(forum), slug(post))
|
||||
return werkzeug.utils.redirect(url)
|
||||
|
||||
# User
|
||||
# --------------------------------------------------
|
||||
|
||||
|
||||
@@ -83,27 +83,55 @@
|
||||
<!-- Reasons for closing Post -->
|
||||
<record id="reason_1" model="forum.post.reason">
|
||||
<field name="name">duplicate post</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_2" model="forum.post.reason">
|
||||
<field name="name">off-topic or not relevant</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_3" model="forum.post.reason">
|
||||
<field name="name">too subjective and argumentative</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_4" model="forum.post.reason">
|
||||
<field name="name">not a real post</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_6" model="forum.post.reason">
|
||||
<field name="name">not relevant or out dated</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_7" model="forum.post.reason">
|
||||
<field name="name">contains offensive or malicious remarks</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_8" model="forum.post.reason">
|
||||
<field name="name">spam or advertising</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_9" model="forum.post.reason">
|
||||
<field name="name">too localized</field>
|
||||
<field name="reason_type">basic</field>
|
||||
</record>
|
||||
<record id="reason_11" model="forum.post.reason">
|
||||
<field name="name">insulting and offensive language</field>
|
||||
<field name="reason_type">offensive</field>
|
||||
</record>
|
||||
<record id="reason_12" model="forum.post.reason">
|
||||
<field name="name">violent language</field>
|
||||
<field name="reason_type">offensive</field>
|
||||
</record>
|
||||
<record id="reason_13" model="forum.post.reason">
|
||||
<field name="name">inappropriate and unacceptable statements</field>
|
||||
<field name="reason_type">offensive</field>
|
||||
</record>
|
||||
<record id="reason_14" model="forum.post.reason">
|
||||
<field name="name">threatening language</field>
|
||||
<field name="reason_type">offensive</field>
|
||||
</record>
|
||||
<record id="reason_15" model="forum.post.reason">
|
||||
<field name="name">racist and hate speech</field>
|
||||
<field name="reason_type">offensive</field>
|
||||
</record>
|
||||
|
||||
<!-- Email template for email validation (for karma purpose) -->
|
||||
|
||||
@@ -63,7 +63,7 @@ class Forum(models.Model):
|
||||
<p class="text-muted text-center" style="text-align: left;">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>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<a href="#" class="js_close_intro">Hide Intro</a> <a class="btn btn-primary forum_register_url" href="/web/login">Register</a> </div>
|
||||
<a href="#" class="js_close_intro">Hide Intro</a> <a class="btn btn-primary forum_register_url" href="/web/login">Register</a> </div>
|
||||
</div>
|
||||
</div>
|
||||
</section>""")
|
||||
@@ -92,6 +92,8 @@ class Forum(models.Model):
|
||||
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.')
|
||||
count_posts_waiting_validation = fields.Integer(string="Number of posts waiting for validation", compute='_compute_count_posts_waiting_validation')
|
||||
count_flagged_posts = fields.Integer(string='Number of flagged posts', compute='_compute_count_flagged_posts')
|
||||
# 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)
|
||||
@@ -126,6 +128,8 @@ class Forum(models.Model):
|
||||
karma_editor = fields.Integer(string='Editor Features: image and links',
|
||||
default=30, oldname='karma_editor_link_files')
|
||||
karma_user_bio = fields.Integer(string='Display detailed user biography', default=750)
|
||||
karma_post = fields.Integer(string='Ask questions without validation', default=100)
|
||||
karma_moderate = fields.Integer(string='Moderate posts', default=1000)
|
||||
|
||||
@api.one
|
||||
@api.constrains('allow_question', 'allow_discussion', 'allow_link', 'default_post_type')
|
||||
@@ -141,6 +145,15 @@ class Forum(models.Model):
|
||||
if self.default_post_type == 'link' and not self.allow_link or self.default_post_type == 'question' and not self.allow_question or self.default_post_type == 'discussion' and not self.allow_discussion:
|
||||
raise Warning(_('Post type in "Default post" must be activated'))
|
||||
|
||||
@api.one
|
||||
def _compute_count_posts_waiting_validation(self):
|
||||
domain = [('forum_id', '=', self.id), ('state', '=', 'pending')]
|
||||
self.count_posts_waiting_validation = self.env['forum.post'].search_count(domain)
|
||||
|
||||
@api.one
|
||||
def _compute_count_flagged_posts(self):
|
||||
domain = [('forum_id', '=', self.id), ('state', '=', 'flagged')]
|
||||
self.count_flagged_posts = self.env['forum.post'].search_count(domain)
|
||||
|
||||
@api.model
|
||||
def create(self, values):
|
||||
@@ -185,15 +198,9 @@ class Post(models.Model):
|
||||
forum_id = fields.Many2one('forum.forum', string='Forum', required=True)
|
||||
content = fields.Html('Content', strip_style=True)
|
||||
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] if self.content else False
|
||||
|
||||
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')
|
||||
state = fields.Selection([('active', 'Active'), ('pending', 'Waiting Validation'), ('close', 'Close'), ('offensive', 'Offensive'), ('flagged', 'Flagged')], string='Status', default='active')
|
||||
views = fields.Integer('Number of Views', default=0)
|
||||
active = fields.Boolean('Active', default=True)
|
||||
post_type = fields.Selection([
|
||||
@@ -206,6 +213,7 @@ class Post(models.Model):
|
||||
domain=lambda self: ['&', ('model', '=', self._name), ('message_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)
|
||||
@@ -217,6 +225,63 @@ class Post(models.Model):
|
||||
write_uid = fields.Many2one('res.users', string='Updated by', select=True, readonly=True)
|
||||
relevancy = fields.Float('Relevance', compute="_compute_relevancy", store=True)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# 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)
|
||||
|
||||
# offensive moderation tools
|
||||
flag_user_id = fields.Many2one('res.users', string='Flagged by')
|
||||
moderator_id = fields.Many2one('res.users', string='Reviewed by', readonly=True)
|
||||
|
||||
# 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')
|
||||
karma_flag = fields.Integer('Flag a post as offensive', 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')
|
||||
can_view = fields.Boolean('Can View', compute='_get_post_karma_rights')
|
||||
can_display_biography = fields.Boolean('Can userbiography of the author be viewed', compute='_get_post_karma_rights')
|
||||
can_post = fields.Boolean('Can Automatically be Validated', compute='_get_post_karma_rights')
|
||||
can_flag = fields.Boolean('Can Flag', compute='_get_post_karma_rights')
|
||||
can_moderate = fields.Boolean('Can Moderate', compute='_get_post_karma_rights')
|
||||
|
||||
@api.one
|
||||
@api.depends('content')
|
||||
def _get_plain_content(self):
|
||||
self.plain_content = tools.html2plaintext(self.content)[0:500] if self.content else False
|
||||
|
||||
@api.one
|
||||
@api.depends('vote_count', 'forum_id.relevancy_post_vote', 'forum_id.relevancy_time_decay')
|
||||
def _compute_relevancy(self):
|
||||
@@ -226,11 +291,6 @@ class Post(models.Model):
|
||||
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'])
|
||||
@@ -248,11 +308,6 @@ class Post(models.Model):
|
||||
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
|
||||
@@ -262,15 +317,6 @@ class Post(models.Model):
|
||||
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):
|
||||
@@ -295,29 +341,6 @@ class Post(models.Model):
|
||||
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')
|
||||
can_view = fields.Boolean('Can View', compute='_get_post_karma_rights')
|
||||
can_display_biography = fields.Boolean('Can userbiography of the author be viewed', compute='_get_post_karma_rights')
|
||||
|
||||
@api.multi
|
||||
def _get_post_karma_rights(self):
|
||||
@@ -347,6 +370,9 @@ class Post(models.Model):
|
||||
post.can_comment_convert = is_admin or user.karma >= post.karma_comment_convert
|
||||
post.can_view = is_admin or user.karma >= post.karma_close or post_sudo.create_uid.karma > 0
|
||||
post.can_display_biography = is_admin or post_sudo.create_uid.karma >= post.forum_id.karma_user_bio
|
||||
post.can_post = is_admin or user.karma >= post.forum_id.karma_post
|
||||
post.can_flag = is_admin or user.karma >= post.forum_id.karma_flag
|
||||
post.can_moderate = is_admin or user.karma >= post.forum_id.karma_moderate
|
||||
|
||||
@api.one
|
||||
@api.constrains('post_type', 'forum_id')
|
||||
@@ -383,6 +409,8 @@ class Post(models.Model):
|
||||
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')
|
||||
if not post.parent_id and not post.can_post:
|
||||
post.state = 'pending'
|
||||
# messaging and chatter
|
||||
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||
if post.parent_id:
|
||||
@@ -397,7 +425,8 @@ class Post(models.Model):
|
||||
(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)
|
||||
if post.state == 'active':
|
||||
self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
|
||||
return post
|
||||
|
||||
@api.model
|
||||
@@ -492,6 +521,66 @@ class Post(models.Model):
|
||||
})
|
||||
return True
|
||||
|
||||
@api.one
|
||||
def validate(self):
|
||||
if not self.can_moderate:
|
||||
raise KarmaError('Not enough karma to validate a post')
|
||||
|
||||
# if state == pending, no karma previously added for the new question
|
||||
if self.state == 'pending':
|
||||
self.create_uid.sudo().add_karma(self.forum_id.karma_gen_question_new)
|
||||
|
||||
self.moderator_id = self.env.user
|
||||
|
||||
self.write({
|
||||
'state': 'active',
|
||||
'active': True,
|
||||
})
|
||||
return True
|
||||
|
||||
@api.one
|
||||
def refuse(self):
|
||||
if not self.can_moderate:
|
||||
raise KarmaError('Not enough karma to refuse a post')
|
||||
|
||||
self.moderator_id = self.env.user
|
||||
return True
|
||||
|
||||
@api.one
|
||||
def flag(self):
|
||||
if not self.can_flag:
|
||||
raise KarmaError('Not enough karma to flag a post')
|
||||
|
||||
if(self.state == 'flagged'):
|
||||
return {'error': 'post_already_flagged'}
|
||||
elif(self.state == 'active'):
|
||||
self.write({
|
||||
'state': 'flagged',
|
||||
'flag_user_id': self.env.user.id,
|
||||
})
|
||||
return self.can_moderate and {'success': 'post_flagged_moderator'} or {'success': 'post_flagged_non_moderator'}
|
||||
else:
|
||||
return {'error': 'post_non_flaggable'}
|
||||
|
||||
@api.one
|
||||
def mark_as_offensive(self, reason_id):
|
||||
if not self.can_moderate:
|
||||
raise KarmaError('Not enough karma to mark a post as offensive')
|
||||
|
||||
# remove some karma
|
||||
# TODO: consider making this a tunable karma parameter (cfr close)
|
||||
_logger.info('Downvoting user <%s> for posting spam/offensive contents', self.create_uid)
|
||||
self.create_uid.sudo().add_karma(self.forum_id.karma_gen_question_downvote * 5)
|
||||
|
||||
self.write({
|
||||
'state': 'offensive',
|
||||
'moderator_id': self.env.user.id,
|
||||
'closed_date': datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT),
|
||||
'closed_reason_id': reason_id,
|
||||
'active': False,
|
||||
})
|
||||
return True
|
||||
|
||||
@api.multi
|
||||
def unlink(self):
|
||||
if any(not post.can_unlink for post in self):
|
||||
@@ -661,6 +750,7 @@ class PostReason(models.Model):
|
||||
_order = 'name'
|
||||
|
||||
name = fields.Char(string='Closing Reason', required=True, translate=True)
|
||||
reason_type = fields.Char(string='Reason Type')
|
||||
|
||||
|
||||
class Vote(models.Model):
|
||||
|
||||
@@ -45,6 +45,50 @@ if(!$('.website_forum').length) {
|
||||
}
|
||||
);
|
||||
|
||||
$('.flag').not('.karma_required').on('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
ajax.jsonRpc($link.data('href'), 'call', {})
|
||||
.then(function (data) {
|
||||
if(data.error) {
|
||||
var $warning;
|
||||
if(data.error == 'anonymous_user') {
|
||||
$warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="flag_alert">'+
|
||||
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
|
||||
_t('Sorry you must be logged to flag a post') +
|
||||
'</div>');
|
||||
} else if(data.error == 'post_already_flagged') {
|
||||
$warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="flag_alert">'+
|
||||
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
|
||||
_t('This post is already flagged') +
|
||||
'</div>');
|
||||
} else if(data.error == 'post_non_flaggable') {
|
||||
$warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="flag_alert">'+
|
||||
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
|
||||
_t('This post can not be flagged') +
|
||||
'</div>');
|
||||
}
|
||||
var flag_alert = $link.parent().find("#flag_alert");
|
||||
if (flag_alert.length === 0) {
|
||||
$link.parent().append($warning);
|
||||
}
|
||||
} else if(data.success) {
|
||||
var elem = $link;
|
||||
if(data.success == 'post_flagged_moderator') {
|
||||
elem.html(' Flagged');
|
||||
var c = parseInt($('#count_flagged_posts').html(), 10);
|
||||
c++;
|
||||
$('#count_flagged_posts').html(c);
|
||||
} else if(data.success == 'post_flagged_non_moderator') {
|
||||
elem.html(' Flagged');
|
||||
var forum_answer = elem.closest('.forum_answer');
|
||||
forum_answer.fadeIn(1000);
|
||||
forum_answer.slideUp(1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.vote_up,.vote_down').not('.karma_required').on('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
@@ -278,4 +322,4 @@ if(!$('.website_forum').length) {
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -10,6 +10,7 @@ KARMA = {
|
||||
'edit_own': 10, 'edit_all': 20,
|
||||
'close_own': 10, 'close_all': 20,
|
||||
'unlink_own': 10, 'unlink_all': 20,
|
||||
'post': 100, 'flag': 500, 'moderate': 1000,
|
||||
'gen_que_new': 1, 'gen_que_upv': 5, 'gen_que_dwv': -10,
|
||||
'gen_ans_upv': 10, 'gen_ans_dwv': -20,
|
||||
}
|
||||
@@ -71,6 +72,7 @@ class TestForumCommon(common.SavepointCase):
|
||||
'karma_close_all': KARMA['close_all'],
|
||||
'karma_unlink_own': KARMA['unlink_own'],
|
||||
'karma_unlink_all': KARMA['unlink_all'],
|
||||
'karma_post': KARMA['post'],
|
||||
'karma_comment_convert_all': KARMA['com_conv_all'],
|
||||
'karma_gen_question_new': KARMA['gen_que_new'],
|
||||
'karma_gen_question_upvote': KARMA['gen_que_upv'],
|
||||
|
||||
@@ -34,7 +34,15 @@ class TestForum(TestForumCommon):
|
||||
'forum_id': self.forum.id,
|
||||
'tag_ids': [(0, 0, {'name': 'Tag1', 'forum_id': self.forum.id})]
|
||||
})
|
||||
self.assertEqual(self.user_portal.karma, KARMA['ask'] + KARMA['gen_que_new'], 'website_forum: wrong karma generation when asking question')
|
||||
self.assertEqual(self.user_portal.karma, KARMA['ask'], 'website_forum: wrong karma generation when asking question')
|
||||
|
||||
self.user_portal.karma = KARMA['post']
|
||||
Post.sudo(self.user_portal).create({
|
||||
'name': " Q0",
|
||||
'forum_id': self.forum.id,
|
||||
'tag_ids': [(0, 0, {'name': 'Tag42', 'forum_id': self.forum.id})]
|
||||
})
|
||||
self.assertEqual(self.user_portal.karma, KARMA['post'] + KARMA['gen_que_new'], 'website_forum: wrong karma generation when asking question')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_answer(self):
|
||||
@@ -113,6 +121,97 @@ class TestForum(TestForumCommon):
|
||||
self.post.sudo(self.user_employee).message_post(body='Test1', message_type='comment')
|
||||
self.assertEqual(len(self.post.message_ids), 4, 'website_forum: wrong behavior of message_post')
|
||||
|
||||
def test_flag_a_post(self):
|
||||
Post = self.env['forum.post']
|
||||
self.user_portal.karma = KARMA['ask']
|
||||
post = Post.sudo(self.user_portal).create({
|
||||
'name': "Q0",
|
||||
'forum_id': self.forum.id,
|
||||
})
|
||||
|
||||
# portal user flags a post: not allowed, unsufficient karma
|
||||
with self.assertRaises(KarmaError):
|
||||
post.sudo(self.user_portal).flag()
|
||||
|
||||
# portal user flags a post: ok if enough karma
|
||||
self.user_portal.karma = KARMA['flag']
|
||||
post.state = 'active'
|
||||
post.sudo(self.user_portal).flag()
|
||||
self.assertEqual(post.state, 'flagged', 'website_forum: wrong state when flagging a post')
|
||||
|
||||
def test_validate_a_post(self):
|
||||
Post = self.env['forum.post']
|
||||
self.user_portal.karma = KARMA['ask']
|
||||
post = Post.sudo(self.user_portal).create({
|
||||
'name': "Q0",
|
||||
'forum_id': self.forum.id,
|
||||
})
|
||||
|
||||
# portal user validate a post: not allowed, unsufficient karma
|
||||
with self.assertRaises(KarmaError):
|
||||
post.sudo(self.user_portal).validate()
|
||||
|
||||
# portal user validate a pending post
|
||||
self.user_portal.karma = KARMA['moderate']
|
||||
post.state = 'pending'
|
||||
init_karma = post.create_uid.karma
|
||||
post.sudo(self.user_portal).validate()
|
||||
self.assertEqual(post.state, 'active', 'website_forum: wrong state when validate a post after pending')
|
||||
self.assertEqual(post.create_uid.karma, init_karma + KARMA['gen_que_new'], 'website_forum: wrong karma when validate a post after pending')
|
||||
|
||||
# portal user validate a flagged post: ok if enough karma
|
||||
self.user_portal.karma = KARMA['moderate']
|
||||
post.state = 'flagged'
|
||||
post.sudo(self.user_portal).validate()
|
||||
self.assertEqual(post.state, 'active', 'website_forum: wrong state when validate a post after flagged')
|
||||
|
||||
# portal user validate an offensive post: ok if enough karma
|
||||
self.user_portal.karma = KARMA['moderate']
|
||||
post.state = 'offensive'
|
||||
init_karma = post.create_uid.karma
|
||||
post.sudo(self.user_portal).validate()
|
||||
self.assertEqual(post.state, 'active', 'website_forum: wrong state when validate a post after offensive')
|
||||
|
||||
def test_refuse_a_post(self):
|
||||
Post = self.env['forum.post']
|
||||
self.user_portal.karma = KARMA['ask']
|
||||
post = Post.sudo(self.user_portal).create({
|
||||
'name': "Q0",
|
||||
'forum_id': self.forum.id,
|
||||
})
|
||||
|
||||
# portal user validate a post: not allowed, unsufficient karma
|
||||
with self.assertRaises(KarmaError):
|
||||
post.sudo(self.user_portal).refuse()
|
||||
|
||||
# portal user validate a pending post
|
||||
self.user_portal.karma = KARMA['moderate']
|
||||
post.state = 'pending'
|
||||
init_karma = post.create_uid.karma
|
||||
post.sudo(self.user_portal).refuse()
|
||||
self.assertEqual(post.moderator_id, self.user_portal, 'website_forum: wrong moderator_id when refusing')
|
||||
self.assertEqual(post.create_uid.karma, init_karma, 'website_forum: wrong karma when refusing a post')
|
||||
|
||||
def test_mark_a_post_as_offensive(self):
|
||||
Post = self.env['forum.post']
|
||||
self.user_portal.karma = KARMA['ask']
|
||||
post = Post.sudo(self.user_portal).create({
|
||||
'name': "Q0",
|
||||
'forum_id': self.forum.id,
|
||||
})
|
||||
|
||||
# portal user mark a post as offensive: not allowed, unsufficient karma
|
||||
with self.assertRaises(KarmaError):
|
||||
post.sudo(self.user_portal).mark_as_offensive(12)
|
||||
|
||||
# portal user mark a post as offensive
|
||||
self.user_portal.karma = KARMA['moderate']
|
||||
post.state = 'flagged'
|
||||
init_karma = post.create_uid.karma
|
||||
post.sudo(self.user_portal).mark_as_offensive(12)
|
||||
self.assertEqual(post.state, 'offensive', 'website_forum: wrong state when marking a post as offensive')
|
||||
self.assertEqual(post.create_uid.karma, init_karma + 5 * KARMA['gen_que_dwv'], 'website_forum: wrong karma when marking a post as offensive')
|
||||
|
||||
def test_convert_answer_to_comment_crash(self):
|
||||
Post = self.env['forum.post']
|
||||
|
||||
|
||||
@@ -72,10 +72,10 @@
|
||||
<field name="karma_unlink_own"/>
|
||||
<field name="karma_unlink_all"/>
|
||||
<field name="karma_dofollow"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="karma_answer_accept_own"/>
|
||||
<field name="karma_answer_accept_all"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="karma_editor"/>
|
||||
<field name="karma_comment_own"/>
|
||||
<field name="karma_comment_all"/>
|
||||
@@ -83,6 +83,9 @@
|
||||
<field name="karma_comment_convert_all"/>
|
||||
<field name="karma_comment_unlink_own"/>
|
||||
<field name="karma_comment_unlink_all"/>
|
||||
<field name="karma_post"/>
|
||||
<field name="karma_flag"/>
|
||||
<field name="karma_moderate"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
|
||||
@@ -166,6 +166,16 @@
|
||||
<a t-if="header.get('is_guidelines')" t-attf-href="/forum/#{slug(forum)}" class="fa fa-arrow-right"> Back to <span t-field="forum.name"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default" id="moderation_tools" t-if="user.karma>=forum.karma_moderate">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Moderation Tools</h3>
|
||||
</div>
|
||||
<div class="panel-body text-center">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/validation_queue"><strong><t t-esc="forum.count_posts_waiting_validation"/></strong> waiting for validation</a>
|
||||
<hr style="margin-top:4px;margin-bottom:0;padding-bottom:4px;" />
|
||||
<a t-attf-href="/forum/#{slug(forum)}/flagged_queue"><strong id="count_flagged_posts"><t t-esc="forum.count_flagged_posts"/></strong> post<t t-if="forum.count_flagged_posts>1">s</t> flagged</a>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="header.get('question_data')">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading text-center">
|
||||
@@ -207,6 +217,53 @@
|
||||
</template>
|
||||
|
||||
<!-- Display a post -->
|
||||
<template id="display_post_question_block">
|
||||
<div class="question-block">
|
||||
<div class="question-name">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
<span t-field="question.name"/>
|
||||
</a>
|
||||
<span t-if="not question.active and question.state!='offensive'"><b> [Deleted]</b></span>
|
||||
<span t-if="not question.active and question.state=='offensive'"><b> [Offensive]</b></span>
|
||||
<span t-if="not question.active and question.state=='offensive' and question.closed_reason_id"><b> [<t t-esc="question.closed_reason_id.name[0].upper() + question.closed_reason_id.name[1:]"/>]</b></span>
|
||||
<span t-if="question.state == 'close'"><b> [Closed]</b></span>
|
||||
<t t-if="question.post_type == 'link'" >
|
||||
<a title="Redirect to external link" t-att-href="question.content_link" class="text-muted"><i class="fa fa-external-link forum_link"/></a>
|
||||
</t>
|
||||
<span t-if="question.has_validated_answer" title="Question has accepted answer"><i class="fa fa-check-circle oe_answer_true"/></span>
|
||||
</div>
|
||||
<div><t t-raw="post_content"/></div>
|
||||
<t t-foreach="question.tag_ids" t-as="question_tag">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/tag/#{slug(question_tag)}/questions">
|
||||
<span t-attf-class="pull-right label #{tag and tag.name == question_tag.name and 'label-primary' or 'label-default'}" t-field="question_tag.name"
|
||||
style="margin-right: 4px;"/>
|
||||
</a>
|
||||
</t>
|
||||
<small class="text-muted">
|
||||
<i t-if="question.post_type == 'question'" t-attf-class="fa fa-question-circle" t-att-title="question.post_type.capitalize()" />
|
||||
<i t-if="question.post_type == 'discussion'" t-attf-class="fa fa-comments-o" t-att-title="question.post_type.capitalize()" />
|
||||
<i t-if="question.post_type == 'link'" t-attf-class="fa fa-link" t-att-title="question.post_type.capitalize()" />
|
||||
<span>By </span><span t-field="question.create_uid" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}' style="display: inline-block;"/>
|
||||
<span> • </span><span t-field="question.write_date" t-field-options='{"format":"short"}'/>
|
||||
<span> • </span><span t-field="question.views" /> View<t t-if="question.views>1">s</t>
|
||||
<span> • </span><a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
|
||||
<t t-esc="question.child_count"/>
|
||||
<t t-if="question.post_type == 'question'">
|
||||
Answer<t t-if="question.child_count>1">s</t>
|
||||
</t>
|
||||
<t t-if="question.post_type != 'question'">
|
||||
Comment<t t-if="question.child_count>1">s</t>
|
||||
</t>
|
||||
</a>
|
||||
<t t-if="question.state == 'flagged'">
|
||||
<span> • </span>
|
||||
Flagged
|
||||
</t>
|
||||
</small>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="display_post">
|
||||
<div class="question">
|
||||
<div class="pull-left">
|
||||
@@ -214,44 +271,7 @@
|
||||
<t t-set="post" t-value="question"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="question-block">
|
||||
<div class="question-name">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
<span t-field="question.name"/>
|
||||
</a>
|
||||
<span t-if="not question.active"><b> [Deleted]</b></span>
|
||||
<span t-if="question.state == 'close'"><b> [Closed]</b></span>
|
||||
<t t-if="question.post_type == 'link'" >
|
||||
<a title="Redirect to external link" t-att-href="question.content_link" class="text-muted"><i class="fa fa-external-link forum_link"/></a>
|
||||
</t>
|
||||
<span t-if="question.has_validated_answer" title="Question has accepted answer"><i class="fa fa-check-circle oe_answer_true"/></span>
|
||||
</div>
|
||||
<t t-foreach="question.tag_ids" t-as="question_tag">
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/tag/#{slug(question_tag)}/questions">
|
||||
<span t-attf-class="pull-right label #{tag and tag.name == question_tag.name and 'label-primary' or 'label-default'}" t-field="question_tag.name"
|
||||
style="margin-right: 4px;"/>
|
||||
</a>
|
||||
</t>
|
||||
<small class="text-muted">
|
||||
<i t-if="question.post_type == 'question'" t-attf-class="fa fa-question-circle" t-att-title="question.post_type.capitalize()" />
|
||||
<i t-if="question.post_type == 'discussion'" t-attf-class="fa fa-comments-o" t-att-title="question.post_type.capitalize()" />
|
||||
<i t-if="question.post_type == 'link'" t-attf-class="fa fa-link" t-att-title="question.post_type.capitalize()" />
|
||||
<span>By </span><span t-field="question.create_uid" t-field-options='{"widget": "contact", "country_image": true, "fields": ["name", "country_id"]}' style="display: inline-block;"/>
|
||||
<span> • </span><span t-field="question.write_date" t-field-options='{"format":"short"}'/>
|
||||
<span> • </span><span t-field="question.views" /> View<t t-if="question.views>1">s</t>
|
||||
<span> • </span><a t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">
|
||||
|
||||
|
||||
<t t-esc="question.child_count"/>
|
||||
<t t-if="question.post_type == 'question'">
|
||||
Answer<t t-if="question.child_count>1">s</t>
|
||||
</t>
|
||||
<t t-if="question.post_type != 'question'">
|
||||
Comment<t t-if="question.child_count>1">s</t>
|
||||
</t>
|
||||
</a>
|
||||
</small>
|
||||
</div>
|
||||
<t t-call="website_forum.display_post_question_block"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -272,6 +292,37 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Moderation tools -->
|
||||
<template id="moderation_display_post_question_block">
|
||||
<t t-call="website_forum.display_post_question_block">
|
||||
<t t-set="post_content">
|
||||
<div class="clearfix"><span t-field="question.content" class="oe_no_empty"/></div>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="moderation_display_post_answer">
|
||||
<div class="clearfix">
|
||||
<div class="question-name">
|
||||
<a style="font-size: 15px;" t-attf-href="/forum/#{ slug(forum) }/question/#{ answer.parent_id.id }/#answer-#{ answer.id }" t-esc="answer.parent_id.name"/>
|
||||
<b>[Answer]</b>
|
||||
<span t-if="not answer.active and answer.state=='offensive'"><b> [Offensive]</b></span>
|
||||
<span t-if="not answer.active and answer.state=='offensive' and answer.closed_reason_id"><b> [<t t-esc="answer.closed_reason_id.name[0].upper() + answer.closed_reason_id.name[1:]"/>]</b></span>
|
||||
<t t-if="answer.state == 'flagged'">
|
||||
<small class="text-muted">
|
||||
Flagged
|
||||
</small>
|
||||
</t>
|
||||
<t t-if="len(answer.website_message_ids)>0">
|
||||
(<t t-esc="len(answer.website_message_ids)"/>
|
||||
<t t-if="len(answer.website_message_ids)>1"> Comments</t>
|
||||
<t t-if="len(answer.website_message_ids)<=1"> Comment</t>)
|
||||
</t>
|
||||
</div>
|
||||
<div class="clearfix"><span t-field="answer.content" class="oe_no_empty"/></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- FAQ Layout -->
|
||||
<template id="faq">
|
||||
<t t-call="website_forum.header">
|
||||
@@ -557,18 +608,28 @@
|
||||
<!-- Moderation: close a post -->
|
||||
<template id="close_post">
|
||||
<t t-call="website_forum.header">
|
||||
<h1 class="mt0">Close Post</h1>
|
||||
<p class="text-muted">
|
||||
<h1 class="mt0"><t t-esc="offensive and 'Offensive' or 'Close'"/> Post</h1>
|
||||
<p class="text-muted" t-if="not offensive">
|
||||
If you close this post, it will be hidden for most users. Only
|
||||
users having a high karma can see closed posts to moderate
|
||||
them.
|
||||
</p>
|
||||
<form t-attf-action="/forum/#{ slug(forum) }/question/#{slug(question)}/close" method="post" role="form" class="form-horizontal mt32 mb64 js_website_submit_form">
|
||||
<p class="text-muted" t-if="offensive">
|
||||
If you mark this post as offensive, it will be hidden for most users. Only
|
||||
users having a high karma can see offensive posts to moderate
|
||||
them.
|
||||
</p>
|
||||
<form t-attf-action="/forum/#{ slug(forum) }/#{offensive and 'post' or 'question'}/#{slug(question)}/#{offensive and 'mark_as_offensive' or 'close'}" method="post" role="form" class="form-horizontal mt32 mb64 js_website_submit_form">
|
||||
<input name="post_id" t-att-value="question.id" type="hidden"/>
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label" for="reason">Post:</label>
|
||||
<div class="col-md-8 mt8">
|
||||
<span t-field="question.name"/>
|
||||
<t t-if="not question.parent_id">
|
||||
<span t-field="question.name"/>
|
||||
</t>
|
||||
<t t-if="question.parent_id">
|
||||
<span t-field="question.parent_id.name"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@@ -583,7 +644,7 @@
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-md-offset-3 col-md-8">
|
||||
<button type="submit" class="btn btn-primary">Close post</button>
|
||||
<button type="submit" class="btn btn-primary"><t t-esc="offensive and 'Mark as offensive' or 'Close post'"/></button>
|
||||
<span class="text-muted">or</span>
|
||||
<a class="btn btn-link" t-attf-href="/forum/#{ slug(forum) }/question/#{ slug(question) }">back to post</a>
|
||||
</div>
|
||||
@@ -648,6 +709,13 @@
|
||||
<!-- Specific Post Layout -->
|
||||
<template id="post_description_full" name="Question Navigation">
|
||||
<t t-call="website_forum.header">
|
||||
<div class="alert alert-info text-center" role="alert" t-if="forum and question.state == 'pending' and user.karma>=forum.karma_moderate and question.active">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/validate" type="button" class="btn btn-success">Accept <i class="fa fa-check"/></a>
|
||||
<a t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/refuse" type="button" class="btn btn-danger">Reject <i class="fa fa-times"/></a>
|
||||
</div>
|
||||
<div class="alert alert-info text-center" role="alert" t-if="question.state == 'pending' and user.karma < forum.karma_moderate">
|
||||
Waiting for validation
|
||||
</div>
|
||||
<div t-attf-class="question #{can_bump and 'oe_js_bump' or ''}" t-att-data-type="question.post_type" t-att-data-last-update="question.write_date" t-att-data-id="question.id">
|
||||
<div class="pull-left">
|
||||
<t t-call="website_forum.vote">
|
||||
@@ -670,7 +738,8 @@
|
||||
<t t-esc="question.name"/>
|
||||
</a>
|
||||
</t>
|
||||
<span t-if="not question.active"><b> [Deleted]</b></span>
|
||||
<span t-if="not question.active and question.state!='offensive'"><b> [Deleted]</b></span>
|
||||
<span t-if="not question.active and question.state=='offensive'"><b> [Offensive]</b></span>
|
||||
<span t-if="question.state == 'close'"><b> [Closed]</b></span>
|
||||
</h1>
|
||||
<div class="pull-right">
|
||||
@@ -749,7 +818,7 @@
|
||||
<t t-set="karma" t-value="not question.can_unlink and question.karma_unlink or 0"/>
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="not question.active">
|
||||
<li t-if="not question.active and question.state != 'offensive'">
|
||||
<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="'Reactivate'"/>
|
||||
@@ -757,6 +826,23 @@
|
||||
<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 question.state == 'offensive'">
|
||||
<t t-call="website_forum.link_button">
|
||||
<t t-set="url" t-value="'/forum/' + slug(forum) +'/post/' + slug(question) + '/validate'"/>
|
||||
<t t-set="label" t-value="'Reactivate'"/>
|
||||
<t t-set="classes" t-value="'fa-trash-o'"/>
|
||||
<t t-set="karma" t-value="not question.can_moderate and question.forum_id.karma_moderate or 0"/>
|
||||
</t>
|
||||
</li>
|
||||
<li t-if="question.active">
|
||||
<a t-attf-style="cursor: pointer;#{not question.can_flag and 'color:#999;' or ''}"
|
||||
t-attf-class="flag fa fa-flag #{not question.can_flag and 'karma_required' or ''}"
|
||||
t-attf-data-karma="#{not question.can_flag and question.forum_id.karma_flag or 0}"
|
||||
t-attf-data-href="/forum/#{slug(forum)}/post/#{slug(question)}/flag"
|
||||
>
|
||||
<t class="flag_flagged_label" t-esc="question.state == 'flagged' and 'Flagged' or 'Flag'"/>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-inline mb0">
|
||||
<li t-if="(question.post_type != 'question' or question.post_type == 'question' and not question.uid_has_answered) and question.state != 'close' and question.active != False">
|
||||
@@ -783,9 +869,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div t-foreach="question.child_ids" t-as="post_answer" class="mt16 mb32">
|
||||
<hr class="mb4 mt4"/>
|
||||
<t t-call="website_forum.post_answers">
|
||||
<t t-set="answer" t-value="post_answer"/>
|
||||
<t t-if="post_answer.state != 'flagged' or (post_answer.state == 'flagged' and post_answer.can_moderate)">
|
||||
<hr class="mb4 mt4"/>
|
||||
<t t-call="website_forum.post_answers">
|
||||
<t t-set="answer" t-value="post_answer"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
<div id="post_reply"
|
||||
@@ -848,6 +936,15 @@
|
||||
<t t-set="karma" t-value="not answer.can_unlink and answer.karma_unlink or 0"/>
|
||||
</t>
|
||||
</li>
|
||||
<li>
|
||||
<a t-attf-style="cursor: pointer;#{not answer.can_flag and 'color:#999;' or ''}"
|
||||
t-attf-class="flag fa fa-flag #{not answer.can_flag and 'karma_required' or ''}"
|
||||
t-attf-data-karma="#{not answer.can_flag and answer.forum_id.karma_flag or 0}"
|
||||
t-attf-data-href="/forum/#{slug(forum)}/post/#{slug(answer)}/flag"
|
||||
>
|
||||
<t class="flag_flagged_label" t-esc="answer.state == 'flagged' and 'Flagged' or 'Flag'"/>
|
||||
</a>
|
||||
</li>
|
||||
<li t-if="question.post_type == 'question'">
|
||||
<t t-call="website_forum.link_button">
|
||||
<t t-set="url" t-value="'/forum/' + slug(forum) + '/post/' + slug(answer) + '/convert_to_comment'"/>
|
||||
@@ -1047,6 +1144,59 @@
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="moderation_queue">
|
||||
<t t-call="website_forum.header">
|
||||
<h2 class="page-header mt0">
|
||||
<t t-set="count_posts" t-value="len(posts_ids)"/>
|
||||
<t t-esc="count_posts"/>
|
||||
<span class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
|
||||
<t t-if="queue_type == 'validation'"> Pending</t>
|
||||
<t t-if="queue_type == 'flagged'"> Flagged</t>
|
||||
<t t-if="queue_type == 'offensive'"> Offensive</t>
|
||||
<b class="caret"/>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li t-att-class="queue_type == 'validation' and 'active' or '' ">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/validation_queue">Pending</a>
|
||||
</li>
|
||||
<li t-att-class="queue_type == 'flagged' and 'active' or '' ">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/flagged_queue">Flagged</a>
|
||||
</li>
|
||||
<li t-if="uid" t-att-class="queue_type == 'offensive' and 'active' or '' ">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/offensive_posts">Offensive</a>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
</h2>
|
||||
<div class="text-center" t-if="count_posts == 0">
|
||||
<t t-if="queue_type == 'validation'">The validation queue is empty.</t>
|
||||
<t t-if="queue_type == 'flagged'">The flagged queue is empty.</t>
|
||||
<t t-if="queue_type == 'offensive'">The offensive queue is empty.</t>
|
||||
</div>
|
||||
<div t-foreach="posts_ids" t-as="question" class="mb16 row" style="padding-bottom:16px;border-bottom:1px solid #e7e7e7;">
|
||||
<div class="col-sm-2">
|
||||
<div class="panel panel-default text-center" style="background-color:#eee;margin:0;padding:5px 12px 5px 12px;">
|
||||
<a t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/validate" style="padding-right:8px;"><i class="fa fa-check fa-2x"/></a>
|
||||
<a t-if="queue_type == 'validation'" t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/refuse"><i class="fa fa-times fa-2x"/></a>
|
||||
<a t-if="queue_type == 'flagged'" t-attf-href="/forum/#{slug(forum)}/post/#{slug(question)}/ask_for_mark_as_offensive"><i class="fa fa-times fa-2x"/></a>
|
||||
<a href="#" t-if="queue_type == 'offensive'" disabled="True"><i class="fa fa-times fa-2x" style="color:rgb(179, 179, 179);"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<t t-if="question.parent_id">
|
||||
<div t-foreach="question" t-as="answer" style="display: inline-block;">
|
||||
<t t-call="website_forum.moderation_display_post_answer"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-if="not question.parent_id" style="display: inline-block;margin-left:0;">
|
||||
<t t-call="website_forum.moderation_display_post_question_block"/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</template>
|
||||
|
||||
<template id="users">
|
||||
<t t-call="website_forum.header">
|
||||
<div t-foreach="users" t-as="row_users" class="row mt16">
|
||||
|
||||
Reference in New Issue
Block a user