[IMP] [TEST] website_forum: security fixes + tests
- fixed voting, karma check could be avoided - fixed posting comments, now correctly checking karma (not for notifications) - fixed bootstraping of users, now not allowed to ask questions by default; added validation email that gives the first karma points required to participate - added tests
This commit is contained in:
parent
bf3251fd0a
commit
ef8099424d
|
@ -2,3 +2,4 @@
|
|||
|
||||
import controllers
|
||||
import models
|
||||
import tests
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime
|
||||
import werkzeug.urls
|
||||
import werkzeug.wrappers
|
||||
import re
|
||||
import simplejson
|
||||
|
||||
from openerp import tools
|
||||
|
@ -13,7 +11,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.translate import _
|
||||
|
||||
controllers = controllers()
|
||||
|
||||
|
@ -35,12 +32,15 @@ class WebsiteForum(http.Controller):
|
|||
|
||||
def _prepare_forum_values(self, forum=None, **kwargs):
|
||||
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()),
|
||||
}
|
||||
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()),
|
||||
'validation_email_sent': request.session.get('validation_email_sent', False),
|
||||
'validation_email_done': request.session.get('validation_email_done', False),
|
||||
}
|
||||
if forum:
|
||||
values['forum'] = forum
|
||||
elif kwargs.get('forum_id'):
|
||||
|
@ -48,6 +48,34 @@ class WebsiteForum(http.Controller):
|
|||
values.update(kwargs)
|
||||
return values
|
||||
|
||||
# User and validation
|
||||
# --------------------------------------------------
|
||||
|
||||
@http.route('/forum/send_validation_email', type='json', auth='user', website=True)
|
||||
def send_validation_email(self, forum_id=None, **kwargs):
|
||||
request.registry['res.users'].send_forum_validation_email(request.cr, request.uid, request.uid, forum_id=forum_id, context=request.context)
|
||||
request.session['validation_email_sent'] = True
|
||||
return True
|
||||
|
||||
@http.route('/forum/validate_email', type='http', auth='public', website=True)
|
||||
def validate_email(self, token, id, email, forum_id=None, **kwargs):
|
||||
if forum_id:
|
||||
try:
|
||||
forum_id = int(forum_id)
|
||||
except ValueError:
|
||||
forum_id = None
|
||||
done = request.registry['res.users'].process_forum_validation_token(request.cr, request.uid, token, int(id), email, forum_id=forum_id, context=request.context)
|
||||
if done:
|
||||
request.session['validation_email_done'] = True
|
||||
if forum_id:
|
||||
return request.redirect("/forum/%s" % int(forum_id))
|
||||
return request.redirect('/forum')
|
||||
|
||||
@http.route('/forum/validate_email/close', type='json', auth='public', website=True)
|
||||
def validate_email_done(self):
|
||||
request.session['validation_email_done'] = False
|
||||
return True
|
||||
|
||||
# Forum
|
||||
# --------------------------------------------------
|
||||
|
||||
|
@ -298,10 +326,12 @@ class WebsiteForum(http.Controller):
|
|||
cr, uid, context = request.cr, request.uid, request.context
|
||||
if kwargs.get('comment') and post.forum_id.id == forum.id:
|
||||
# TDE FIXME: check that post_id is the question or one of its answers
|
||||
request.registry['forum.post']._post_comment(
|
||||
cr, uid, post,
|
||||
request.registry['forum.post'].message_post(
|
||||
cr, uid, post.id,
|
||||
body=kwargs.get('comment'),
|
||||
context=context)
|
||||
type='comment',
|
||||
subtype='mt_comment',
|
||||
context=dict(context, mail_create_nosubcribe=True))
|
||||
return werkzeug.utils.redirect("/forum/%s/question/%s" % (slug(forum), slug(question)))
|
||||
|
||||
@http.route('/forum/<model("forum.forum"):forum>/post/<model("forum.post"):post>/toggle_correct', type='json', auth="public", website=True)
|
||||
|
|
|
@ -100,5 +100,36 @@
|
|||
<field name="name">too localized</field>
|
||||
</record>
|
||||
|
||||
<!-- Email template for email validation (for karma purpose) -->
|
||||
<record id="validation_email" model="email.template">
|
||||
<field name="name">Email Verification</field>
|
||||
<field name="model_id" ref="base.model_res_users"/>
|
||||
<field name="email_from"><![CDATA[${object.company_id.name} <${(object.company_id.email or user.email)|safe}>]]></field>
|
||||
<field name="email_to">${object.email|safe}</field>
|
||||
<field name="subject"><![CDATA[${object.company_id.name} Forums validation]]></field>
|
||||
<field name="body_html"><![CDATA[
|
||||
<p>
|
||||
Hello ${object.name},
|
||||
</p>
|
||||
<p>
|
||||
You have been invited to validate your email in order to get access to "${object.company_id.name}" Q/A Forums.
|
||||
</p>
|
||||
<p>
|
||||
To validate your email, please click on the following link:
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="${ctx.get('token_url')}">Validate my account for "${object.company_id.name}" Q/A Forums</a></li>
|
||||
</ul>
|
||||
<p>
|
||||
Thanks,
|
||||
</p>
|
||||
<pre>
|
||||
--
|
||||
${object.company_id.name or ''}
|
||||
${object.company_id.email or ''}
|
||||
${object.company_id.phone or ''}
|
||||
</pre>]]></field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime
|
||||
import uuid
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
import openerp
|
||||
from openerp import tools
|
||||
from openerp import api, tools
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.addons.website.models.website import slug
|
||||
from openerp.exceptions import Warning
|
||||
from openerp.osv import osv, fields
|
||||
from openerp.tools import html2plaintext
|
||||
from openerp.tools.translate import _
|
||||
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
class KarmaError(Forbidden):
|
||||
""" Karma-related error, used for forum and posts. """
|
||||
|
@ -23,42 +25,48 @@ class Forum(osv.Model):
|
|||
_description = 'Forums'
|
||||
_inherit = ['mail.thread', 'website.seo.metadata']
|
||||
|
||||
def init(self, cr):
|
||||
""" Add forum uuid for user email validation. """
|
||||
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'])
|
||||
|
||||
_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_gen_question_new': fields.integer('Asking a question'),
|
||||
'karma_gen_question_upvote': fields.integer('Question upvoted'),
|
||||
'karma_gen_question_downvote': fields.integer('Question downvoted'),
|
||||
'karma_gen_answer_upvote': fields.integer('Answer upvoted'),
|
||||
'karma_gen_answer_downvote': fields.integer('Answer downvoted'),
|
||||
'karma_gen_answer_accept': fields.integer('Accepting an answer'),
|
||||
'karma_gen_answer_accepted': fields.integer('Answer accepted'),
|
||||
'karma_gen_answer_flagged': fields.integer('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'),
|
||||
'karma_ask': fields.integer('Ask a question'),
|
||||
'karma_answer': fields.integer('Answer a question'),
|
||||
'karma_edit_own': fields.integer('Edit its own posts'),
|
||||
'karma_edit_all': fields.integer('Edit all posts'),
|
||||
'karma_close_own': fields.integer('Close its own posts'),
|
||||
'karma_close_all': fields.integer('Close all posts'),
|
||||
'karma_unlink_own': fields.integer('Delete its own posts'),
|
||||
'karma_unlink_all': fields.integer('Delete all posts'),
|
||||
'karma_upvote': fields.integer('Upvote'),
|
||||
'karma_downvote': fields.integer('Downvote'),
|
||||
'karma_answer_accept_own': fields.integer('Accept an answer on its own questions'),
|
||||
'karma_answer_accept_all': fields.integer('Accept an answer to all questions'),
|
||||
'karma_editor_link_files': fields.integer('Linking files (Editor)'),
|
||||
'karma_editor_clickable_link': fields.integer('Clickable links (Editor)'),
|
||||
'karma_comment_own': fields.integer('Comment its own posts'),
|
||||
'karma_comment_all': fields.integer('Comment all posts'),
|
||||
'karma_comment_convert_own': fields.integer('Convert its own answers to comments and vice versa'),
|
||||
'karma_comment_convert_all': fields.integer('Convert all answers to comments and vice versa'),
|
||||
'karma_comment_unlink_own': fields.integer('Unlink its own comments'),
|
||||
'karma_comment_unlink_all': fields.integer('Unlink all comments'),
|
||||
'karma_retag': fields.integer('Change question tags'),
|
||||
'karma_flag': fields.integer('Flag a post as offensive'),
|
||||
}
|
||||
|
||||
def _get_default_faq(self, cr, uid, context=None):
|
||||
|
@ -70,7 +78,7 @@ 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_new': 0, # set to null for anti spam protection
|
||||
'karma_gen_question_upvote': 5,
|
||||
'karma_gen_question_downvote': -2,
|
||||
'karma_gen_answer_upvote': 10,
|
||||
|
@ -78,8 +86,8 @@ class Forum(osv.Model):
|
|||
'karma_gen_answer_accept': 2,
|
||||
'karma_gen_answer_accepted': 15,
|
||||
'karma_gen_answer_flagged': -100,
|
||||
'karma_ask': 0,
|
||||
'karma_answer': 0,
|
||||
'karma_ask': 3, # set to not null for anti spam protection
|
||||
'karma_answer': 3, # set to not null for anti spam protection
|
||||
'karma_edit_own': 1,
|
||||
'karma_edit_all': 300,
|
||||
'karma_close_own': 100,
|
||||
|
@ -92,8 +100,8 @@ class Forum(osv.Model):
|
|||
'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_own': 3,
|
||||
'karma_comment_all': 5,
|
||||
'karma_comment_convert_own': 50,
|
||||
'karma_comment_convert_all': 500,
|
||||
'karma_comment_unlink_own': 50,
|
||||
|
@ -393,13 +401,6 @@ class Post(osv.Model):
|
|||
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)
|
||||
new_vote = '1' if upvote else '-1'
|
||||
|
@ -509,15 +510,18 @@ class Post(osv.Model):
|
|||
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)
|
||||
|
||||
def _post_comment(self, cr, uid, post, body, context=None):
|
||||
context = dict(context or {}, mail_create_nosubcribe=True)
|
||||
if not post.can_comment:
|
||||
raise KarmaError('Not enough karma to comment')
|
||||
return self.message_post(cr, uid, post.id,
|
||||
body=body,
|
||||
type='comment',
|
||||
subtype='mt_comment',
|
||||
context=context)
|
||||
@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)
|
||||
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(osv.Model):
|
||||
_name = "forum.post.reason"
|
||||
|
@ -557,6 +561,17 @@ class Vote(osv.Model):
|
|||
def create(self, cr, uid, vals, context=None):
|
||||
vote_id = super(Vote, self).create(cr, uid, vals, context=context)
|
||||
vote = self.browse(cr, uid, vote_id, context=context)
|
||||
|
||||
# own post check
|
||||
if vote.user_id.id == vote.post_id.create_uid.id:
|
||||
raise Warning('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.')
|
||||
|
||||
# karma update
|
||||
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:
|
||||
|
@ -567,6 +582,16 @@ class Vote(osv.Model):
|
|||
def write(self, cr, uid, ids, values, context=None):
|
||||
if 'vote' in values:
|
||||
for vote in self.browse(cr, uid, ids, context=context):
|
||||
# own post check
|
||||
if vote.user_id.id == vote.post_id.create_uid.id:
|
||||
raise Warning('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:
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from datetime import datetime
|
||||
from urllib import urlencode
|
||||
|
||||
import hashlib
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
class Users(osv.Model):
|
||||
_inherit = 'res.users'
|
||||
|
||||
def __init__(self, pool, cr):
|
||||
init_res = super(Users, self).__init__(pool, cr)
|
||||
self.SELF_WRITEABLE_FIELDS = list(set(
|
||||
self.SELF_WRITEABLE_FIELDS + \
|
||||
init_res = super(Users, self).__init__(pool, cr)
|
||||
self.SELF_WRITEABLE_FIELDS = list(
|
||||
set(
|
||||
self.SELF_WRITEABLE_FIELDS +
|
||||
['country_id', 'city', 'website', 'website_description', 'website_published']))
|
||||
return init_res
|
||||
|
||||
|
@ -37,6 +45,50 @@ class Users(osv.Model):
|
|||
'karma': 0,
|
||||
}
|
||||
|
||||
def _generate_forum_token(self, cr, uid, user_id, email):
|
||||
"""Return a token for email validation. This token is valid for the day
|
||||
and is a hash based on a (secret) uuid generated by the forum module,
|
||||
the user_id, the email and currently the day (to be updated if necessary). """
|
||||
forum_uuid = self.pool.get('ir.config_parameter').get_param(cr, SUPERUSER_ID, 'website_forum.uuid')
|
||||
return hashlib.sha256('%s-%s-%s-%s' % (
|
||||
datetime.now().replace(hour=0, minute=0, second=0, microsecond=0),
|
||||
forum_uuid,
|
||||
user_id,
|
||||
email)).hexdigest()
|
||||
|
||||
def send_forum_validation_email(self, cr, uid, user_id, forum_id=None, context=None):
|
||||
user = self.pool['res.users'].browse(cr, uid, user_id, context=context)
|
||||
token = self._generate_forum_token(cr, uid, user_id, user.email)
|
||||
activation_template_id = self.pool['ir.model.data'].xmlid_to_res_id(cr, uid, 'website_forum.validation_email')
|
||||
if activation_template_id:
|
||||
params = {
|
||||
'token': token,
|
||||
'id': user_id,
|
||||
'email': user.email}
|
||||
if forum_id:
|
||||
params['forum_id'] = forum_id
|
||||
base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url')
|
||||
token_url = base_url + '/forum/validate_email?%s' % urlencode(params)
|
||||
tpl_ctx = dict(context, token_url=token_url)
|
||||
self.pool['email.template'].send_mail(cr, SUPERUSER_ID, activation_template_id, user_id, force_send=True, context=tpl_ctx)
|
||||
return True
|
||||
|
||||
def process_forum_validation_token(self, cr, uid, token, user_id, email, forum_id=None, context=None):
|
||||
validation_token = self.pool['res.users']._generate_forum_token(cr, uid, user_id, email)
|
||||
user = self.pool['res.users'].browse(cr, SUPERUSER_ID, user_id, context=context)
|
||||
if token == validation_token and user.karma == 0:
|
||||
karma = 3
|
||||
if not forum_id:
|
||||
forum_ids = self.pool['forum.forum'].search(cr, uid, [], limit=1, context=context)
|
||||
if forum_ids:
|
||||
forum_id = forum_ids[0]
|
||||
if forum_id:
|
||||
forum = self.pool['forum.forum'].browse(cr, uid, forum_id, context=context)
|
||||
# karma gained: karma to ask a question and have 2 downvotes
|
||||
karma = forum.karma_ask + (-2 * forum.karma_gen_question_downvote)
|
||||
return user.write({'karma': karma})
|
||||
return False
|
||||
|
||||
def add_karma(self, cr, uid, ids, karma, context=None):
|
||||
for user in self.browse(cr, uid, ids, context=context):
|
||||
self.write(cr, uid, [user.id], {'karma': user.karma + karma}, context=context)
|
||||
|
|
|
@ -6,8 +6,8 @@ $(document).ready(function () {
|
|||
ev.preventDefault();
|
||||
var $warning = $('<div class="alert alert-danger alert-dismissable oe_forum_alert" id="karma_alert">'+
|
||||
'<button type="button" class="close notification_close" data-dismiss="alert" aria-hidden="true">×</button>'+
|
||||
karma + ' karma is required to perform this action. You can earn karma by answering questions or having '+
|
||||
'your answers upvoted by the community.</div>');
|
||||
karma + ' karma is required to perform this action. You can earn karma by having '+
|
||||
'your answers upvoted by the community.</div>');
|
||||
var vote_alert = $(ev.currentTarget).parent().find("#vote_alert");
|
||||
if (vote_alert.length == 0) {
|
||||
$(ev.currentTarget).parent().append($warning);
|
||||
|
@ -50,7 +50,6 @@ $(document).ready(function () {
|
|||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
$('.accept_answer').not('.karma_required').on('click', function (ev) {
|
||||
|
@ -76,7 +75,6 @@ $(document).ready(function () {
|
|||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
$('.favourite_question').on('click', function (ev) {
|
||||
|
@ -89,7 +87,6 @@ $(document).ready(function () {
|
|||
$link.removeClass("forum_favourite_question")
|
||||
}
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
$('.comment_delete').on('click', function (ev) {
|
||||
|
@ -98,15 +95,29 @@ $(document).ready(function () {
|
|||
openerp.jsonRpc($link.data('href'), 'call', {}).then(function (data) {
|
||||
$link.parents('.comment').first().remove();
|
||||
});
|
||||
return true;
|
||||
});
|
||||
|
||||
$('.notification_close').on('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
openerp.jsonRpc("/forum/notification_read", 'call', {
|
||||
'notification_id': $link.attr("id")})
|
||||
return true;
|
||||
'notification_id': $link.attr("id")});
|
||||
});
|
||||
|
||||
$('.send_validation_email').on('click', function (ev) {
|
||||
ev.preventDefault();
|
||||
var $link = $(ev.currentTarget);
|
||||
openerp.jsonRpc("/forum/send_validation_email", 'call', {
|
||||
'forum_id': $link.attr('forum-id'),
|
||||
}).then(function (data) {
|
||||
if (data) {
|
||||
$('button.validation_email_close').click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$('.validated_email_close').on('click', function (ev) {
|
||||
openerp.jsonRpc("/forum/validate_email/close", 'call', {});
|
||||
});
|
||||
|
||||
if($('input.load_tags').length){
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
import common
|
||||
import test_forum
|
|
@ -0,0 +1,93 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.tests import common
|
||||
|
||||
KARMA = {
|
||||
'ask': 5, 'ans': 10,
|
||||
'com_own': 5, 'com_all': 10,
|
||||
'com_conv_all': 50,
|
||||
'upv': 5, 'dwv': 10,
|
||||
'edit_own': 10, 'edit_all': 20,
|
||||
'close_own': 10, 'close_all': 20,
|
||||
'unlink_own': 10, 'unlink_all': 20,
|
||||
'gen_que_new': 1, 'gen_que_upv': 5, 'gen_que_dwv': -10,
|
||||
'gen_ans_upv': 10, 'gen_ans_dwv': -20,
|
||||
}
|
||||
|
||||
|
||||
class TestForumCommon(common.TransactionCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestForumCommon, self).setUp()
|
||||
|
||||
Forum = self.env['forum.forum']
|
||||
Post = self.env['forum.post']
|
||||
|
||||
# Test users
|
||||
TestUsersEnv = self.env['res.users'].with_context({'no_reset_password': True})
|
||||
group_employee_id = self.ref('base.group_user')
|
||||
group_portal_id = self.ref('base.group_portal')
|
||||
group_public_id = self.ref('base.group_public')
|
||||
self.user_employee = TestUsersEnv.create({
|
||||
'name': 'Armande Employee',
|
||||
'login': 'Armande',
|
||||
'alias_name': 'armande',
|
||||
'email': 'armande.employee@example.com',
|
||||
'karma': 0,
|
||||
'groups_id': [(6, 0, [group_employee_id])]
|
||||
})
|
||||
self.user_portal = TestUsersEnv.create({
|
||||
'name': 'Beatrice Portal',
|
||||
'login': 'Beatrice',
|
||||
'alias_name': 'beatrice',
|
||||
'email': 'beatrice.employee@example.com',
|
||||
'karma': 0,
|
||||
'groups_id': [(6, 0, [group_portal_id])]
|
||||
})
|
||||
self.user_public = TestUsersEnv.create({
|
||||
'name': 'Cedric Public',
|
||||
'login': 'Cedric',
|
||||
'alias_name': 'cedric',
|
||||
'email': 'cedric.employee@example.com',
|
||||
'karma': 0,
|
||||
'groups_id': [(6, 0, [group_public_id])]
|
||||
})
|
||||
|
||||
# Test forum
|
||||
self.forum = Forum.create({
|
||||
'name': 'TestForum',
|
||||
'karma_ask': KARMA['ask'],
|
||||
'karma_answer': KARMA['ans'],
|
||||
'karma_comment_own': KARMA['com_own'],
|
||||
'karma_comment_all': KARMA['com_all'],
|
||||
'karma_answer_accept_own': 9999,
|
||||
'karma_answer_accept_all': 9999,
|
||||
'karma_upvote': KARMA['upv'],
|
||||
'karma_downvote': KARMA['dwv'],
|
||||
'karma_edit_own': KARMA['edit_own'],
|
||||
'karma_edit_all': KARMA['edit_all'],
|
||||
'karma_close_own': KARMA['close_own'],
|
||||
'karma_close_all': KARMA['close_all'],
|
||||
'karma_unlink_own': KARMA['unlink_own'],
|
||||
'karma_unlink_all': KARMA['unlink_all'],
|
||||
'karma_comment_convert_all': KARMA['com_conv_all'],
|
||||
'karma_gen_question_new': KARMA['gen_que_new'],
|
||||
'karma_gen_question_upvote': KARMA['gen_que_upv'],
|
||||
'karma_gen_question_downvote': KARMA['gen_que_dwv'],
|
||||
'karma_gen_answer_upvote': KARMA['gen_ans_upv'],
|
||||
'karma_gen_answer_downvote': KARMA['gen_ans_dwv'],
|
||||
'karma_gen_answer_accept': 9999,
|
||||
'karma_gen_answer_accepted': 9999,
|
||||
})
|
||||
self.post = Post.create({
|
||||
'name': 'TestQuestion',
|
||||
'content': 'I am not a bird.',
|
||||
'forum_id': self.forum.id,
|
||||
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': self.forum.id})]
|
||||
})
|
||||
self.answer = Post.create({
|
||||
'name': 'TestAnswer',
|
||||
'content': 'I am an anteater.',
|
||||
'forum_id': self.forum.id,
|
||||
'parent_id': self.post.id,
|
||||
})
|
|
@ -0,0 +1,180 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from openerp.addons.website_forum.tests.common import KARMA, TestForumCommon
|
||||
from openerp.addons.website_forum.models.forum import KarmaError
|
||||
from openerp.exceptions import Warning, AccessError
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
class TestForum(TestForumCommon):
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_ask(self):
|
||||
Post = self.env['forum.post']
|
||||
|
||||
# Public user asks a question: not allowed
|
||||
with self.assertRaises(AccessError):
|
||||
Post.sudo(self.user_public).create({
|
||||
'name': " Question ?",
|
||||
'forum_id': self.forum.id,
|
||||
})
|
||||
|
||||
# Portal user asks a question with tags: not allowed, unsufficient karma
|
||||
with self.assertRaises(KarmaError):
|
||||
Post.sudo(self.user_portal).create({
|
||||
'name': " Q_0",
|
||||
'forum_id': self.forum.id,
|
||||
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': self.forum.id})]
|
||||
})
|
||||
|
||||
# Portal user asks a question with tags: ok if enough karma
|
||||
self.user_portal.karma = KARMA['ask']
|
||||
Post.sudo(self.user_portal).create({
|
||||
'name': " Q0",
|
||||
'forum_id': self.forum.id,
|
||||
'tag_ids': [(0, 0, {'name': 'Tag0', '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')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_answer(self):
|
||||
Post = self.env['forum.post']
|
||||
|
||||
# Answers its own question: not allowed, unsufficient karma
|
||||
with self.assertRaises(KarmaError):
|
||||
Post.sudo(self.user_employee).create({
|
||||
'name': " A0",
|
||||
'forum_id': self.forum.id,
|
||||
'parent_id': self.post.id,
|
||||
})
|
||||
|
||||
# Answers on question: ok if enough karma
|
||||
self.user_employee.karma = KARMA['ans']
|
||||
Post.sudo(self.user_employee).create({
|
||||
'name': " A0",
|
||||
'forum_id': self.forum.id,
|
||||
'parent_id': self.post.id,
|
||||
})
|
||||
self.assertEqual(self.user_employee.karma, KARMA['ans'], 'website_forum: wrong karma generation when answering question')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_vote_crash(self):
|
||||
Post = self.env['forum.post']
|
||||
self.user_employee.karma = KARMA['ans']
|
||||
emp_answer = Post.sudo(self.user_employee).create({
|
||||
'name': 'TestAnswer',
|
||||
'forum_id': self.forum.id,
|
||||
'parent_id': self.post.id})
|
||||
|
||||
# upvote its own post
|
||||
with self.assertRaises(Warning):
|
||||
emp_answer.vote(upvote=True)
|
||||
|
||||
# not enough karma
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).vote(upvote=True)
|
||||
|
||||
def test_vote(self):
|
||||
self.post.create_uid.karma = KARMA['ask']
|
||||
self.user_portal.karma = KARMA['upv']
|
||||
self.post.sudo(self.user_portal).vote(upvote=True)
|
||||
self.assertEqual(self.post.create_uid.karma, KARMA['ask'] + KARMA['gen_que_upv'], 'website_forum: wrong karma generation of upvoted question author')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
|
||||
def test_downvote_crash(self):
|
||||
Post = self.env['forum.post']
|
||||
self.user_employee.karma = KARMA['ans']
|
||||
emp_answer = Post.sudo(self.user_employee).create({
|
||||
'name': 'TestAnswer',
|
||||
'forum_id': self.forum.id,
|
||||
'parent_id': self.post.id})
|
||||
|
||||
# downvote its own post
|
||||
with self.assertRaises(Warning):
|
||||
emp_answer.vote(upvote=False)
|
||||
|
||||
# not enough karma
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).vote(upvote=False)
|
||||
|
||||
def test_downvote(self):
|
||||
self.post.create_uid.karma = 50
|
||||
self.user_portal.karma = KARMA['dwv']
|
||||
self.post.sudo(self.user_portal).vote(upvote=False)
|
||||
self.assertEqual(self.post.create_uid.karma, 50 + KARMA['gen_que_dwv'], 'website_forum: wrong karma generation of downvoted question author')
|
||||
|
||||
def test_comment_crash(self):
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).message_post(body='Should crash', type='comment')
|
||||
|
||||
def test_comment(self):
|
||||
self.post.sudo(self.user_employee).message_post(body='Test0', type='notification')
|
||||
self.user_employee.karma = KARMA['com_all']
|
||||
self.post.sudo(self.user_employee).message_post(body='Test1', type='comment')
|
||||
self.assertEqual(len(self.post.message_ids), 4, 'website_forum: wrong behavior of message_post')
|
||||
|
||||
def test_convert_answer_to_comment_crash(self):
|
||||
Post = self.env['forum.post']
|
||||
|
||||
# converting a question does nothing
|
||||
msg_ids = self.post.sudo(self.user_portal).convert_answer_to_comment()
|
||||
self.assertEqual(msg_ids[0], False, 'website_forum: question to comment conversion failed')
|
||||
self.assertEqual(Post.search([('name', '=', 'TestQuestion')])[0].forum_id.name, 'TestForum', 'website_forum: question to comment conversion failed')
|
||||
|
||||
with self.assertRaises(KarmaError):
|
||||
self.answer.sudo(self.user_portal).convert_answer_to_comment()
|
||||
|
||||
def test_convert_answer_to_comment(self):
|
||||
self.user_portal.karma = KARMA['com_conv_all']
|
||||
post_author = self.answer.create_uid.partner_id
|
||||
msg_ids = self.answer.sudo(self.user_portal).convert_answer_to_comment()
|
||||
self.assertEqual(len(msg_ids), 1, 'website_forum: wrong answer to comment conversion')
|
||||
msg = self.env['mail.message'].browse(msg_ids[0])
|
||||
self.assertEqual(msg.author_id, post_author, 'website_forum: wrong answer to comment conversion')
|
||||
self.assertIn('I am an anteater', msg.body, 'website_forum: wrong answer to comment conversion')
|
||||
|
||||
def test_edit_post_crash(self):
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).write({'name': 'I am not your father.'})
|
||||
|
||||
def test_edit_post(self):
|
||||
self.post.create_uid.karma = KARMA['edit_own']
|
||||
self.post.write({'name': 'Actually I am your dog.'})
|
||||
self.user_portal.karma = KARMA['edit_all']
|
||||
self.post.sudo(self.user_portal).write({'name': 'Actually I am your cat.'})
|
||||
|
||||
def test_close_post_crash(self):
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).close(None)
|
||||
|
||||
def test_close_post_own(self):
|
||||
self.post.create_uid.karma = KARMA['close_own']
|
||||
self.post.close(None)
|
||||
|
||||
def test_close_post_all(self):
|
||||
self.user_portal.karma = KARMA['close_all']
|
||||
self.post.sudo(self.user_portal).close(None)
|
||||
|
||||
def test_deactivate_post_crash(self):
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).write({'active': False})
|
||||
|
||||
def test_deactivate_post_own(self):
|
||||
self.post.create_uid.karma = KARMA['unlink_own']
|
||||
self.post.write({'active': False})
|
||||
|
||||
def test_deactivate_post_all(self):
|
||||
self.user_portal.karma = KARMA['unlink_all']
|
||||
self.post.sudo(self.user_portal).write({'active': False})
|
||||
|
||||
def test_unlink_post_crash(self):
|
||||
with self.assertRaises(KarmaError):
|
||||
self.post.sudo(self.user_portal).unlink()
|
||||
|
||||
def test_unlink_post_own(self):
|
||||
self.post.create_uid.karma = KARMA['unlink_own']
|
||||
self.post.unlink()
|
||||
|
||||
def test_unlink_post_all(self):
|
||||
self.user_portal.karma = KARMA['unlink_all']
|
||||
self.post.sudo(self.user_portal).unlink()
|
|
@ -24,28 +24,45 @@
|
|||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="karma_ask"/>
|
||||
<field name="karma_edit_own"/>
|
||||
<field name="karma_edit_all"/>
|
||||
<field name="karma_close_own"/>
|
||||
<field name="karma_close_all"/>
|
||||
<field name="karma_unlink_own"/>
|
||||
<field name="karma_unlink_all"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="karma_upvote"/>
|
||||
<field name="karma_downvote"/>
|
||||
<field name="karma_answer_accept_own"/>
|
||||
<field name="karma_answer_accept_all"/>
|
||||
<field name="karma_editor_link_files"/>
|
||||
<field name="karma_editor_clickable_link"/>
|
||||
<field name="karma_comment_own"/>
|
||||
<field name="karma_comment_all"/>
|
||||
<field name="karma_comment_convert_own"/>
|
||||
<field name="karma_comment_convert_all"/>
|
||||
<field name="karma_comment_unlink_own"/>
|
||||
<field name="karma_comment_unlink_all"/>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string='Karma Gains'>
|
||||
<group>
|
||||
<field name="karma_gen_question_new"/>
|
||||
<field name="karma_gen_question_upvote"/>
|
||||
<field name="karma_gen_question_downvote"/>
|
||||
<field name="karma_gen_answer_upvote"/>
|
||||
<field name="karma_gen_answer_downvote"/>
|
||||
<field name="karma_gen_answer_accept"/>
|
||||
<field name="karma_gen_answer_accepted"/>
|
||||
</group>
|
||||
</page>
|
||||
<page string='Karma Requirements'>
|
||||
<group>
|
||||
<group>
|
||||
<field name="karma_ask"/>
|
||||
<field name="karma_upvote"/>
|
||||
<field name="karma_downvote"/>
|
||||
<field name="karma_edit_own"/>
|
||||
<field name="karma_edit_all"/>
|
||||
<field name="karma_close_own"/>
|
||||
<field name="karma_close_all"/>
|
||||
<field name="karma_unlink_own"/>
|
||||
<field name="karma_unlink_all"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="karma_answer_accept_own"/>
|
||||
<field name="karma_answer_accept_all"/>
|
||||
<field name="karma_comment_own"/>
|
||||
<field name="karma_comment_all"/>
|
||||
<field name="karma_comment_convert_own"/>
|
||||
<field name="karma_comment_convert_all"/>
|
||||
<field name="karma_comment_unlink_own"/>
|
||||
<field name="karma_comment_unlink_all"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids" widget="mail_followers" groups="base.group_user"/>
|
||||
|
|
|
@ -96,6 +96,21 @@
|
|||
<div t-field="notification.body"/>
|
||||
<a t-attf-href="/forum/#{ slug(forum) }/user/#{ user.id }#badges" class="fa fa-arrow-right">View Your Badges</a>
|
||||
</div>
|
||||
<div t-if="not validation_email_sent and not is_public_user and user.karma == 0" class="alert alert-danger alert-dismissable">
|
||||
<button type="button" class="close validation_email_close" data-dismiss="alert" aria-hidden="true">&times;</button>
|
||||
<div>
|
||||
<p>
|
||||
It appears your email has not been verified.
|
||||
<a class="send_validation_email" href="#" t-att-forum-id="forum.id">Click here to send a verification email allowing you to participate to the forum.</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div t-if="validation_email_done" class="alert alert-success alert-dismissable">
|
||||
<button type="button" class="close validated_email_close" data-dismiss="alert" aria-hidden="true">&times;</button>
|
||||
<div>
|
||||
<p>Congratulations! Your email has just been validated. You may now participate to our forums.</p>
|
||||
</div>
|
||||
</div>
|
||||
<t t-raw="0"/>
|
||||
</div>
|
||||
<div class="col-sm-3" id="right-column">
|
||||
|
|
Loading…
Reference in New Issue