[REF] gamification: code review of badge.py

bzr revid: qdp-launchpad@openerp.com-20130603160314-zprlgye2rpd140pv
This commit is contained in:
Quentin (OpenERP) 2013-06-03 18:03:14 +02:00
parent 8df1919dbb
commit a8eb9301e4
2 changed files with 57 additions and 116 deletions

View File

@ -20,9 +20,7 @@
############################################################################## ##############################################################################
from openerp.osv import fields, osv from openerp.osv import fields, osv
from openerp import tools
from openerp.tools.translate import _ from openerp.tools.translate import _
from openerp.tools.safe_eval import safe_eval
from templates import TemplateHelper from templates import TemplateHelper
from datetime import date from datetime import date
@ -30,25 +28,22 @@ import logging
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class gamification_badge_user(osv.Model): class gamification_badge_user(osv.Model):
"""User having received a badge""" """User having received a badge"""
_name = 'gamification.badge.user' _name = 'gamification.badge.user'
_description = 'Gamification user badge' _description = 'Gamification user badge'
_order = "create_date desc"
_columns = { _columns = {
'user_id': fields.many2one('res.users', string="User", required=True), 'user_id': fields.many2one('res.users', string="User", required=True),
'badge_id': fields.many2one('gamification.badge', string='Badge', required=True), 'badge_id': fields.many2one('gamification.badge', string='Badge', required=True),
'comment': fields.text('Comment'), 'comment': fields.text('Comment'),
'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"), 'badge_name': fields.related('badge_id', 'name', type="char", string="Badge Name"),
'create_date': fields.datetime('Created', readonly=True), 'create_date': fields.datetime('Created', readonly=True),
'create_uid': fields.many2one('res.users', 'Creator', readonly=True), 'create_uid': fields.many2one('res.users', 'Creator', readonly=True),
} }
_order = "create_date desc"
class gamification_badge(osv.Model): class gamification_badge(osv.Model):
"""Badge object that users can send and receive""" """Badge object that users can send and receive"""
@ -57,70 +52,50 @@ class gamification_badge(osv.Model):
_description = 'Gamification badge' _description = 'Gamification badge'
_inherit = ['mail.thread'] _inherit = ['mail.thread']
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False) def _get_unique_global_list(self, cr, uid, ids, name, args, context=None):
"""Return the list of unique res.users ids having received this badge"""
result = dict.fromkeys(ids, {'': False, '': False})
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = tools.image_get_resized_images(obj.image) res = set()
for owner in obj.owner_ids:
res.add(owner.user_id.id)
res = list(res)
result[obj.id]['unique_owner_ids'] = res
result[obj.id]['stat_count_distinct'] = len(res)
return result return result
#TODO: use multi attribute to compute all the functional fields in a row for global/monthly/user wise (or not) stats
def _get_global_count(self, cr, uid, ids, name, args, context=None): def _get_global_count(self, cr, uid, ids, name, args, context=None):
"""Return the number of time this badge has been granted""" """Return the number of time this badge has been granted"""
result = dict.fromkeys(ids, False) result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = len(self.pool.get('gamification.badge.user').search(
cr, uid, [('badge_id', '=', obj.id)], context=context))
result[obj.id] = len(obj.owner_ids) result[obj.id] = len(obj.owner_ids)
return result return result
def _get_unique_global_list(self, cr, uid, ids, name, args, context=None):
"""Return the list of unique res.users ids having received this badge"""
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
res = self.pool.get('gamification.badge.user').read_group(
cr, uid, domain=[('badge_id', '=', obj.id)],
fields=['badge_id', 'user_id'],
groupby=['user_id'], context=context)
result[obj.id] = [badge_user['user_id'][0] for badge_user in res]
return result
def _get_unique_global_count(self, cr, uid, ids, name, args, context=None):
"""Return the number of time this badge has been granted to individual users"""
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
res = self.pool.get('gamification.badge.user').read_group(
cr, uid, domain=[('badge_id', '=', obj.id)],
fields=['badge_id', 'user_id'],
groupby=['user_id'], context=context)
result[obj.id] = len(res)
return result
def _get_month_count(self, cr, uid, ids, name, args, context=None): def _get_month_count(self, cr, uid, ids, name, args, context=None):
"""Return the number of time this badge has been granted this month""" """Return the number of time this badge has been granted this month"""
badge_user_obj = self.pool.get('gamification.badge.user')
result = dict.fromkeys(ids, False) result = dict.fromkeys(ids, False)
first_month_day = date.today().replace(day=1).isoformat() first_month_day = date.today().replace(day=1).isoformat()#TODO: this isn't good. Must use DEFAULT_SERVER_DATE_FORMAT
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = len(self.pool.get('gamification.badge.user').search( result[obj.id] = badge_user_obj.search(cr, uid, [('badge_id', '=', obj.id), ('create_date', '>=', first_month_day)], context=context, count=True)
cr, uid, [('badge_id', '=', obj.id),
('create_date', '>=', first_month_day)], context=context))
return result return result
def _get_global_my_count(self, cr, uid, ids, name, args, context=None): def _get_global_my_count(self, cr, uid, ids, name, args, context=None):
"""Return the number of time this badge has been granted to the current user""" """Return the number of time this badge has been granted to the current user"""
badge_user_obj = self.pool.get('gamification.badge.user')
result = dict.fromkeys(ids, False) result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = len(self.pool.get('gamification.badge.user').search( result[obj.id] = badge_user_obj.search(cr, uid, [('badge_id', '=', obj.id), ('user_id', '=', uid)], context=context, count=True)
cr, uid, [('badge_id', '=', obj.id), ('user_id', '=', uid)],
context=context))
return result return result
def _get_month_my_count(self, cr, uid, ids, name, args, context=None): def _get_month_my_count(self, cr, uid, ids, name, args, context=None):
"""Return the number of time this badge has been granted to the current user this month""" """Return the number of time this badge has been granted to the current user this month"""
badge_user_obj = self.pool.get('gamification.badge.user')
result = dict.fromkeys(ids, False) result = dict.fromkeys(ids, False)
first_month_day = date.today().replace(day=1).isoformat() first_month_day = date.today().replace(day=1).isoformat()#TODO: this isn't good. Must use DEFAULT_SERVER_DATE_FORMAT
for obj in self.browse(cr, uid, ids, context=context): for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = len(self.pool.get('gamification.badge.user').search( result[obj.id] = badge_user_obj.search(cr, uid, [('badge_id', '=', obj.id), ('user_id', '=', uid), ('create_date', '>=', first_month_day)], context=context, count=True)
cr, uid, [('badge_id', '=', obj.id), ('user_id', '=', uid),
('create_date', '>=', first_month_day)], context=context))
return result return result
def _get_month_my_sent(self, cr, uid, ids, name, args, context=None): def _get_month_my_sent(self, cr, uid, ids, name, args, context=None):
@ -142,20 +117,20 @@ class gamification_badge(osv.Model):
""" """
result = dict.fromkeys(ids, False) result = dict.fromkeys(ids, False)
for badge in self.browse(cr, uid, ids, context=context): for badge in self.browse(cr, uid, ids, context=context):
if self.can_grant_badge(cr, uid, uid, badge.id, context) != 1: if self._can_grant_badge(cr, uid, uid, badge.id, context) != 1:
#if the user cannot grant this badge at all, result is 0
result[badge.id] = 0 result[badge.id] = 0
elif not badge.rule_max: elif not badge.rule_max:
#if there is no limitation, -1 is returned which mean 'infinite'
result[badge.id] = -1 result[badge.id] = -1
else: else:
result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending result[badge.id] = badge.rule_max_number - badge.stat_my_monthly_sending
return result return result
_columns = { _columns = {
'name': fields.char('Badge', required=True, translate=True), 'name': fields.char('Badge', required=True, translate=True),
'description': fields.text('Description'), 'description': fields.text('Description'),
'image': fields.binary("Image", 'image': fields.binary("Image", help="This field holds the image used for the badge, limited to 256x256"),
help="This field holds the image used for the badge, limited to 256x256"),
# image_select: selection with a on_change to fill image with predefined picts # image_select: selection with a on_change to fill image with predefined picts
'rule_auth': fields.selection([ 'rule_auth': fields.selection([
('everyone', 'Everyone'), ('everyone', 'Everyone'),
@ -163,7 +138,7 @@ class gamification_badge(osv.Model):
('having', 'People having some badges'), ('having', 'People having some badges'),
('nobody', 'No one, assigned through challenges'), ('nobody', 'No one, assigned through challenges'),
], ],
string="Allowed to Grant", string="Allowance to Grant",
help="Who can grant this badge", help="Who can grant this badge",
required=True), required=True),
'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users', 'rule_auth_user_ids': fields.many2many('res.users', 'rel_badge_auth_users',
@ -186,10 +161,8 @@ class gamification_badge(osv.Model):
string='Remaining Sending Allowed', help="If a maxium is set"), string='Remaining Sending Allowed', help="If a maxium is set"),
'plan_ids': fields.one2many('gamification.goal.plan', 'reward_id', 'plan_ids': fields.one2many('gamification.goal.plan', 'reward_id',
string="Reward for Challenges"), string="Reward of Challenges"),
'compute_code': fields.char('Compute Code',
help="The name of the python method that will be executed to verify if a user can receive this badge."),
'goal_type_ids': fields.many2many('gamification.goal.type', 'goal_type_ids': fields.many2many('gamification.goal.type',
string='Goals Linked', string='Goals Linked',
help="The users that have succeeded theses goals will receive automatically the badge."), help="The users that have succeeded theses goals will receive automatically the badge."),
@ -199,15 +172,17 @@ class gamification_badge(osv.Model):
'unique_owner_ids': fields.function(_get_unique_global_list, 'unique_owner_ids': fields.function(_get_unique_global_list,
string='Unique Owners', string='Unique Owners',
help="The list of unique users having received this badge.", help="The list of unique users having received this badge.",
multi='unique_users',
type="many2many", relation="res.users"), type="many2many", relation="res.users"),
'stat_count': fields.function(_get_global_count, string='Total', 'stat_count': fields.function(_get_global_count, string='Total',
type="integer", type="integer",
help="The number of time this badge has been received."), help="The number of time this badge has been received."),
'stat_count_distinct': fields.function(_get_unique_global_count, 'stat_count_distinct': fields.function(_get_unique_global_list,
type="integer", type="integer",
string='Number of users', string='Number of users',
help="The number of time this badge has been received by individual users."), multi='unique_users',
help="The number of time this badge has been received by unique users."),
'stat_this_month': fields.function(_get_month_count, 'stat_this_month': fields.function(_get_month_count,
type="integer", type="integer",
string='Monthly total', string='Monthly total',
@ -226,10 +201,9 @@ class gamification_badge(osv.Model):
'stat_count_distinct': 0, 'stat_count_distinct': 0,
'stat_this_month': 0, 'stat_this_month': 0,
'rule_auth': 'everyone', 'rule_auth': 'everyone',
'compute_code': "self.nobody(cr, uid, context)"
} }
def send_badge(self, cr, uid, badge_id, badge_user_ids, user_from=None, context=None): def send_badge(self, cr, uid, badge_id, badge_user_ids, user_from=False, context=None):
"""Send a notification to a user for receiving a badge """Send a notification to a user for receiving a badge
Does NOT verify constrains on badge granting. Does NOT verify constrains on badge granting.
@ -237,36 +211,22 @@ class gamification_badge(osv.Model):
The stats counters are incremented The stats counters are incremented
:param badge_id: id of the badge to deserve :param badge_id: id of the badge to deserve
:param badge_user_ids: list(int) of badge users that will receive the badge :param badge_user_ids: list(int) of badge users that will receive the badge
:param user_from: res.users object that has sent the badge :param user_from: optional id of the res.users object that has sent the badge
""" """
context = context or {}
badge = self.browse(cr, uid, badge_id, context=context) badge = self.browse(cr, uid, badge_id, context=context)
template_env = TemplateHelper() template_env = TemplateHelper()
res = None res = None
for badge_user in self.pool.get('gamification.badge.user').browse(cr, uid, badge_user_ids, context=context): for badge_user in self.pool.get('gamification.badge.user').browse(cr, uid, badge_user_ids, context=context):
values = {'badge_user': badge_user} values = {'badge_user': badge_user, 'user_from': user_from}
if user_from:
values['user_from'] = user_from
else:
values['user_from'] = False
body_html = template_env.get_template('badge_received.mako').render(values) body_html = template_env.get_template('badge_received.mako').render(values)
context['badge_user'] = badge_user res = self.message_post(cr, uid, badge.id, body=body_html, type='comment', subtype='mt_comment', context=context)
res = self.message_post(cr, uid, badge.id,
body=body_html,
type='comment',
subtype='mt_comment',
context=context)
return res return res
def check_granting(self, cr, uid, user_from_id, badge_id, context=None): def check_granting(self, cr, uid, user_from_id, badge_id, context=None):
"""Check the user can grant a badge and raise the appropriate exception """Check the user 'user_from_id' can grant the badge 'badge_id' and raise the appropriate exception
if not""" if not"""
context = context or {} status_code = self._can_grant_badge(cr, uid, user_from_id, badge_id, context=context)
status_code = self.can_grant_badge(cr, uid, user_from_id, badge_id, context)
if status_code == 1: if status_code == 1:
return True return True
elif status_code == 2: elif status_code == 2:
@ -281,7 +241,7 @@ class gamification_badge(osv.Model):
_logger.exception("Unknown badge status code: %d" % int(status_code)) _logger.exception("Unknown badge status code: %d" % int(status_code))
return False return False
def can_grant_badge(self, cr, uid, user_from_id, badge_id, context=None): def _can_grant_badge(self, cr, uid, user_from_id, badge_id, context=None):
"""Check if a user can grant a badge to another user """Check if a user can grant a badge to another user
:param user_from_id: the id of the res.users trying to send the badge :param user_from_id: the id of the res.users trying to send the badge
@ -293,75 +253,57 @@ class gamification_badge(osv.Model):
4: don't have the required badges 4: don't have the required badges
5: user's monthly limit reached 5: user's monthly limit reached
""" """
context = context or {}
badge = self.browse(cr, uid, badge_id, context=context) badge = self.browse(cr, uid, badge_id, context=context)
if badge.rule_auth == 'nobody': if badge.rule_auth == 'nobody':
return 2 return 2
elif badge.rule_auth == 'users': elif badge.rule_auth == 'users' and user_from_id not in [user.id for user in badge.rule_auth_user_ids]:
if user_from_id not in [user.id for user in badge.rule_auth_user_ids]: return 3
return 3
elif badge.rule_auth == 'having': elif badge.rule_auth == 'having':
badge_users = self.pool.get('gamification.badge.user').search( all_user_badges = self.pool.get('gamification.badge.user').search(cr, uid, [('user_id', '=', user_from_id)], context=context)
cr, uid, [('user_id', '=', user_from_id)], context=context) for required_badge in badge.rule_auth_badge_ids:
if required_badge.id not in all_user_badges:
if len(badge_users) == 0: return 4
# the user_from has no badges
return 4
owners = [owner.id for owner in badge.owner_ids]
granted = False
for badge_user in badge_users:
if badge_user in owners:
granted = True
break
if not granted:
return 4
# else badge.rule_auth == 'everyone' -> no check
if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number: if badge.rule_max and badge.stat_my_monthly_sending >= badge.rule_max_number:
# sent the maximum number of time this month
return 5 return 5
# badge.rule_auth == 'everyone' -> no check
return 1 return 1
class grant_badge_wizard(osv.TransientModel): class grant_badge_wizard(osv.TransientModel):
""" Wizard allowing to grant a badge to a user"""
_name = 'gamification.badge.user.wizard' _name = 'gamification.badge.user.wizard'
_columns = { _columns = {
'user_id': fields.many2one("res.users", string='User', required=True), 'user_id': fields.many2one("res.users", string='User', required=True),
'badge_id': fields.many2one("gamification.badge", string='Badge', required=True), 'badge_id': fields.many2one("gamification.badge", string='Badge', required=True),
'comment': fields.text('Comment'), 'comment': fields.text('Comment'),
} }
def action_grant_badge(self, cr, uid, ids, context=None): def action_grant_badge(self, cr, uid, ids, context=None):
"""Wizard action for sending a badge to a chosen user""" """Wizard action for sending a badge to a chosen user"""
if context is None:
context = {}
badge_obj = self.pool.get('gamification.badge') badge_obj = self.pool.get('gamification.badge')
badge_user_obj = self.pool.get('gamification.badge.user') badge_user_obj = self.pool.get('gamification.badge.user')
for wiz in self.browse(cr, uid, ids, context=context): for wiz in self.browse(cr, uid, ids, context=context):
if uid == wiz.user_id.id: if uid == wiz.user_id.id:
raise osv.except_osv(_('Warning!'), _('You can not send a badge to yourself')) raise osv.except_osv(_('Warning!'), _('You can not grant a badge to yourself'))
if badge_obj.check_granting(cr, uid, #check if the badge granting is legitimate
user_from_id=uid, if badge_obj.check_granting(cr, uid, user_from_id=uid, badge_id=wiz.badge_id.id, context=context):
badge_id=wiz.badge_id.id, #create the badge
context=context):
values = { values = {
'user_id': wiz.user_id.id, 'user_id': wiz.user_id.id,
'badge_id': wiz.badge_id.id, 'badge_id': wiz.badge_id.id,
'comment': wiz.comment, 'comment': wiz.comment,
} }
badge_user = badge_user_obj.create(cr, uid, values, context=context) badge_user = badge_user_obj.create(cr, uid, values, context=context)
#notify the user
badge_obj.send_badge(cr, uid, wiz.badge_id.id, [badge_user], user_from=uid, context=context)
user_from = self.pool.get('res.users').browse(cr, uid, uid, context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
badge_obj.send_badge(cr, uid, wiz.badge_id.id, [badge_user], user_from=user_from, context=context)
return {}

View File

@ -106,7 +106,7 @@ class hr_grant_badge_wizard(osv.TransientModel):
if uid == wiz.user_id.id: if uid == wiz.user_id.id:
raise osv.except_osv(_('Warning!'), _('You can not send a badge to yourself')) raise osv.except_osv(_('Warning!'), _('You can not send a badge to yourself'))
if badge_obj.can_grant_badge(cr, uid, if badge_obj._can_grant_badge(cr, uid,
user_from_id=uid, user_from_id=uid,
badge_id=wiz.badge_id.id, badge_id=wiz.badge_id.id,
context=context): context=context):
@ -119,9 +119,8 @@ class hr_grant_badge_wizard(osv.TransientModel):
} }
badge_user = badge_user_obj.create(cr, uid, values, context=context) badge_user = badge_user_obj.create(cr, uid, values, context=context)
user_from = self.pool.get('res.users').browse(cr, uid, uid, context=context)
badge_obj.send_badge(cr, uid, wiz.badge_id.id, [badge_user], user_from=user_from, context=context) badge_obj.send_badge(cr, uid, wiz.badge_id.id, [badge_user], user_from=uid, context=context)
return {} return {}