[MERGE]: Merge with latest trunk
[odoo/odoo.git] / addons / idea / idea.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6 #
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.
11 #
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.
16 #
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/>.
19 #
20 ##############################################################################
21
22 from osv import osv
23 from osv import fields
24 from tools.translate import _
25 import time
26
27 VoteValues = [('-1', 'Not Voted'), ('0', 'Very Bad'), ('25', 'Bad'), \
28               ('50', 'Normal'), ('75', 'Good'), ('100', 'Very Good') ]
29 DefaultVoteValue = '50'
30
31 class idea_category(osv.osv):
32     """ Category of Idea """
33
34     def name_get(self, cr, uid, ids, context=None):
35         if not len(ids):
36             return []
37         reads = self.read(cr, uid, ids, ['name','parent_id'], context=context)
38         res = []
39         for record in reads:
40             name = record['name']
41             if record['parent_id']:
42                 name = record['parent_id'][1]+' / '+name
43             res.append((record['id'], name))
44         return res
45
46     def _categ_name_get_fnc(self, cr, uid, ids, prop, unknow_none, context=None):
47         res = self.name_get(cr, uid, ids, context=context)
48         return dict(res)
49
50     _name = "idea.category"
51     _description = "Idea Category"
52
53     _columns = {
54         'name': fields.char('Category', size=64, required=True),
55         'complete_name': fields.function(_categ_name_get_fnc, type="char", string='Name'),
56         'summary': fields.text('Summary'),
57         'parent_id': fields.many2one('idea.category', 'Parent Categories', ondelete='set null'),
58         'child_ids': fields.one2many('idea.category', 'parent_id', 'Child Categories'),
59         'visibility':fields.boolean('Open Idea?', required=False, help="If True creator of the idea will be visible to others"),
60     }
61     _sql_constraints = [
62         ('name', 'unique(parent_id,name)', 'The name of the category must be unique' )
63     ]
64     _order = 'parent_id,name asc'
65
66     _constraints = [
67         (osv.osv._check_recursion, 'Error ! You cannot create recursive categories.', ['parent_id'])
68     ]
69
70 idea_category()
71
72 class idea_idea(osv.osv):
73     """ Idea """
74     _name = 'idea.idea'
75     _rec_name = 'name'
76
77     def _vote_avg_compute(self, cr, uid, ids, name, arg, context=None):
78
79         """ compute average for voting
80          @param cr: the current row, from the database cursor,
81          @param uid: the current user’s ID for security checks,
82          @param ids: List of voting’s IDs
83          @return: dictionay of Idea """
84
85         if not ids:
86             return {}
87
88         sql = """SELECT i.id, avg(v.score::integer)
89            FROM idea_idea i LEFT OUTER JOIN idea_vote v ON i.id = v.idea_id
90             WHERE i.id IN %s
91             GROUP BY i.id
92         """
93
94         cr.execute(sql, (tuple(ids),))
95         return dict(cr.fetchall())
96
97     def _vote_count(self, cr, uid, ids, name, arg, context=None):
98
99         """ count number of vote
100          @param cr: the current row, from the database cursor,
101          @param uid: the current user’s ID for security checks,
102          @param ids: List of voting count’s IDs
103          @return: dictionay of Idea """
104
105         if not ids:
106             return {}
107
108         sql = """SELECT i.id, COUNT(1)
109            FROM idea_idea i LEFT OUTER JOIN idea_vote v ON i.id = v.idea_id
110             WHERE i.id IN %s
111             GROUP BY i.id
112         """
113
114         cr.execute(sql, (tuple(ids),))
115         return dict(cr.fetchall())
116
117     def _comment_count(self, cr, uid, ids, name, arg, context=None):
118
119         """ count number of comment
120         @param cr: the current row, from the database cursor,
121         @param uid: the current user’s ID for security checks,
122         @param ids: List of comment’s IDs
123         @return: dictionay of Idea """
124
125         if not ids:
126             return {}
127
128         sql = """SELECT i.id, COUNT(1)
129            FROM idea_idea i LEFT OUTER JOIN idea_comment c ON i.id = c.idea_id
130             WHERE i.id IN %s
131             GROUP BY i.id
132         """
133
134         cr.execute(sql, (tuple(ids),))
135         return dict(cr.fetchall())
136
137     def _vote_read(self, cr, uid, ids, name, arg, context=None):
138
139         """ Read Vote
140         @param cr: the current row, from the database cursor,
141         @param uid: the current user’s ID for security checks,
142         @param ids: List of vote read’s IDs """
143
144         res = {}
145         for id in ids:
146             res[id] = '-1'
147         vote_obj = self.pool.get('idea.vote')
148         votes_ids = vote_obj.search(cr, uid, [('idea_id', 'in', ids), ('user_id', '=', uid)])
149         vote_obj_id = vote_obj.browse(cr, uid, votes_ids, context=context)
150
151         for vote in vote_obj_id:
152             res[vote.idea_id.id] = vote.score
153         return res
154
155     def _vote_save(self, cr, uid, id, field_name, field_value, arg, context=None):
156
157         """ save Vote
158         @param cr: the current row, from the database cursor,
159         @param uid: the current user’s ID for security checks,
160         @param ids: List of vote save’s IDs """
161
162         vote_obj = self.pool.get('idea.vote')
163         vote = vote_obj.search(cr, uid, [('idea_id', '=', id), ('user_id', '=', uid)])
164         textual_value = str(field_value)
165
166         if vote:
167             if int(field_value) >= 0:
168                 vote_obj.write(cr, uid, vote, {'score': textual_value })
169             else:
170                 vote_obj.unlink(cr, uid, vote)
171         else:
172             if int(field_value) >= 0:
173                 vote_obj.create(cr, uid, {'idea_id': id, 'user_id': uid, 'score': textual_value })
174
175     _columns = {
176         'user_id': fields.many2one('res.users', 'Creator', required=True, readonly=True),
177         'name': fields.char('Idea Summary', size=64, required=True, readonly=True, oldname='title', states={'draft':[('readonly',False)]}),
178         'description': fields.text('Description', help='Content of the idea', readonly=True, states={'draft':[('readonly',False)]}),
179         'comment_ids': fields.one2many('idea.comment', 'idea_id', 'Comments'),
180         'created_date': fields.datetime('Creation date', readonly=True),
181         'open_date': fields.datetime('Open date', readonly=True, help="Date when an idea opened"),
182         'vote_ids': fields.one2many('idea.vote', 'idea_id', 'Vote'),
183         'my_vote': fields.function(_vote_read, fnct_inv = _vote_save, string="My Vote", type="selection", selection=VoteValues),
184         'vote_avg': fields.function(_vote_avg_compute, string="Average Score", type="float"),
185         'count_votes': fields.function(_vote_count, string="Count of votes", type="integer"),
186         'count_comments': fields.function(_comment_count, string="Count of comments", type="integer"),
187         'category_id': fields.many2one('idea.category', 'Category', required=True, readonly=True, states={'draft':[('readonly',False)]}),
188         'state': fields.selection([('draft', 'New'),
189             ('open', 'Opened'),
190             ('close', 'Accepted'),
191             ('cancel', 'Refused')],
192             'State', readonly=True,
193             help='When the Idea is created the state is \'Draft\'.\n It is \
194             opened by the user, the state is \'Opened\'.\
195             \nIf the idea is accepted, the state is \'Accepted\'.'
196         ),
197         'visibility':fields.boolean('Open Idea?', required=False),
198         'stat_vote_ids': fields.one2many('idea.vote.stat', 'idea_id', 'Statistics', readonly=True),
199         'vote_limit': fields.integer('Maximum Vote per User',
200                      help="Set to one if  you require only one Vote per user"),
201     }
202
203     _defaults = {
204         'user_id': lambda self,cr,uid,context: uid,
205         'my_vote': lambda *a: '-1',
206         'state': lambda *a: 'draft',
207         'vote_limit': lambda * a: 1,
208         'created_date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
209         'visibility': lambda *a: True,
210     }
211     _order = 'id desc'
212
213     def create(self, cr, user, vals, context=None):
214         """
215         Create a new record for a model idea_idea
216         @param cr: A database cursor
217         @param user: ID of the user currently logged in
218         @param vals: provides data for new record
219         @param context: context arguments, like lang, time zone
220
221         @return: Returns an id of the new record
222         """
223         visibility = False
224
225         if vals.get('category_id', False):
226             category_pool = self.pool.get('idea.category')
227             category = category_pool.browse(cr, user, vals.get('category_id'), context=context)
228             visibility = category.visibility
229
230         vals.update({
231             'visibility':visibility
232         })
233
234         res_id = super(idea_idea, self).create(cr, user, vals, context=context)
235         return res_id
236
237     def copy(self, cr, uid, id, default={}, context=None):
238         """
239         Create the new record in idea_idea model from existing one
240         @param cr: A database cursor
241         @param user: ID of the user currently logged in
242         @param id: list of record ids on which copy method executes
243         @param default: dict type contains the values to be overridden during copy of object
244         @param context: context arguments, like lang, time zone
245
246         @return: Returns the id of the new record
247         """
248
249         default.update({
250             'comment_ids':False,
251             'vote_ids':False,
252             'stat_vote_ids':False
253
254         })
255         res_id = super(idea_idea, self).copy(cr, uid, id, default, context=context)
256         return res_id
257
258     def write(self, cr, user, ids, vals, context=None):
259         """
260         Update redord(s) exist in {ids}, with new value provided in {vals}
261
262         @param cr: A database cursor
263         @param user: ID of the user currently logged in
264         @param ids: list of record ids to update
265         @param vals: dict of new values to be set
266         @param context: context arguments, like lang, time zone
267
268         @return: Returns True on success, False otherwise
269         """
270         state = self.browse(cr, user, ids[0], context=context).state
271
272         if vals.get('my_vote', False):
273             if vals.get('state', state) != 'open':
274                 raise osv.except_osv(_("Warning !"), _("Draft/Accepted/Cancelled ideas Could not be voted"))
275
276         res = super(idea_idea, self).write(cr, user, ids, vals, context=context)
277         return res
278
279     def idea_cancel(self, cr, uid, ids):
280         self.write(cr, uid, ids, { 'state': 'cancel' })
281         return True
282
283     def idea_open(self, cr, uid, ids):
284         self.write(cr, uid, ids, { 'state': 'open' ,'open_date': time.strftime('%Y-%m-%d %H:%M:%S')})
285         return True
286
287     def idea_close(self, cr, uid, ids):
288         self.write(cr, uid, ids, { 'state': 'close' })
289         return True
290
291     def idea_draft(self, cr, uid, ids):
292         self.write(cr, uid, ids, { 'state': 'draft' })
293         return True
294 idea_idea()
295
296
297 class idea_comment(osv.osv):
298     """ Apply Idea for Comment """
299
300     _name = 'idea.comment'
301     _description = 'Comment'
302     _rec_name = 'content'
303
304     _columns = {
305         'idea_id': fields.many2one('idea.idea', 'Idea', required=True, ondelete='cascade'),
306         'user_id': fields.many2one('res.users', 'User', required=True),
307         'content': fields.text('Comment', required=True),
308         'create_date': fields.datetime('Creation date', readonly=True),
309     }
310
311     _defaults = {
312         'user_id': lambda self, cr, uid, context: uid
313     }
314
315     _order = 'id desc'
316
317 idea_comment()
318
319
320 class idea_vote(osv.osv):
321     """ Apply Idea for Vote """
322
323     _name = 'idea.vote'
324     _description = 'Idea Vote'
325     _rec_name = 'score'
326
327     _columns = {
328         'user_id': fields.many2one('res.users', 'User', readonly="True"),
329         'idea_id': fields.many2one('idea.idea', 'Idea', readonly="True", ondelete='cascade'),
330         'score': fields.selection(VoteValues, 'Vote Status', readonly="True"),
331         'date': fields.datetime('Date', readonly="True"),
332         'comment': fields.text('Comment', readonly="True"),
333     }
334     _defaults = {
335         'score': lambda *a: DefaultVoteValue,
336         'date': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'),
337     }
338
339 idea_vote()
340
341 class idea_vote_stat(osv.osv):
342     """ Idea votes Statistics """
343
344     _name = 'idea.vote.stat'
345     _description = 'Idea Votes Statistics'
346     _auto = False
347     _rec_name = 'idea_id'
348
349     _columns = {
350         'idea_id': fields.many2one('idea.idea', 'Idea', readonly=True),
351         'score': fields.selection(VoteValues, 'Score', readonly=True),
352         'nbr': fields.integer('Number of Votes', readonly=True),
353     }
354
355     def init(self, cr):
356         """
357         initialize the sql view for the stats
358
359         cr -- the cursor
360         """
361         cr.execute("""
362             CREATE OR REPLACE VIEW idea_vote_stat AS (
363                 SELECT
364                     MIN(v.id) AS id,
365                     i.id AS idea_id,
366                     v.score,
367                     COUNT(1) AS nbr
368                 FROM
369                     idea_vote v
370                     LEFT JOIN idea_idea i ON (v.idea_id = i.id)
371                 GROUP BY
372                     i.id, v.score, i.id )
373         """)
374
375 idea_vote_stat()
376
377 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
378