1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2013-Today OpenERP SA (<http://www.openerp.com>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
25 from openerp import tools
26 from openerp import SUPERUSER_ID
27 from openerp.osv import osv, fields
28 from openerp.tools.translate import _
29 from datetime import date, datetime
31 from openerp.addons.website.models.website import slug
33 #TODO: Do we need a forum object like blog object ? Need to check with BE team
34 class Forum(osv.Model):
35 _name = 'website.forum'
36 _description = 'Forums'
37 _inherit = ['mail.thread', 'website.seo.metadata']
39 def _get_right_column(self, cr, uid, ids, field_name, arg, context):
41 for forum in self.browse(cr, uid, ids, context=context):
42 res[forum.id] = """<div class="panel panel-default">
43 <div class="panel-heading">
44 <h3 class="panel-title">About This Forum</h3>
46 <div class="panel-body">
47 This community is for professionals and enthusiasts of our
48 products and services.<br/>
49 <a href="/forum/%s/faq" class="fa fa-arrow-right"> Read Guidelines</a>
51 </div>""" % slug(forum)
55 'name': fields.char('Name', required=True, translate=True),
56 'faq': fields.html('Guidelines'),
57 'right_column':fields.function(_get_right_column, string="Right Column", type='html', store=True),
59 def _get_default_faq(self, cr, uid, context={}):
60 fname = openerp.modules.get_module_resource('website_forum', 'data', 'forum_default_faq.html')
61 with open(fname, 'r') as f:
66 'faq': _get_default_faq,
69 class Post(osv.Model):
70 _name = 'website.forum.post'
71 _description = "Question"
72 _inherit = ['mail.thread', 'website.seo.metadata']
74 def _get_vote_count(self, cr, uid, ids, field_name, arg, context):
75 res = dict.fromkeys(ids, 0)
76 for post in self.browse(cr, uid, ids, context=context):
78 for vote in post.vote_ids:
79 res[post.id] += int(vote.vote)
82 def _get_vote(self, cr, uid, ids, context=None):
84 for vote in self.pool['website.forum.post.vote'].browse(cr, uid, ids, context=context):
85 result[vote.post_id.id] = True
88 def _get_child_count(self, cr, uid, ids, field_name=False, arg={}, context=None):
89 res = dict.fromkeys(ids, 0)
90 for post in self.browse(cr, uid, ids, context=context):
92 res[post.parent_id.id] = len(post.parent_id.child_ids)
94 res[post.id] = len(post.child_ids)
97 def _get_child(self, cr, uid, ids, context=None):
100 def _get_view_count(self, cr, uid, ids, field_name=False, arg={}, context=None):
101 res = dict.fromkeys(ids, 0)
102 for post in self.browse(cr, uid, ids, context=context):
103 res[post.id] = post.views + 1
106 def _set_view_count(self, cr, uid, ids, name, value, arg, context=None):
107 if isinstance(ids, (int, long)):
109 for rec in self.browse(cr, uid, ids, context=context):
110 views = rec.views + value
111 cr.execute('UPDATE website_forum_post SET views=%s WHERE id=%s;',(views, rec.id))
114 def _get_views(self, cr, uid, ids, context=None):
116 for statistic in self.pool['website.forum.post.statistics'].browse(cr, uid, ids, context=context):
117 result[statistic.post_id.id] = True
120 def _get_user_vote(self, cr, uid, ids, field_name, arg, context):
121 res = dict.fromkeys(ids, 0)
122 # Note: read_group is not returning all fields which we passed in list.when it will work uncomment this code and remove remaining code
123 #Vote = self.pool['website.forum.post.vote']
124 #data = Vote.read_group(cr, uid, [('post_id','in', ids), ('user_id', '=', uid)], [ "post_id", "vote"], groupby=["post_id"], context=context)
126 # res[rec[post_id][0]] = rec['vote']
127 for post in self.browse(cr, uid, ids, context=context):
128 for vote in post.vote_ids:
129 if vote.user_id.id == uid:
132 elif vote.vote == '-1':
136 def _get_user_favourite(self, cr, uid, ids, field_name, arg, context):
137 res = dict.fromkeys(ids, False)
138 user = self.pool['res.users'].browse(cr, uid, uid, context=context)
139 for post in self.browse(cr, uid, ids, context=context):
140 if user in post.favourite_ids:
145 'name': fields.char('Title', size=128),
146 'forum_id': fields.many2one('website.forum', 'Forum', required=True),
147 'content': fields.text('Content'),
148 'create_date': fields.datetime('Asked on', select=True, readonly=True),
149 'user_id': fields.many2one('res.users', 'Asked by', select=True, readonly=True ),
150 'write_date': fields.datetime('Update on', select=True, readonly=True ),
151 'write_uid': fields.many2one('res.users', 'Update by', select=True, readonly=True),
153 'tags': fields.many2many('website.forum.tag', 'forum_tag_rel', 'forum_id', 'forum_tag_id', 'Tag'),
154 'vote_ids':fields.one2many('website.forum.post.vote', 'post_id', 'Votes'),
155 'user_vote':fields.function(_get_user_vote, string="My Vote", type='integer'),
157 'favourite_ids': fields.many2many('res.users', 'forum_favourite_rel', 'forum_id', 'user_id', 'Favourite'),
158 'user_favourite':fields.function(_get_user_favourite, string="My Favourite", type='boolean'),
160 'state': fields.selection([('active', 'Active'), ('close', 'Close'),('offensive', 'Offensive')], 'Status'),
161 'active': fields.boolean('Active'),
163 'views_ids': fields.one2many('website.forum.post.statistics', 'post_id', 'Views'),
164 'views':fields.function(_get_view_count, string="Views", type='integer',
166 'website.forum.post.statistics': (_get_views, [], 10),
170 'parent_id': fields.many2one('website.forum.post', 'Question', ondelete='cascade'),
171 'child_ids': fields.one2many('website.forum.post', 'parent_id', 'Answers'),
172 'child_count':fields.function(_get_child_count, string="Answers", type='integer',
174 'website.forum.post': (_get_child, [], 10),
178 'history_ids': fields.one2many('blog.post.history', 'post_id', 'History', help='Last post modifications'),
179 # TODO FIXME: when website_mail/mail_thread.py inheritance work -> this field won't be necessary
180 'website_message_ids': fields.one2many(
181 'mail.message', 'res_id',
182 domain=lambda self: [
183 '&', ('model', '=', self._name), ('type', '=', 'comment')
185 string='Post Messages',
186 help="Comments on forum post",
189 'vote_count':fields.function(_get_vote_count, string="Votes", type='integer',
191 'website.forum.post': (lambda self, cr, uid, ids, c={}: ids, ['vote_ids'], 10),
192 'website.forum.post.vote': (_get_vote, [], 10),
196 'correct': fields.boolean('Correct Answer/ Answer on this question accepted.'),
197 'reason_id': fields.many2one('website.forum.post.reason', 'Reason'),
198 'closed_by': fields.many2one('res.users', 'Closed by'),
199 'closed_date': fields.datetime('Closed on', readonly=True),
207 def create_history(self, cr, uid, ids, context=None):
208 hist_obj = self.pool['website.forum.post.history']
209 for post in self.browse(cr, uid, ids, context=context):
210 history_count = hist_obj.search(cr, uid, [('post_id','=', post.id)], count=True, context=context)
211 date = datetime.today().strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
212 name = "No.%s Revision" %(history_count+1) if history_count else "initial version"
213 hist_obj.create(cr, uid, {
215 'content': post.content,
216 'name': '%s - (%s) %s' % (history_count+1, date, name),
217 'post_name': post.name,
218 'tags': [(6,0, [x.id for x in post.tags])],
219 'user_id': post.write_uid and post.write_uid.id or post.user_id.id,
222 def create(self, cr, uid, vals, context=None):
225 create_context = dict(context, mail_create_nolog=True)
226 body, subtype = "Asked a question", "website_forum.mt_question_create"
227 if vals.get("parent_id"):
228 body, subtype = "Answered a question", "website_forum.mt_answer_create"
229 #Note: because of no name it gives error on slug so set name of question in answer
230 question = self.browse(cr, uid, vals.get("parent_id"), context=context)
231 vals['name'] = question.name
232 #add 2 karma to user when asks question.
233 self.pool['res.users'].write(cr, SUPERUSER_ID, [vals.get('user_id')], {'karma': 2}, context=context)
234 post_id = super(Post, self).create(cr, uid, vals, context=create_context)
235 self.create_history(cr, uid, [post_id], context=context)
236 self.message_post(cr, uid, [post_id], body=_(body), subtype=subtype, context=context)
239 def write(self, cr, uid, ids, vals, context=None):
240 super(Post, self).write(cr, uid, ids, vals, context=context)
241 #NOTE: to avoid message post on write of last comment time
242 if not vals.get('message_last_post'):
243 user = self.pool['res.users'].browse(cr, uid ,uid, context=context)
244 self.create_history(cr, uid, ids, context=context)
245 for post in self.browse(cr, uid, ids, context=context):
246 body, subtype = "Edited question", "website_forum.mt_question_edit"
248 body, subtype = "Edited answer", "website_forum.mt_answer_edit"
249 self.message_post(cr, uid, [post.id], body=_(body), subtype=subtype, context=context)
251 #update karma of related user when any answer accepted.
253 if vals.get('correct'):
255 elif vals.get('correct') == False:
257 self.pool['res.users'].write(cr, SUPERUSER_ID, [post.user_id.id], {'karma': value}, context=context)
260 class PostStatistics(osv.Model):
261 _name = "website.forum.post.statistics"
262 _description = "Post Statistics"
264 'name': fields.char('Post Reason'),
265 'post_id': fields.many2one('website.forum.post', 'Post', ondelete='cascade'),
266 'user_id': fields.many2one('res.users', 'Created by'),
269 class PostReason(osv.Model):
270 _name = "website.forum.post.reason"
271 _description = "Post Reason"
273 'name': fields.char('Post Reason'),
276 class Users(osv.Model):
277 _inherit = 'res.users'
279 def _get_user_badge_level(self, cr, uid, ids, name, args, context=None):
280 """Return total badge per level of users"""
281 result = dict.fromkeys(ids, False)
282 badge_user_obj = self.pool['gamification.badge.user']
285 'gold_badge': badge_user_obj.search(cr, uid, [('badge_id.level', '=', 'gold'), ('user_id', '=', id)], context=context, count=True),
286 'silver_badge': badge_user_obj.search(cr, uid, [('badge_id.level', '=', 'silver'), ('user_id', '=', id)], context=context, count=True),
287 'bronze_badge': badge_user_obj.search(cr, uid, [('badge_id.level', '=', 'bronze'), ('user_id', '=', id)], context=context, count=True),
291 def _set_user_karma(self, cr, uid, ids, name, value, arg, context=None):
292 if isinstance(ids, (int, long)):
294 for rec in self.browse(cr, uid, ids, context=context):
295 karma = rec.karma + value
296 cr.execute('UPDATE res_users SET karma=%s WHERE id=%s;',(karma, rec.id))
299 def _get_user_karma(self, cr, uid, ids, field_name, arg, context=None):
301 for user in self.browse(cr, uid, ids, context=context):
302 result[user.id] = user.karma
306 'create_date': fields.datetime('Create Date', select=True, readonly=True),
307 'forum': fields.boolean('Is Forum Member'),
308 'karma': fields.function(_get_user_karma, fnct_inv=_set_user_karma, type='integer', string="Karma", store=True),
310 'badges': fields.one2many('gamification.badge.user', 'user_id', 'Badges'),
311 'gold_badge':fields.function(_get_user_badge_level, string="Number of gold badges", type='integer', multi='badge_level'),
312 'silver_badge':fields.function(_get_user_badge_level, string="Number of silver badges", type='integer', multi='badge_level'),
313 'bronze_badge':fields.function(_get_user_badge_level, string="Number of bronze badges", type='integer', multi='badge_level'),
320 class PostHistory(osv.Model):
321 _name = 'website.forum.post.history'
322 _description = 'Post History'
323 _inherit = ['website.seo.metadata']
325 'name': fields.char('History Title'),
326 'post_id': fields.many2one('website.forum.post', 'Post', ondelete='cascade'),
327 'post_name': fields.char('Post Name'),
328 'content': fields.text('Content'),
329 'user_id': fields.many2one('res.users', 'Created by', select=True, readonly=True),
330 'tags': fields.many2many('website.forum.tag', 'post_tag_rel', 'post_id', 'post_tag_id', 'Tag'),
333 class Vote(osv.Model):
334 _name = 'website.forum.post.vote'
335 _description = 'Vote'
337 'post_id': fields.many2one('website.forum.post', 'Post', ondelete='cascade', required=True),
338 'user_id': fields.many2one('res.users', 'User'),
339 'vote': fields.selection([('1', '1'),('-1', '-1'),('0','0')], 'Vote'),
340 'create_date': fields.datetime('Create Date', select=True, readonly=True),
343 'user_id': lambda self, cr, uid, ctx: uid,
344 'vote': lambda *args: 1
347 def create(self, cr, uid, vals, context=None):
348 vote_id = super(Vote, self).create(cr, uid, vals, context=context)
349 Post = self.pool["website.forum.post"]
350 record = Post.browse(cr, uid, vals.get('post_id'), context=context)
351 #Add 10 karma when user get up vote and subtract 10 karma when gets down vote.
352 value = 10 if vals.get('vote') == '1' else -10
353 self.pool['res.users'].write(cr, SUPERUSER_ID, [record.user_id.id], {'karma': value}, context=context)
354 body = "voted %s %s" % ('answer' if record.parent_id else 'question','up' if vals.get('vote')==1 else 'down')
355 Post.message_post(cr, uid, [record.id], body=_(body), context=context)
358 def vote(self, cr, uid, post_id, vote, context=None):
359 assert int(vote) in (1, -1, 0), "vote can be -1 or 1, nothing else"
360 #user can not vote on own post.
361 post = self.pool['website.forum.post'].browse(cr, uid, post_id, context=context)
362 if post.user_id.id == uid:
363 return {'error': 'own_post'}
364 user = self.pool['res.users'].browse(cr, uid, uid, context=None)
365 if (vote == '-1') and (user.karma <= 10):
366 return {'error': 'lessthen_10_karma'}
367 vote_ids = self.search(cr, uid, [('post_id', '=', post_id), ('user_id','=',uid)], context=context)
369 #when user will click again on vote it should set it 0.
370 record = self.browse(cr,uid, vote_ids[0], context=context)
371 new_vote = '0' if record.vote in ['1','-1'] else vote
372 #update karma when user changed vote.
373 if record.vote == '1' or new_vote == '-1':
375 elif record.vote == '-1' or new_vote == '1':
377 self.pool['res.users'].write(cr, SUPERUSER_ID, [record.post_id.user_id.id], {'karma': value}, context=context)
378 self.write(cr, uid, vote_ids, {
382 self.create(cr, uid, {
387 return {'vote_count': post.vote_count}
389 class MailMessage(osv.Model):
390 _inherit = 'mail.message'
392 'create_uid': fields.many2one('res.users', 'Created by', readonly=True ),
395 class Tags(osv.Model):
396 _name = "website.forum.tag"
398 _inherit = ['website.seo.metadata']
400 def _get_posts_count(self, cr, uid, ids, field_name, arg, context=None):
402 Post = self.pool['website.forum.post']
404 result[tag] = Post.search_count(cr, uid , [('tags', '=', tag)], context=context)
407 def _get_post(self, cr, uid, ids, context=None):
409 for post in self.pool['website.forum.post'].browse(cr, uid, ids, context=context):
410 for tag in post.tags:
411 result[tag.id] = True
415 'name': fields.char('Name', size=64, required=True),
416 'forum_id': fields.many2one('website.forum', 'Forum', required=True),
417 'posts_count': fields.function(_get_posts_count, type='integer', string="# of Posts",
419 'website.forum.post': (_get_post, ['tags'], 10),