import controllers
import models
+import tests
# -*- coding: utf-8 -*-
-from datetime import datetime
import werkzeug.urls
import werkzeug.wrappers
-import re
import simplejson
from openerp import tools
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()
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'):
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
# --------------------------------------------------
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)
<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>
# -*- 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. """
_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):
_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,
'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,
'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,
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'
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"
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:
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:
# -*- 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
'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)
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);
}
}
});
- return true;
});
$('.accept_answer').not('.karma_required').on('click', function (ev) {
}
}
});
- return true;
});
$('.favourite_question').on('click', function (ev) {
$link.removeClass("forum_favourite_question")
}
});
- return true;
});
$('.comment_delete').on('click', function (ev) {
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){
--- /dev/null
+# -*- coding: utf-8 -*-
+
+import common
+import test_forum
--- /dev/null
+# -*- 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,
+ })
--- /dev/null
+# -*- 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()
<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"/>
<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">