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.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))
102 'unique_owner_ids': res,
103 'stat_count': len(obj.owner_ids),
104 'stat_count_distinct': len(res)
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)
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)
122 def _remaining_sending_calc(self, cr, uid, ids, name, args, context=None):
123 """Computes the number of badges remaining the user can send
125 0 if not allowed or no remaining
126 integer if limited sending
127 -1 if infinite (should not be displayed)
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
134 elif not badge.rule_max:
135 # if there is no limitation, -1 is returned which means 'infinite'
136 result[badge.id] = -1
138 result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
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'),
151 string="Allowance to Grant",
152 help="Who can grant this badge",
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"),
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,
168 string='My Monthly Sending Total',
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"),
174 'challenge_ids': fields.one2many('gamification.challenge', 'reward_id',
175 string="Reward of Challenges"),
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."),
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"),
190 'stat_count': fields.function(_get_owners_info, string='Total',
192 multi='unique_users',
193 help="The number of time this badge has been received."),
194 'stat_count_distinct': fields.function(_get_owners_info,
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,
201 string='Monthly total',
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',
207 help="The number of time the current user has received this badge."),
208 'stat_my_this_month': fields.function(_get_badge_user_stats,
210 string='My Monthly Total',
212 help="The number of time the current user has received this badge this month."),
216 'rule_auth': 'everyone',
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
224 Do not check for SUPERUSER_ID
226 status_code = self._can_grant_badge(cr, uid, badge_id, context=context)
227 if status_code == self.CAN_GRANT:
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.'))
238 _logger.exception("Unknown badge status code: %d" % int(status_code))
241 def _can_grant_badge(self, cr, uid, badge_id, context=None):
242 """Check if a user can grant a badge to another user
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.
248 if uid == SUPERUSER_ID:
249 return self.CAN_GRANT
251 badge = self.browse(cr, uid, badge_id, context=context)
253 if badge.rule_auth == 'nobody':
254 return self.NOBODY_CAN_GRANT
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
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
265 if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
268 # badge.rule_auth == 'everyone' -> no check
269 return self.CAN_GRANT
271 def check_progress(self, cr, uid, context=None):
273 model, res_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'gamification', 'badge_hidden')
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):
282 badge_user_obj.create(cr, SUPERUSER_ID, values, context=context)