diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-07-29 14:10:30 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-07-29 14:10:30 +0200 |
| commit | d5e1fd16b4fd796cc25b2b2d694dadb375110ce7 (patch) | |
| tree | 35d130b4161985cd9ca3071a59980b52e8401be5 /addons/gamification | |
| parent | ae71f95371b4a5c99c10a23e3553c5badc45a1ac (diff) | |
| parent | cda4f3c308ca233aead81f5ae97d250b5b7bf82f (diff) | |
[MERGE] forward port branch saas-12.4 up to cda4f3c308c
Diffstat (limited to 'addons/gamification')
| -rw-r--r-- | addons/gamification/i18n/cs.po | 15 | ||||
| -rw-r--r-- | addons/gamification/models/gamification_karma_rank.py | 15 | ||||
| -rw-r--r-- | addons/gamification/models/res_users.py | 84 | ||||
| -rw-r--r-- | addons/gamification/tests/__init__.py | 1 | ||||
| -rw-r--r-- | addons/gamification/tests/test_recompute_rank.py | 128 |
5 files changed, 236 insertions, 7 deletions
diff --git a/addons/gamification/i18n/cs.po b/addons/gamification/i18n/cs.po index 6f37d037a24..c032b348152 100644 --- a/addons/gamification/i18n/cs.po +++ b/addons/gamification/i18n/cs.po @@ -153,6 +153,8 @@ msgid "" "<br/>Nobody has succeeded to reach every goal, no badge is rewarded for this" " challenge." msgstr "" +"<br/>Nikomu se nepodařilo dosáhnout jakéhokoliv cíle, za tuto výzvu není " +"odměněn žádný odznak." #. module: gamification #: code:addons/gamification/models/challenge.py:655 @@ -189,6 +191,8 @@ msgid "" "<span class=\"o_stat_text\">Related</span>\n" " <span class=\"o_stat_text\">Goals</span>" msgstr "" +"<span class=\"o_stat_text\">Související</span>\n" +" <span class=\"o_stat_text\">Cíle</span>" #. module: gamification #: model:mail.template,body_html:gamification.simple_report_template @@ -398,6 +402,9 @@ msgid "" " It can be deserved automatically when some conditions are met or manually by users.\n" " Some badges are harder than others to get with specific conditions." msgstr "" +"Odznak je symbol udělený uživateli jako znak odměny.\n" +"To může být zaslouženo automaticky, když jsou splněny některé podmínky, nebo ručně uživateli.\n" +"Některé odznaky je těžší získat než jiné v závislosti na specifickýxch podmákách." #. module: gamification #: model_terms:ir.actions.act_window,help:gamification.goal_definition_list_action @@ -405,6 +412,8 @@ msgid "" "A goal definition is a technical model of goal defining a condition to reach.\n" " The dates, values to reach or users are defined in goal instance." msgstr "" +"Definice cíle je technický model cíle, který definuje podmínku pro dosažení cíle.\n" +"Data, hodnoty k dosažení nebo uživatelé jsou definováni v instanci cíle." #. module: gamification #: model:ir.model.fields,help:gamification.field_gamification_challenge_line__condition @@ -414,6 +423,8 @@ msgid "" "A goal is considered as completed when the current value is compared to the " "value to reach" msgstr "" +"Cíl je považován za dokončený, když je aktuální hodnota porovnána s " +"hodnotou, která má být dosažena" #. module: gamification #: model_terms:ir.actions.act_window,help:gamification.goal_list_action @@ -421,6 +432,8 @@ msgid "" "A goal is defined by a user and a goal definition.\n" " Goals can be created automatically by using challenges." msgstr "" +"Cíl je definován uživatelem a definicí cíle.\n" +"Cíle lze vytvářet automaticky pomocí výzev." #. module: gamification #: selection:gamification.badge,rule_auth:0 @@ -972,7 +985,7 @@ msgstr "" #. module: gamification #: model:ir.model,name:gamification.model_gamification_badge_user_wizard msgid "Gamification User Badge Wizard" -msgstr "" +msgstr "Průvodce odznakem uživatele Gamifikace" #. module: gamification #: model:ir.model,name:gamification.model_gamification_challenge_line diff --git a/addons/gamification/models/gamification_karma_rank.py b/addons/gamification/models/gamification_karma_rank.py index cbbe7bf19d1..f88712f3c44 100644 --- a/addons/gamification/models/gamification_karma_rank.py +++ b/addons/gamification/models/gamification_karma_rank.py @@ -55,8 +55,19 @@ class KarmaRank(models.Model): return res def write(self, vals): + if 'karma_min' in vals: + previous_ranks = self.env['gamification.karma.rank'].search([], order="karma_min DESC").ids + low = min(vals['karma_min'], self.karma_min) + high = max(vals['karma_min'], self.karma_min) + tools.image_resize_images(vals) res = super(KarmaRank, self).write(vals) - users = self.env['res.users'].sudo().search([('karma', '>', 0)]) - users._recompute_rank() + + if 'karma_min' in vals: + after_ranks = self.env['gamification.karma.rank'].search([], order="karma_min DESC").ids + if previous_ranks != after_ranks: + users = self.env['res.users'].sudo().search([('karma', '>', 0)]) + else: + users = self.env['res.users'].sudo().search([('karma', '>=', low), ('karma', '<=', high)]) + users._recompute_rank() return res diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py index e262eec4a8e..3e8b6d8e1cd 100644 --- a/addons/gamification/models/res_users.py +++ b/addons/gamification/models/res_users.py @@ -7,9 +7,11 @@ import logging _logger = logging.getLogger(__name__) + class Users(models.Model): _inherit = 'res.users' + karma = fields.Integer('Karma', default=0) badge_ids = fields.One2many('gamification.badge.user', 'user_id', string='Badges', copy=False) gold_badge = fields.Integer('Gold badges count', compute="_get_user_badge_level") @@ -59,18 +61,32 @@ class Users(models.Model): return True def _rank_changed(self): - if self.rank_id.karma_min > 0: - template = self.env.ref('gamification.mail_template_data_new_rank_reached', raise_if_not_found=False) - if template: - template.send_mail(self.id, force_send=True, notif_layout='mail.mail_notification_light') + """ + Method that can be called on a batch of users with the same new rank + """ + template = self.env.ref('gamification.mail_template_data_new_rank_reached', raise_if_not_found=False) + if template: + for u in self: + if u.rank_id.karma_min > 0: + template.send_mail(u.id, force_send=len(self) == 1, notif_layout='mail.mail_notification_light') def _recompute_rank(self): """ The caller should filter the users on karma > 0 before calling this method to avoid looping on every single users + + Compute rank of each user by user. + For each user, check the rank of this user """ + ranks = [{'rank': rank, 'karma_min': rank.karma_min} for rank in self.env['gamification.karma.rank'].search([], order="karma_min DESC")] + + # 3 is the number of search/requests used by rank in _recompute_rank_bulk() + if len(self) > len(ranks) * 3: + self._recompute_rank_bulk() + return + for user in self: old_rank = user.rank_id if user.karma == 0 and ranks: @@ -86,6 +102,66 @@ class Users(models.Model): if old_rank != user.rank_id: user._rank_changed() + def _recompute_rank_bulk(self): + """ + Compute rank of each user by rank. + For each rank, check which users need to be ranked + + """ + ranks = [{'rank': rank, 'karma_min': rank.karma_min} for rank in + self.env['gamification.karma.rank'].search([], order="karma_min DESC")] + + users_todo = self + + next_rank_id = False + # wtf, next_rank_id should be a related on rank_id.next_rank_id and life might get easier. + # And we only need to recompute next_rank_id on write with min_karma or in the create on rank model. + for r in ranks: + rank_id = r['rank'].id + dom = [ + ('karma', '>=', r['karma_min']), + ('id', 'in', users_todo.ids), + '|', # noqa + '|', ('rank_id', '!=', rank_id), ('rank_id', '=', False), + '|', ('next_rank_id', '!=', next_rank_id), ('next_rank_id', '=', False if next_rank_id else -1), + ] + users = self.env['res.users'].search(dom) + if users: + users_to_notify = self.env['res.users'].search([ + ('karma', '>=', r['karma_min']), + '|', ('rank_id', '!=', rank_id), ('rank_id', '=', False), + ('id', 'in', users.ids), + ]) + users.write({ + 'rank_id': rank_id, + 'next_rank_id': next_rank_id, + }) + users_to_notify._rank_changed() + users_todo -= users + + nothing_to_do_users = self.env['res.users'].search([ + ('karma', '>=', r['karma_min']), + '|', ('rank_id', '=', rank_id), ('next_rank_id', '=', next_rank_id), + ('id', 'in', users_todo.ids), + ]) + users_todo -= nothing_to_do_users + next_rank_id = r['rank'].id + + if ranks: + lower_rank = ranks[-1]['rank'] + users = self.env['res.users'].search([ + ('karma', '>=', 0), + ('karma', '<', lower_rank.karma_min), + '|', ('rank_id', '!=', False), ('next_rank_id', '!=', lower_rank.id), + ('id', 'in', users_todo.ids), + ]) + if users: + users.write({ + 'rank_id': False, + 'next_rank_id': lower_rank.id, + }) + + def _get_next_rank(self): """ For fresh users with 0 karma that don't have a rank_id and next_rank_id yet this method returns the first karma rank (by karma ascending). This acts as a diff --git a/addons/gamification/tests/__init__.py b/addons/gamification/tests/__init__.py index 0219f6754d0..a6a599bf6c8 100644 --- a/addons/gamification/tests/__init__.py +++ b/addons/gamification/tests/__init__.py @@ -2,3 +2,4 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from . import test_challenge +from . import test_recompute_rank diff --git a/addons/gamification/tests/test_recompute_rank.py b/addons/gamification/tests/test_recompute_rank.py new file mode 100644 index 00000000000..c82fb4759fd --- /dev/null +++ b/addons/gamification/tests/test_recompute_rank.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests import common +from unittest.mock import patch + + +class TestComputeRankCommon(common.TransactionCase): + + def setUp(self): + super(TestComputeRankCommon, self).setUp() + + def _patched_send_mail(*args, **kwargs): + pass + + self.User = self.env['res.users'].with_context(no_reset_password=True, mail_create_nosubscribe=True) + self.users = self.User + + patch_email = patch('odoo.addons.mail.models.mail_template.MailTemplate.send_mail', _patched_send_mail) + patch_email.start() + for k in range(-5, 1030, 30): + self.users += self.User.create({ + 'name': str(k), + 'login': "test_recompute_rank_%s" % k, + 'karma': k, + }) + + self.env['gamification.karma.rank'].search([]).unlink() + + self.rank_1 = self.env['gamification.karma.rank'].create({ + 'name': 'rank 1', + 'karma_min': 0, + }) + + self.rank_2 = self.env['gamification.karma.rank'].create({ + 'name': 'rank 2', + 'karma_min': 250, + }) + + self.rank_3 = self.env['gamification.karma.rank'].create({ + 'name': 'rank 3', + 'karma_min': 500, + }) + self.rank_4 = self.env['gamification.karma.rank'].create({ + 'name': 'rank 4', + 'karma_min': 1000, + }) + + patch_email.stop() + + +class test_recompute(TestComputeRankCommon): + + def test_00_initial_compute(self): + + self.assertEqual(len(self.users), 35) + + self.assertEqual( + len(self.rank_1.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_1.karma_min and u.karma < self.rank_2.karma_min]) + ) + self.assertEqual( + len(self.rank_2.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_2.karma_min and u.karma < self.rank_3.karma_min]) + ) + self.assertEqual( + len(self.rank_3.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_3.karma_min and u.karma < self.rank_4.karma_min]) + ) + self.assertEqual( + len(self.rank_4.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_4.karma_min]) + ) + + def test_01_switch_rank(self): + + self.assertEqual(len(self.users), 35) + + self.rank_3.karma_min = 100 + # rank_1 -> rank_3 -> rank_2 -> rank_4 + + self.assertEqual( + len(self.rank_1.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_1.karma_min and u.karma < self.rank_3.karma_min]) + ) + self.assertEqual( + len(self.rank_3.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_3.karma_min and u.karma < self.rank_2.karma_min]) + ) + self.assertEqual( + len(self.rank_2.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_2.karma_min and u.karma < self.rank_4.karma_min]) + ) + self.assertEqual( + len(self.rank_4.user_ids & self.users), + len([u for u in self.users if u.karma >= self.rank_4.karma_min]) + ) + + def test_02_update_rank_without_switch(self): + number_of_users = False + + def _patched_recompute_rank(_self, *args, **kwargs): + nonlocal number_of_users + number_of_users = len(_self & self.users) + + patch_bulk = patch('odoo.addons.gamification.models.res_users.Users._recompute_rank', _patched_recompute_rank) + patch_bulk.start() + self.rank_3.karma_min = 700 + self.assertEqual(number_of_users, 7, "Should just recompute for the 7 users between 500 and 700") + patch_bulk.stop() + + def test_03_test_bulk_call(self): + self.assertEqual(len(self.users), 35) + + def _patched_check_in_bulk(*args, **kwargs): + raise + + patch_bulk = patch('odoo.addons.gamification.models.res_users.Users._recompute_rank_bulk', _patched_check_in_bulk) + patch_bulk.start() + + # call on 5 users should not trigger the bulk function + self.users[0:5]._recompute_rank() + + # call on 50 users should trigger the bulk function + with self.assertRaises(Exception): + self.users[0:50]._recompute_rank() + + patch_bulk.stop() |
