[FIX] [CLEAN] Various: fixed / cleaned use of dict.fromkeys.
[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((res_id, {'stat_count': 0, 'stat_count_distinct': 0, 'unique_owner_ids': []}) for res_id in ids)
99
100         cr.execute("""
101             SELECT badge_id, count(user_id) as stat_count,
102                 count(distinct(user_id)) as stat_count_distinct,
103                 array_agg(distinct(user_id)) as unique_owner_ids
104             FROM gamification_badge_user
105             WHERE badge_id in %s
106             GROUP BY badge_id
107             """, (tuple(ids),))
108         for (badge_id, stat_count, stat_count_distinct, unique_owner_ids) in cr.fetchall():
109             result[badge_id] = {
110                 'stat_count': stat_count,
111                 'stat_count_distinct': stat_count_distinct,
112                 'unique_owner_ids': unique_owner_ids,
113             }
114         return result
115
116     def _get_badge_user_stats(self, cr, uid, ids, name, args, context=None):
117         """Return stats related to badge users"""
118         result = dict.fromkeys(ids, False)
119         badge_user_obj = self.pool.get('gamification.badge.user')
120         first_month_day = date.today().replace(day=1).strftime(DF)
121         for bid in ids:
122             result[bid] = {
123                 'stat_my': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('user_id', '=', uid)], context=context, count=True),
124                 'stat_this_month': badge_user_obj.search(cr, uid, [('badge_id', '=', bid), ('create_date', '>=', first_month_day)], context=context, count=True),
125                 '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),
126                 '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)
127             }
128         return result
129
130     def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
131         """Computes the number of badges remaining the user can send
132
133         0 if not allowed or no remaining
134         integer if limited sending
135         -1 if infinite (should not be displayed)
136         """
137         result = dict.fromkeys(ids, False)
138         for badge in self.browse(cr, uid, ids, context=context):
139             if self._can_grant_badge(cr, uid, badge.id, context) != 1:
140                 # if the user cannot grant this badge at all, result is 0
141                 result[badge.id] = 0
142             elif not badge.rule_max:
143                 # if there is no limitation, -1 is returned which means 'infinite'
144                 result[badge.id] = -1
145             else:
146                 result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
147         return result
148
149     _columns = {
150         'name': fields.char('Badge', required=True, translate=True),
151         'description': fields.text('Description'),
152         'image': fields.binary("Image", help="This field holds the image used for the badge, limited to 256x256"),
153         'rule_auth': fields.selection([
154                 ('everyone', 'Everyone'),
155                 ('users', 'A selected list of users'),
156                 ('having', 'People having some badges'),
157                 ('nobody', 'No one, assigned through challenges'),
158             ],
159             string="Allowance to Grant",
160             help="Who can grant this badge",
161             required=True),
162         'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
163             string='Authorized Users',
164             help="Only these people can give this badge"),
165         'rule_auth_badge_ids': fields.many2many('gamification.badge',
166             'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
167             string='Required Badges',
168             help="Only the people having these badges can give this badge"),
169
170         'rule_max': fields.boolean('Monthly Limited Sending',
171             help="Check to set a monthly limit per person of sending this badge"),
172         'rule_max_number': fields.integer('Limitation Number',
173             help="The maximum number of time this badge can be sent per month per person."),
174         'stat_my_monthly_sending': fields.function(_get_badge_user_stats,
175             type="integer",
176             string='My Monthly Sending Total',
177             multi='badge_users',
178             help="The number of time the current user has sent this badge this month."),
179         'remaining_sending': fields.function(_remaining_sending_calc, type='integer',
180             string='Remaining Sending Allowed', help="If a maxium is set"),
181
182         'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
183             string="Reward of Challenges"),
184
185         'goal_definition_ids': fields.many2many('gamification.goal.definition', 'badge_unlocked_definition_rel',
186             string='Rewarded by',
187             help="The users that have succeeded theses goals will receive automatically the badge."),
188
189         'owner_ids': fields.one2many('gamification.badge.user', 'badge_id',
190             string='Owners', help='The list of instances of this badge granted to users'),
191         'active': fields.boolean('Active'),
192         'unique_owner_ids': fields.function(_get_owners_info,
193             string='Unique Owners',
194             help="The list of unique users having received this badge.",
195             multi='unique_users',
196             type="many2many", relation="res.users"),
197
198         'stat_count': fields.function(_get_owners_info, string='Total',
199             type="integer",
200             multi='unique_users',
201             help="The number of time this badge has been received."),
202         'stat_count_distinct': fields.function(_get_owners_info,
203             type="integer",
204             string='Number of users',
205             multi='unique_users',
206             help="The number of time this badge has been received by unique users."),
207         'stat_this_month': fields.function(_get_badge_user_stats,
208             type="integer",
209             string='Monthly total',
210             multi='badge_users',
211             help="The number of time this badge has been received this month."),
212         'stat_my': fields.function(_get_badge_user_stats, string='My Total',
213             type="integer",
214             multi='badge_users',
215             help="The number of time the current user has received this badge."),
216         'stat_my_this_month': fields.function(_get_badge_user_stats,
217             type="integer",
218             string='My Monthly Total',
219             multi='badge_users',
220             help="The number of time the current user has received this badge this month."),
221     }
222
223     _defaults = {
224         'rule_auth': 'everyone',
225         'active': True,
226     }
227
228     def check_granting(self, cr, uid, badge_id, context=None):
229         """Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
230         if not
231
232         Do not check for SUPERUSER_ID
233         """
234         status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
235         if status_code == self.CAN_GRANT:
236             return True
237         elif status_code == self.NOBODY_CAN_GRANT:
238             raise osv.except_osv(_('Warning!'), _('This badge can not be sent by users.'))
239         elif status_code == self.USER_NOT_VIP:
240             raise osv.except_osv(_('Warning!'), _('You are not in the user allowed list.'))
241         elif status_code == self.BADGE_REQUIRED:
242             raise osv.except_osv(_('Warning!'), _('You do not have the required badges.'))
243         elif status_code == self.TOO_MANY:
244             raise osv.except_osv(_('Warning!'), _('You have already sent this badge too many time this month.'))
245         else:
246             _logger.exception("Unknown badge status code: %d" % int(status_code))
247         return False
248
249     def _can_grant_badge(self, cr, uid, badge_id, context=None):
250         """Check if a user can grant a badge to another user
251
252         :param uid: the id of the res.users trying to send the badge
253         :param badge_id: the granted badge id
254         :return: integer representing the permission.
255         """
256         if uid == SUPERUSER_ID:
257             return self.CAN_GRANT
258
259         badge = self.browse(cr, uid, badge_id, context=context)
260
261         if badge.rule_auth == 'nobody':
262             return self.NOBODY_CAN_GRANT
263
264         elif badge.rule_auth == 'users' and uid not in [user.id for user in badge.rule_auth_user_ids]:
265             return self.USER_NOT_VIP
266
267         elif badge.rule_auth == 'having':
268             all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', uid)], context=context)
269             for required_badge in badge.rule_auth_badge_ids:
270                 if required_badge.id not in all_user_badges:
271                     return self.BADGE_REQUIRED
272
273         if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
274             return self.TOO_MANY
275
276         # badge.rule_auth == 'everyone' -> no check
277         return self.CAN_GRANT
278
279     def check_progress(self, cr, uid, context=None):
280         try:
281             model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
282         except ValueError:
283             return True
284         badge_user_obj = self.pool.get('gamification.badge.user')
285         if not badge_user_obj.search(cr, uid, [('user_id', '=', uid), ('badge_id', '=', res_id)], context=context):
286             values = {
287                 'user_id': uid,
288                 'badge_id': res_id,
289             }
290             badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)
291         return True