[IMP] report; minimal layout is now a qweb template, allowing users to customize...
[odoo/odoo.git] / addons / gamification / models / badge.py
1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 #
4 #    OpenERP, Open Source Management Solution
5 #    Copyright (C) 2013 OpenERP SA (<http://www.openerp.com>)
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU General Public License as published by
9 #    the Free Software Foundation, either version 3 of the License, or
10 #    (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 General Public License for more details.
16 #
17 #    You should have received a copy of the GNU General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>
19 #
20 ##############################################################################
21
22 from openerp import SUPERUSER_ID
23 from openerp.osv import fields, osv
24 from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF
25 from openerp.tools.translate import _
26
27 from datetime import date
28 import logging
29
30 _logger = logging.getLogger(__name__)
31
32 class gamification_badge_user(osv.Model):
33     """User having received a badge"""
34
35     _name = 'gamification.badge.user'
36     _description = 'Gamification user badge'
37     _order = "create_date desc"
38     _rec_name = "badge_name"
39
40     _columns = {
41         'user_id': fields.many2one('res.users', string="User", required=True),
42         'sender_id': fields.many2one('res.users', string="Sender", help="The user who has send the badge"),
43         'badge_id': fields.many2one('gamification.badge', string='Badge', required=True, ondelete="cascade"),
44         'challenge_id': fields.many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge"),
45         'comment': fields.text('Comment'),
46         'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
47         'create_date': fields.datetime('Created', readonly=True),
48         'create_uid': fields.many2one('res.users', string='Creator', readonly=True),
49     }
50
51
52     def _send_badge(self, cr, uid, ids, context=None):
53         """Send a notification to a user for receiving a badge
54
55         Does not verify constrains on badge granting.
56         The users are added to the owner_ids (create badge_user if needed)
57         The stats counters are incremented
58         :param ids: list(int) of badge users that will receive the badge
59         """
60         res = True
61         temp_obj = self.pool.get('email.template')
62         user_obj = self.pool.get('res.users')
63         template_id = self.pool['ir.model.data'].get_object(cr, uid, 'gamification', 'email_template_badge_received', context)
64         for badge_user in self.browse(cr, uid, ids, context=context):
65             body_html = temp_obj.render_template(cr, uid, template_id.body_html, 'gamification.badge.user', badge_user.id, context=context)
66             res = user_obj.message_post(
67                 cr, uid, badge_user.user_id.id,
68                 body=body_html,
69                 subtype='gamification.mt_badge_granted',
70                 partner_ids=[badge_user.user_id.partner_id.id],
71                 context=context)
72         return res
73
74     def create(self, cr, uid, vals, context=None):
75         self.pool.get('gamification.badge').check_granting(cr, uid, badge_id=vals.get('badge_id'), context=context)
76         return super(gamification_badge_user, self).create(cr, uid, vals, context=context)
77
78
79 class gamification_badge(osv.Model):
80     """Badge object that users can send and receive"""
81
82     CAN_GRANT = 1
83     NOBODY_CAN_GRANT = 2
84     USER_NOT_VIP = 3
85     BADGE_REQUIRED = 4
86     TOO_MANY = 5
87
88     _name = 'gamification.badge'
89     _description = 'Gamification badge'
90     _inherit = ['mail.thread']
91
92     def _get_owners_info(self, cr, uid, ids, name, args, context=None):
93         """Return:
94             the list of unique res.users ids having received this badge
95             the total number of time this badge was granted
96             the total number of users this badge was granted to
97         """
98         result = dict.fromkeys(ids, False)
99         for obj in self.browse(cr, uid, ids, context=context):
100             res = list(set(owner.user_id.id for owner in obj.owner_ids))
101             result[obj.id] = {
102                 'unique_owner_ids': res,
103                 'stat_count': len(obj.owner_ids),
104                 'stat_count_distinct': len(res)
105             }
106         return result
107
108     def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None):
109         """Return stats related to badge users"""
110         result = dict.fromkeys(ids, False)
111         badge_user_obj = self.pool.get('gamification.badge.user')
112         first_month_day = date.today().replace(day=1).strftime(DF)
113         for bid in ids:
114             result[bid] = {
115                 'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True),
116                 'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True),
117                 'stat_my_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True),
118                 'stat_my_monthly_sending': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_uid', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True)
119             }
120         return result
121
122     def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
123         """Computes the number of badges remaining the user can send
124
125         0 if not allowed or no remaining
126         integer if limited sending
127         -1 if infinite (should not be displayed)
128         """
129         result = dict.fromkeys(ids, False)
130         for badge in self.browse(cr, uid, ids, context=context):
131             if self._can_grant_badge(cr, uid, badge.id, context) != 1:
132                 # if the user cannot grant this badge at all, result is 0
133                 result[badge.id] = 0
134             elif not badge.rule_max:
135                 # if there is no limitation, -1 is returned which means 'infinite'
136                 result[badge.id] = -1
137             else:
138                 result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
139         return result
140
141     _columns = {
142         'name': fields.char('Badge', required=True, translate=True),
143         'description': fields.text('Description'),
144         'image': fields.binary("Image", help="This field holds the image used for the badge, limited to 256x256"),
145         'rule_auth': fields.selection([
146                 ('everyone', 'Everyone'),
147                 ('users', 'A selected list of users'),
148                 ('having', 'People having some badges'),
149                 ('nobody', 'No one, assigned through challenges'),
150             ],
151             string="Allowance to Grant",
152             help="Who can grant this badge",
153             required=True),
154         'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
155             string='Authorized Users',
156             help="Only these people can give this badge"),
157         'rule_auth_badge_ids': fields.many2many('gamification.badge',
158             'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
159             string='Required Badges',
160             help="Only the people having these badges can give this badge"),
161
162         'rule_max': fields.boolean('Monthly Limited Sending',
163             help="Check to set a monthly limit per person of sending this badge"),
164         'rule_max_number': fields.integer('Limitation Number',
165             help="The maximum number of time this badge can be sent per month per person."),
166         'stat_my_monthly_sending': fields.function(_get_badge_user_stats,
167             type="integer",
168             string='My Monthly Sending Total',
169             multi='badge_users',
170             help="The number of time the current user has sent this badge this month."),
171         'remaining_sending': fields.function(_remaining_sending_calc, type='integer',
172             string='Remaining Sending Allowed', help="If a maxium is set"),
173
174         'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
175             string="Reward of Challenges"),
176
177         'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel',
178             string='Rewarded by',
179             help="The users that have succeeded theses goals will receive automatically the badge."),
180
181         'owner_ids': fields.one2many('gamification.badge.user', 'badge_id',
182             string='Owners', help='The list of instances of this badge granted to users'),
183         'active': fields.boolean('Active'),
184         'unique_owner_ids': fields.function(_get_owners_info,
185             string='Unique Owners',
186             help="The list of unique users having received this badge.",
187             multi='unique_users',
188             type="many2many", relation="res.users"),
189
190         'stat_count': fields.function(_get_owners_info, string='Total',
191             type="integer",
192             multi='unique_users',
193             help="The number of time this badge has been received."),
194         'stat_count_distinct': fields.function(_get_owners_info,
195             type="integer",
196             string='Number of users',
197             multi='unique_users',
198             help="The number of time this badge has been received by unique users."),
199         'stat_this_month': fields.function(_get_badge_user_stats,
200             type="integer",
201             string='Monthly total',
202             multi='badge_users',
203             help="The number of time this badge has been received this month."),
204         'stat_my': fields.function(_get_badge_user_stats, string='My Total',
205             type="integer",
206             multi='badge_users',
207             help="The number of time the current user has received this badge."),
208         'stat_my_this_month': fields.function(_get_badge_user_stats,
209             type="integer",
210             string='My Monthly Total',
211             multi='badge_users',
212             help="The number of time the current user has received this badge this month."),
213     }
214
215     _defaults = {
216         'rule_auth': 'everyone',
217         'active': True,
218     }
219
220     def check_granting(self, cr, uid, badge_id, context=None):
221         """Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
222         if not
223
224         Do not check for SUPERUSER_ID
225         """
226         status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
227         if status_code == self.CAN_GRANT:
228             return True
229         elif status_code == self.NOBODY_CAN_GRANT:
230             raise osv.except_osv(_('Warning!'), _('This badge can not be sent by users.'))
231         elif status_code == self.USER_NOT_VIP:
232             raise osv.except_osv(_('Warning!'), _('You are not in the user allowed list.'))
233         elif status_code == self.BADGE_REQUIRED:
234             raise osv.except_osv(_('Warning!'), _('You do not have the required badges.'))
235         elif status_code == self.TOO_MANY:
236             raise osv.except_osv(_('Warning!'), _('You have already sent this badge too many time this month.'))
237         else:
238             _logger.exception("Unknown badge status code: %d" % int(status_code))
239         return False
240
241     def _can_grant_badge(self, cr, uid, badge_id, context=None):
242         """Check if a user can grant a badge to another user
243
244         :param uid: the id of the res.users trying to send the badge
245         :param badge_id: the granted badge id
246         :return: integer representing the permission.
247         """
248         if uid == SUPERUSER_ID:
249             return self.CAN_GRANT
250
251         badge = self.browse(cr, uid, badge_id, context=context)
252
253         if badge.rule_auth == 'nobody':
254             return self.NOBODY_CAN_GRANT
255
256         elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]:
257             return self.USER_NOT_VIP
258
259         elif badge.rule_auth == 'having':
260             all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context)
261             for required_badge in badge.rule_auth_badge_ids:
262                 if required_badge.id not in all_user_badges:
263                     return self.BADGE_REQUIRED
264
265         if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
266             return self.TOO_MANY
267
268         # badge.rule_auth == 'everyone' -> no check
269         return self.CAN_GRANT
270
271     def check_progress(self, cr, uid, context=None):
272         try:
273             model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
274         except ValueError:
275             return True
276         badge_user_obj = self.pool.get('gamification.badge.user')
277         if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context):
278             values = {
279                 'user_id': uid,
280                 'badge_id': res_id,
281             }
282             badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)
283         return True