diff --git a/addons/website_forum/__openerp__.py b/addons/website_forum/__openerp__.py index 741dc3b01b0..d01816c387e 100644 --- a/addons/website_forum/__openerp__.py +++ b/addons/website_forum/__openerp__.py @@ -23,7 +23,7 @@ { 'name': 'Forum', 'category': 'Website', - 'summary': 'Ask Questions and give Answers', + 'summary': 'Forum, FAQ, Q&A', 'version': '1.0', 'description': """ Ask questions, get answers, no distractions diff --git a/addons/website_forum/controllers/main.py b/addons/website_forum/controllers/main.py index efdd0bebc72..bce4c882b5c 100644 --- a/addons/website_forum/controllers/main.py +++ b/addons/website_forum/controllers/main.py @@ -54,10 +54,11 @@ class website_forum(http.Controller): return request.redirect("/forum/%s" % forum_id) @http.route(['/forum/', '/forum//page/'], type='http', auth="public", website=True, multilang=True) - def questions(self, forum, page=1, **searches): + def questions(self, forum, page=1, filters='', sorting='', **searches): cr, uid, context = request.cr, request.uid, request.context Forum = request.registry['website.forum.post'] domain = [('forum_id', '=', forum.id), ('parent_id', '=', False)] + order = "id desc" search = searches.get('search',False) if search: @@ -65,20 +66,26 @@ class website_forum(http.Controller): ('name', 'ilike', search), ('content', 'ilike', search)] - type = searches.get('type',False) - if not type: - searches['type'] = 'all' - if type == 'unanswered': + if not filters: + filters = 'all' + if filters == 'unanswered': domain += [ ('child_ids', '=', False) ] #TODO: update domain to show followed questions of user - if type == 'followed': + if filters == 'followed': domain += [ ('create_uid', '=', uid) ] + if sorting == 'date': + order = 'write_date desc' + if sorting == 'answered': + order = 'child_count desc' + if sorting == 'vote': + order = 'vote_count desc' + step = 10 question_count = Forum.search(cr, uid, domain, count=True, context=context) pager = request.website.pager(url="/forum/%s/" % slug(forum), total=question_count, page=page, step=step, scope=10) - obj_ids = Forum.search(cr, uid, domain, limit=step, offset=pager['offset'], context=context) + obj_ids = Forum.search(cr, uid, domain, limit=step, offset=pager['offset'], order=order, context=context) question_ids = Forum.browse(cr, uid, obj_ids, context=context) values = { @@ -86,6 +93,8 @@ class website_forum(http.Controller): 'question_ids': question_ids, 'forum': forum, 'pager': pager, + 'filters': filters, + 'sorting': sorting, 'searches': searches, } @@ -107,10 +116,11 @@ class website_forum(http.Controller): for answer in question.child_ids: if answer.create_uid.id == request.uid: answer_done = True - post['type'] = 'question' + filters = 'question' values = { 'question': question, 'searches': post, + 'filters': filters, 'answer_done': answer_done, 'reversed': reversed, 'forum': forum, @@ -243,10 +253,9 @@ class website_forum(http.Controller): def tag_questions(self, forum, tag, page=1, **kwargs): cr, uid, context = request.cr, request.uid, request.context Post = request.registry['website.forum.post'] - post_ids = [que.id for que in tag.post_ids] - obj_ids = Post.search(cr, uid, [('forum_id', '=', forum.id), ('id', 'in', post_ids)], context=context) + obj_ids = Post.search(cr, uid, [('forum_id', '=', forum.id), ('tags', '=', tag.id)], context=context) question_ids = Post.browse(cr, uid, obj_ids, context=context) - pager = request.website.pager(url="/forum/%s/tag" % slug(forum), total=len(tag.post_ids), page=page, step=10, scope=10) + pager = request.website.pager(url="/forum/%s/tag" % slug(forum), total=len(obj_ids), page=page, step=10, scope=10) kwargs['tags'] = 'True' values = { @@ -257,7 +266,7 @@ class website_forum(http.Controller): } return request.website.render("website_forum.index", values) - @http.route(['/forum//tags'], type='http', auth="public", website=True, multilang=True) + @http.route(['/forum//tag'], type='http', auth="public", website=True, multilang=True) def tags(self, forum, page=1, **searches): cr, uid, context = request.cr, request.uid, request.context Tag = request.registry['website.forum.tag'] @@ -270,11 +279,11 @@ class website_forum(http.Controller): } return request.website.render("website_forum.tag", values) - @http.route(['/forum//badges'], type='http', auth="public", website=True, multilang=True) + @http.route(['/forum//badge'], type='http', auth="public", website=True, multilang=True) def badges(self, forum, **searches): cr, uid, context = request.cr, request.uid, request.context Badge = request.registry['gamification.badge'] - badge_ids = Badge.search(cr, uid, [('forum', '=', True)], context=context) + badge_ids = Badge.search(cr, uid, [('level', '!=', False)], context=context) badges = Badge.browse(cr, uid, badge_ids, context=context) values = { 'badges': badges, @@ -322,20 +331,7 @@ class website_forum(http.Controller): def post_vote(self, **post): cr, uid, context, post_id = request.cr, request.uid, request.context, int(post.get('post_id')) Vote = request.registry['website.forum.post.vote'] - Post = request.registry['website.forum.post'] - vote_ids = Vote.search(cr, uid, [('post_id', '=', post_id)], context=context) - - if vote_ids: - Vote.unlink(cr, uid, vote_ids, context=context) - else: - Vote.create(cr, uid, { - 'post_id': post_id, - 'user_id': uid, - 'vote': post.get('vote'), - }, context=context) - record = Post.browse(cr, uid, post_id, context=context) - - return record.vote_count + return Vote.vote(cr, uid, post_id, post.get('vote'), context) @http.route('/forum/post_delete/', type='json', auth="user", multilang=True, methods=['POST'], website=True) def delete_answer(self, **kwarg): diff --git a/addons/website_forum/data/forum_badges_data.xml b/addons/website_forum/data/forum_badges_data.xml index 497ebc6d333..b9156289684 100644 --- a/addons/website_forum/data/forum_badges_data.xml +++ b/addons/website_forum/data/forum_badges_data.xml @@ -6,230 +6,191 @@ Autobiographer - Completed all user profile fields - True - gold - - - - Citizen Patrol - First flagged post - True - silver + Completed own biography + bronze Cleanup First rollback - True gold Commentator Posted 10 comments - True gold - + Critic First downvote - True gold Disciplined Deleted own post with 3 or more upvotes - True gold - + Editor First edit - True gold Enlightened First answer was accepted with 3 or more votes - True silver - + Enthusiast - Visited site every day for 30 days in a row - True + Voted on questions/answers for 15 days in a row silver - + Expert - Very active in one tag - True + Posted more than 10 questions or answers in one tag silver Famous Question Asked a question with 500 views - True gold Favorite Question Question favorited by 5 users - True silver Good Answer Answer voted up 6 times - True silver Good Question Question voted up 6 times - True silver Great Answer Answer voted up 15 times - True gold Great Question Question voted up 15 times - True gold Guru Answer accepted with 15 or more votes - True silver Necromancer Answered a question more than 30 days later with at least 2 votes - True silver Nice Answer Answer voted up 4 times - True bronze Nice Quesiotn Question voted up 4 times - True bronze Notable Question Asked a question with 250 views - True silver Organizer First retag - True bronze Peer Pressure Deleted own post with 3 or more downvotes - True gold Popular Question Asked a question with 150 views - True gold Pundit Left 10 comments with score of 10 or more - True silver Scholar Asked a question and accepted an answer - True gold self-Learner Answered own question with at least 4 up votes - True gold Stellar Question Question favorited by 25 users - True bronze Associate Editor Edited 30 entries - True silver Student Asked first question with at least one up vote - True gold Supporter First upvote - True gold Taxonomist Created a tag used by 15 questions - True silver Teacher Received at least 3 upvote for an answer for the first time - True gold diff --git a/addons/website_forum/models/website_forum.py b/addons/website_forum/models/website_forum.py index 0b0fffb24c8..b0aec67da9c 100644 --- a/addons/website_forum/models/website_forum.py +++ b/addons/website_forum/models/website_forum.py @@ -62,7 +62,6 @@ class Post(osv.Model): _name = 'website.forum.post' _description = "Question" _inherit = ['mail.thread', 'website.seo.metadata'] - _order = "id desc" def _get_votes(self, cr, uid, ids, field_name, arg, context): res = dict.fromkeys(ids, False) @@ -94,6 +93,16 @@ class Post(osv.Model): result[vote.post_id.id] = True return result.keys() + def _get_child_count(self, cr, uid, ids, field_name=False, arg={}, context=None): + res = dict.fromkeys(ids, 0) + for post in self.browse(cr, uid, ids, context=context): + if post.parent_id: + res[post.parent_id.id] = len(post.parent_id.child_ids) + return res + + def _get_child(self, cr, uid, ids, context=None): + return ids + _columns = { 'name': fields.char('Title', size=128), 'forum_id': fields.many2one('website.forum', 'Forum', required=True), @@ -114,6 +123,11 @@ class Post(osv.Model): 'parent_id': fields.many2one('website.forum.post', 'Question', ondelete='cascade'), 'child_ids': fields.one2many('website.forum.post', 'parent_id', 'Answers'), + 'child_count':fields.function(_get_child_count, string="Answers", type='integer', + store={ + 'website.forum.post': (_get_child, [], 10), + } + ), 'history_ids': fields.one2many('blog.post.history', 'post_id', 'History', help='Last post modifications'), # TODO FIXME: when website_mail/mail_thread.py inheritance work -> this field won't be necessary @@ -144,26 +158,20 @@ class Post(osv.Model): _defaults = { 'state': 'active', 'vote_count': 0, - 'active': True + 'active': True, } def create_history(self, cr, uid, ids, vals, context=None): - History = self.pool['website.forum.post.history'] - for post in ids: - if vals.get('content'): - create_date = vals.get('create_date') - res = { - 'name': 'Update %s - %s' % (create_date, vals.get('name')), - 'content': vals.get('content', ''), - 'post_id': post - } - if vals.get('version'): - res.update({'version':vals.get('version')}) - - if vals.get('tags'): - res.update({'tags':vals.get('tags')}) - - History.create(cr, uid, res, context=context) + hist_obj = self.pool['website.forum.post.history'] + for post in self.browse(cr, uid, ids, context=context): + hist_obj.create(cr, uid, { + 'post_id': post.id, + 'content': post.content, + 'name': post.name, + 'tags': [(6,0, [x.id for x in post.tags])], + 'date': post.write_date or post.create_date, + 'user_id': post.write_uid and post.write_uid.id or post.create_uid.id + }, context=context) def create(self, cr, uid, vals, context=None): if context is None: @@ -206,7 +214,7 @@ class Users(osv.Model): _columns = { 'create_date': fields.datetime('Create Date', select=True, readonly=True), - 'karma': fields.integer('Karma'), # Use Gamification for this + 'karma': fields.integer('Karma'), # Use a function field for this 'forum': fields.boolean('Is Forum Member'), 'badges': fields.one2many('gamification.badge.user', 'user_id', 'Badges'), @@ -224,11 +232,10 @@ class PostHistory(osv.Model): _description = 'Post History' _inherit = ['website.seo.metadata'] _columns = { - 'name': fields.char('Update Notes', size=64, required=True), + 'name': fields.char('Post Title'), 'post_id': fields.many2one('website.forum.post', 'Post', ondelete='cascade'), - 'create_date': fields.datetime('Created on', select=True, readonly=True), - 'create_uid': fields.many2one('res.users', 'Created by', select=True, readonly=True), - 'version': fields.integer('Version'), + 'date': fields.datetime('Created on', select=True, readonly=True), + 'user_id': fields.many2one('res.users', 'Created by', select=True, readonly=True), 'content': fields.html('Contents', help='Automatically sanitized HTML contents'), 'tags': fields.many2many('website.forum.tag', 'forum_tag_rel', 'forum_id', 'forum_tag_id', 'Tag'), } @@ -239,9 +246,15 @@ class Vote(osv.Model): _columns = { 'post_id': fields.many2one('website.forum.post', 'Post', required=True), 'user_id': fields.many2one('res.users', 'User'), - 'vote': fields.selection([('1', '1'),('-1', '-1')], 'rate'), + 'vote': fields.selection([('1', '1'),('-1', '-1'),('0','0')], 'Vote'), } - + _defaults = { + 'user_id': lambda self, cr, uid, ctx: uid, + 'vote': lambda *args: 1 + } + # TODO: improve this: translate strings _() + # no need to have different text for question/answer + # need different text for upvote, downvote def create(self, cr, uid, vals, context=None): vote_id = super(Vote, self).create(cr, uid, vals, context=context) Post = self.pool["website.forum.post"] @@ -252,33 +265,40 @@ class Vote(osv.Model): Post.message_post(cr, uid, [record.id], body=body, context=context) return vote_id + def vote(self, cr, uid, post_id, vote, context=None): + assert int(vote) in (1, -1, 0), "vote can be -1 or 1, nothing else" + post_obj = self.pool.get('website.forum.post') + vote_ids = self.search(cr, uid, [('post_id', '=', post_id), ('user_id','=',uid)], context=context) + if vote_ids: + self.write(cr, uid, vote_uid, { + 'vote': vote + }, context=context) + else: + self.create(cr, uid, { + 'post_id': post_id, + 'vote': vote, + }, context=context) + return post_obj.browse(cr, uid, post_id, context=context).vote_count + class Badge(osv.Model): _inherit = 'gamification.badge' _columns = { - 'forum': fields.boolean('Is a Forum Badge'), - 'level': fields.selection([('bronze', 'bronze'), ('silver', 'silver'), ('gold', 'gold')], 'Badge Level'), - } - _defaults = { - 'forum': False, - 'level': 'bronze' + 'level': fields.selection([('bronze', 'bronze'), ('silver', 'silver'), ('gold', 'gold')], 'Forum Badge Level'), } class Tags(osv.Model): _name = "website.forum.tag" _description = "Tag" _inherit = ['website.seo.metadata'] - - def _get_questions(self, cr, uid, ids, field_name, arg, context=None): + def _get_posts_count(self, cr, uid, ids, field_name, arg, context=None): result = {} Post = self.pool['website.forum.post'] for tag in ids: - question_ids = Post.search(cr, uid , [('tags.id', '=', tag)], context=context) - result[tag] = question_ids + result[tag] = Post.search_count(cr, uid , [('tags', '=', tag)], context=context) return result _columns = { 'name': fields.char('Name', size=64, required=True), 'forum_id': fields.many2one('website.forum', 'Forum', required=True), - 'post_ids': fields.function(_get_questions, type='many2many', relation="website.forum.post", string="Questions", - ), + 'posts_count': fields.function(_get_posts_count, type='integer', string="# of Posts"), } diff --git a/addons/website_forum/tests/__init__.py b/addons/website_forum/tests/__init__.py index 3dcbf31d714..96bcdb5aa14 100644 --- a/addons/website_forum/tests/__init__.py +++ b/addons/website_forum/tests/__init__.py @@ -19,6 +19,3 @@ # ############################################################################## -#import test_ui - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/website_forum/tests/test_ui.py b/addons/website_forum/tests/test_ui.py deleted file mode 100644 index 8337c357c40..00000000000 --- a/addons/website_forum/tests/test_ui.py +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2013-Today OpenERP SA (). -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## - -import openerp.tests - -class TestUi(openerp.tests.HttpCase): - def test_admin(self): - self.phantom_js("/", "openerp.website.Tour.run_test('question')", "openerp.website.Tour") - diff --git a/addons/website_forum/views/website_forum.xml b/addons/website_forum/views/website_forum.xml index fcb14682a5f..336c4c35ba7 100644 --- a/addons/website_forum/views/website_forum.xml +++ b/addons/website_forum/views/website_forum.xml @@ -35,10 +35,10 @@