1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2013 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 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.
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.
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/>
20 ##############################################################################
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 _
27 from datetime import date
30 _logger = logging.getLogger(__name__)
32 class gamification_badge_user(osv.Model):
33 """User having received a badge"""
35 _name = 'gamification.badge.user'
36 _description = 'Gamification user badge'
37 _order = "create_date desc"
38 _rec_name = "badge_name"
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),
52 def _send_badge(self, cr, uid, ids, context=None):
53 """Send a notification to a user for receiving a badge
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
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,
69 subtype='gamification.mt_badge_granted',
70 partner_ids=[badge_user.user_id.partner_id.id],
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)
79 class gamification_badge(osv.Model):
80 """Badge object that users can send and receive"""
88 _name = 'gamification.badge'
89 _description = 'Gamification badge'
90 _inherit = ['mail.thread']
92 def _get_owners_info(self, cr, uid, ids, name, args, context=None):
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
98 result = dict((res_id, {'stat_count': 0, 'stat_count_distinct': 0, 'unique_owner_ids': []}) for res_id in ids)
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
108 for (badge_id, stat_count, stat_count_distinct, unique_owner_ids) in cr.fetchall():
110 'stat_count': stat_count,
111 'stat_count_distinct': stat_count_distinct,
112 'unique_owner_ids': unique_owner_ids,
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)
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)
130 def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
131 """Computes the number of badges remaining the user can send
133 0 if not allowed or no remaining
134 integer if limited sending
135 -1 if infinite (should not be displayed)
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
142 elif not badge.rule_max:
143 # if there is no limitation, -1 is returned which means 'infinite'
144 result[badge.id] = -1
146 result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
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'),
159 string="Allowance to Grant",
160 help="Who can grant this badge",
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"),
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,
176 string='My Monthly Sending Total',
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"),
182 'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
183 string="Reward of Challenges"),
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."),
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"),
198 'stat_count': fields.function(_get_owners_info, string='Total',
200 multi='unique_users',
201 help="The number of time this badge has been received."),
202 'stat_count_distinct': fields.function(_get_owners_info,
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,
209 string='Monthly total',
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',
215 help="The number of time the current user has received this badge."),
216 'stat_my_this_month': fields.function(_get_badge_user_stats,
218 string='My Monthly Total',
220 help="The number of time the current user has received this badge this month."),
224 'rule_auth': 'everyone',
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
232 Do not check for SUPERUSER_ID
234 status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
235 if status_code == self.CAN_GRANT:
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.'))
246 _logger.exception("Unknown badge status code: %d" % int(status_code))
249 def _can_grant_badge(self, cr, uid, badge_id, context=None):
250 """Check if a user can grant a badge to another user
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.
256 if uid == SUPERUSER_ID:
257 return self.CAN_GRANT
259 badge = self.browse(cr, uid, badge_id, context=context)
261 if badge.rule_auth == 'nobody':
262 return self.NOBODY_CAN_GRANT
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
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
273 if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
276 # badge.rule_auth == 'everyone' -> no check
277 return self.CAN_GRANT
279 def check_progress(self, cr, uid, context=None):
281 model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
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):
290 badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)