diff --git a/addons/gamification/data/goal_base.xml b/addons/gamification/data/goal_base.xml index 0437dee031c..c12763025f6 100644 --- a/addons/gamification/data/goal_base.xml +++ b/addons/gamification/data/goal_base.xml @@ -164,7 +164,7 @@ once personal never - [('groups_id', 'in', ref('base.group_user'))] + inprogress other @@ -174,7 +174,7 @@ once personal never - [('groups_id', 'in', ref('base.user_root'))] + inprogress other diff --git a/addons/gamification/models/challenge.py b/addons/gamification/models/challenge.py index 295340a309b..a0c4b638ae3 100644 --- a/addons/gamification/models/challenge.py +++ b/addons/gamification/models/challenge.py @@ -21,14 +21,13 @@ from openerp import SUPERUSER_ID from openerp.osv import fields, osv -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT as DF +from openerp.tools import ustr, DEFAULT_SERVER_DATE_FORMAT as DF from openerp.tools.safe_eval import safe_eval as eval from openerp.tools.translate import _ from datetime import date, datetime, timedelta import calendar import logging -import functools _logger = logging.getLogger(__name__) # display top 3 in ranking, could be db variable @@ -117,12 +116,6 @@ class gamification_challenge(osv.Model): except ValueError: return False - def _get_challenger_users(self, cr, uid, domain, context=None): - ref = functools.partial(self.pool['ir.model.data'].xmlid_to_res_id, cr, uid) - user_domain = eval(domain, {'ref': ref}) - return self.pool['res.users'].search(cr, uid, user_domain, context=context) - - _order = 'end_date, start_date, name, id' _columns = { 'name': fields.char('Challenge Name', required=True, translate=True), @@ -218,7 +211,6 @@ class gamification_challenge(osv.Model): def create(self, cr, uid, vals, context=None): """Overwrite the create method to add the user of groups""" - # add users when change the group auto-subscription if vals.get('user_domain'): user_ids = self._get_challenger_users(cr, uid, vals.get('user_domain'), context=context) @@ -240,14 +232,18 @@ class gamification_challenge(osv.Model): if isinstance(ids, (int,long)): ids = [ids] - if vals.get('state') == 'inprogress': - for challenge in self.browse(cr, uid, ids, context=context): - user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context) - write_op = [(4, user_id) for user_id in user_ids] - self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context) - self.message_subscribe_users(cr, uid, [challenge.id], user_ids, context=context) + if vals.get('user_domain'): + user_ids = self._get_challenger_users(cr, uid, vals.get('user_domain'), context=context) - self.generate_goals_from_challenge(cr, uid, ids, context=context) + if not vals.get('user_ids'): + vals['user_ids'] = [] + vals['user_ids'] += [(4, user_id) for user_id in user_ids] + + write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context) + + if vals.get('state') == 'inprogress': + self._recompute_challenge_users(cr, uid, ids, context=context) + self._generate_goals_from_challenge(cr, uid, ids, context=context) elif vals.get('state') == 'done': self.check_challenge_reward(cr, uid, ids, force=True, context=context) @@ -256,9 +252,6 @@ class gamification_challenge(osv.Model): # resetting progress if self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', 'in', ids), ('state', '=', 'inprogress')], context=context): raise osv.except_osv("Error", "You can not reset a challenge with unfinished goals.") - - write_res = super(gamification_challenge, self).write(cr, uid, ids, vals, context=context) - return write_res @@ -314,18 +307,10 @@ class gamification_challenge(osv.Model): # update every running goal already generated linked to selected challenges goal_obj.update(cr, uid, goal_ids, context=context) + self._recompute_challenge_users(cr, uid, ids, context=context) + self._generate_goals_from_challenge(cr, uid, ids, context=context) + for challenge in self.browse(cr, uid, ids, context=context): - # in case of new users matching the domain - old_user_ids = [user.id for user in challenge.user_ids] - new_user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context) - to_remove_ids = list(set(old_user_ids) - set(new_user_ids)) - to_add_ids = list(set(new_user_ids) - set(old_user_ids)) - - write_op = [(3, user_id) for user_id in to_remove_ids] - write_op += [(4, user_id) for user_id in to_add_ids] - self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context) - - self.generate_goals_from_challenge(cr, uid, [challenge.id], context=context) # goals closed but still opened at the last report date closed_goals_to_report = goal_obj.search(cr, uid, [ @@ -345,11 +330,37 @@ class gamification_challenge(osv.Model): return True def quick_update(self, cr, uid, challenge_id, context=None): - """Update all the goals of a challenge, no generation of new goals""" + """Update all the goals of a specific challenge, no generation of new goals""" goal_ids = self.pool.get('gamification.goal').search(cr, uid, [('challenge_id', '=', challenge_id)], context=context) self.pool.get('gamification.goal').update(cr, uid, goal_ids, context=context) return True + def _get_challenger_users(self, cr, uid, domain, context=None): + user_domain = eval(ustr(domain)) + return self.pool['res.users'].search(cr, uid, user_domain, context=context) + + def _recompute_challenge_users(self, cr, uid, challenge_ids, context=None): + """Recompute the domain to add new users and remove the one no longer matching the domain""" + for challenge in self.browse(cr, uid, challenge_ids, context=context): + if challenge.user_domain: + + old_user_ids = [user.id for user in challenge.user_ids] + new_user_ids = self._get_challenger_users(cr, uid, challenge.user_domain, context=context) + to_remove_ids = list(set(old_user_ids) - set(new_user_ids)) + to_add_ids = list(set(new_user_ids) - set(old_user_ids)) + + write_op = [(3, user_id) for user_id in to_remove_ids] + write_op += [(4, user_id) for user_id in to_add_ids] + if write_op: + self.write(cr, uid, [challenge.id], {'user_ids': write_op}, context=context) + + if to_remove_ids: + self.message_unsubscribe_users(cr, uid, [challenge.id], to_remove_ids, context=None) + if to_add_ids: + self.message_subscribe_users(cr, uid, [challenge.id], to_add_ids, context=context) + + return True + def action_check(self, cr, uid, ids, context=None): """Check a challenge @@ -370,12 +381,17 @@ class gamification_challenge(osv.Model): ##### Automatic actions ##### def generate_goals_from_challenge(self, cr, uid, ids, context=None): + _logger.warning("Deprecated, use private method _generate_goals_from_challenge(...) instead.") + return self._generate_goals_from_challenge(cr, uid, ids, context=context) + + def _generate_goals_from_challenge(self, cr, uid, ids, context=None): """Generate the goals for each line and user. If goals already exist for this line and user, the line is skipped. This can be called after each change in the list of users or lines. :param list(int) ids: the list of challenge concerned""" + goal_obj = self.pool.get('gamification.goal') to_update = [] for challenge in self.browse(cr, uid, ids, context=context): (start_date, end_date) = start_end_date_for_period(challenge.period) @@ -387,45 +403,49 @@ class gamification_challenge(osv.Model): end_date = challenge.end_date for line in challenge.line_ids: - # FIXME: allow to restrict to a subset of users - for user in challenge.user_ids: - goal_obj = self.pool.get('gamification.goal') - domain = [('line_id', '=', line.id), ('user_id', '=', user.id)] - if start_date: - domain.append(('start_date', '=', start_date)) + # there is potentially a lot of users + # detect the ones with no goal linked to this line + date_clause = "" + query_params = [line.id] + if start_date: + date_clause += "AND g.start_date = %s" + query_params.append(start_date) + if end_date: + date_clause += "AND g.end_date = %s" + query_params.append(end_date) + + query = """SELECT u.id AS user_id + FROM res_users u + LEFT JOIN gamification_goal g + ON (u.id = g.user_id) + WHERE line_id = %s + {date_clause} + """.format(date_clause=date_clause) - # goal already existing for this line ? - if len(goal_obj.search(cr, uid, domain, context=context)) > 0: + cr.execute(query, query_params) + user_with_goal_ids = cr.dictfetchall() + user_without_goal_ids = list(set([user.id for user in challenge.user_ids]) - set([user['user_id'] for user in user_with_goal_ids])) - # resume canceled goals - domain.append(('state', '=', 'canceled')) - canceled_goal_ids = goal_obj.search(cr, uid, domain, context=context) - if canceled_goal_ids: - goal_obj.write(cr, uid, canceled_goal_ids, {'state': 'inprogress'}, context=context) - to_update.extend(canceled_goal_ids) + values = { + 'definition_id': line.definition_id.id, + 'line_id': line.id, + 'target_goal': line.target_goal, + 'state': 'inprogress', + } - # skip to next user - continue + if start_date: + values['start_date'] = start_date + if end_date: + values['end_date'] = end_date - values = { - 'definition_id': line.definition_id.id, - 'line_id': line.id, - 'user_id': user.id, - 'target_goal': line.target_goal, - 'state': 'inprogress', - } + if challenge.remind_update_delay: + values['remind_update_delay'] = challenge.remind_update_delay - if start_date: - values['start_date'] = start_date - if end_date: - values['end_date'] = end_date - - if challenge.remind_update_delay: - values['remind_update_delay'] = challenge.remind_update_delay - - new_goal_id = goal_obj.create(cr, uid, values, context=context) - to_update.append(new_goal_id) + for user_id in user_without_goal_ids: + values.update({'user_id': user_id}) + goal_id = goal_obj.create(cr, uid, values, context=context) + to_update.append(goal_id) goal_obj.update(cr, uid, to_update, context=context) @@ -638,7 +658,7 @@ class gamification_challenge(osv.Model): message = "%s has joined the challenge" % user.name self.message_post(cr, SUPERUSER_ID, challenge_ids, body=message, context=context) self.write(cr, SUPERUSER_ID, challenge_ids, {'invited_user_ids': [(3, user_id)], 'user_ids': [(4, user_id)]}, context=context) - return self.generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context) + return self._generate_goals_from_challenge(cr, SUPERUSER_ID, challenge_ids, context=context) # TODO in trunk, remove unused parameter user_id def discard_challenge(self, cr, uid, challenge_ids, context=None, user_id=None): diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py index 330311ba05e..67020520467 100644 --- a/addons/gamification/models/res_users.py +++ b/addons/gamification/models/res_users.py @@ -19,7 +19,6 @@ # ############################################################################## -from openerp import SUPERUSER_ID from openerp.osv import osv from challenge import MAX_VISIBILITY_RANKING diff --git a/addons/gamification/views/challenge.xml b/addons/gamification/views/challenge.xml index 75dcd4a7959..87dea680222 100644 --- a/addons/gamification/views/challenge.xml +++ b/addons/gamification/views/challenge.xml @@ -48,6 +48,7 @@