diff --git a/addons/gamification/data/gamification_karma_rank_data.xml b/addons/gamification/data/gamification_karma_rank_data.xml
index 9cb6d088de6..763bfe54e17 100644
--- a/addons/gamification/data/gamification_karma_rank_data.xml
+++ b/addons/gamification/data/gamification_karma_rank_data.xml
@@ -1,11 +1,15 @@
-
- 2500
+
+
+ 2500
+ I am the Root!
-
- 2500
+
+
+ 2500
+ I am the Admin!
diff --git a/addons/gamification/data/gamification_karma_tracking_demo.xml b/addons/gamification/data/gamification_karma_tracking_demo.xml
index 1b08eca6336..f17c379bd85 100644
--- a/addons/gamification/data/gamification_karma_tracking_demo.xml
+++ b/addons/gamification/data/gamification_karma_tracking_demo.xml
@@ -32,9 +32,6 @@
2500
-
- 2500
-
30
-
- 30
-
-
+
Gamification: Karma tracking consolidation
code
- model._consolidate_last_month()
+ model._consolidate_cron()
1
months
diff --git a/addons/gamification/models/gamification_karma_tracking.py b/addons/gamification/models/gamification_karma_tracking.py
index f7b184ad24d..1dd326691c2 100644
--- a/addons/gamification/models/gamification_karma_tracking.py
+++ b/addons/gamification/models/gamification_karma_tracking.py
@@ -1,69 +1,135 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-import calendar
-
from dateutil.relativedelta import relativedelta
-from odoo import api, fields, models
+from odoo import _, api, fields, models
+from odoo.tools import date_utils
class KarmaTracking(models.Model):
_name = 'gamification.karma.tracking'
_description = 'Track Karma Changes'
_rec_name = 'user_id'
- _order = 'tracking_date DESC'
+ _order = 'tracking_date desc, id desc'
- user_id = fields.Many2one('res.users', 'User', index=True, readonly=True, required=True, ondelete='cascade')
- old_value = fields.Integer('Old Karma Value', required=True, readonly=True)
- new_value = fields.Integer('New Karma Value', required=True, readonly=True)
+ def _get_origin_selection_values(self):
+ return [('res.users', _('User'))]
+
+ user_id = fields.Many2one('res.users', 'User', index=True, required=True, ondelete='cascade')
+ old_value = fields.Integer('Old Karma Value', readonly=True)
+ new_value = fields.Integer('New Karma Value', required=True)
+ gain = fields.Integer('Gain', compute='_compute_gain', readonly=False)
consolidated = fields.Boolean('Consolidated')
- tracking_date = fields.Date(default=fields.Date.context_today)
+
+ tracking_date = fields.Datetime(default=fields.Datetime.now, readonly=True, index=True)
+ reason = fields.Text(default=lambda self: _('Add Manually'), string='Description')
+ origin_ref = fields.Reference(
+ string='Source',
+ selection=lambda self: self._get_origin_selection_values(),
+ default=lambda self: f'res.users,{self.env.user.id}',
+ )
+ origin_ref_model_name = fields.Selection(
+ string='Source Type', selection=lambda self: self._get_origin_selection_values(),
+ compute='_compute_origin_ref_model_name', store=True)
+
+ @api.depends('old_value', 'new_value')
+ def _compute_gain(self):
+ for karma in self:
+ karma.gain = karma.new_value - (karma.old_value or 0)
+
+ @api.depends('origin_ref')
+ def _compute_origin_ref_model_name(self):
+ for karma in self:
+ if not karma.origin_ref:
+ karma.origin_ref_model_name = False
+ continue
+
+ karma.origin_ref_model_name = karma.origin_ref._name
+
+ @api.model_create_multi
+ def create(self, values_list):
+ # fill missing old value with current user karma
+ users = self.env['res.users'].browse([
+ values['user_id']
+ for values in values_list
+ if 'old_value' not in values and values.get('user_id')
+ ])
+ karma_per_users = {user.id: user.karma for user in users}
+
+ for values in values_list:
+ if 'old_value' not in values and values.get('user_id'):
+ values['old_value'] = karma_per_users[values['user_id']]
+
+ if 'gain' in values and 'old_value' in values:
+ values['new_value'] = values['old_value'] + values['gain']
+ del values['gain']
+
+ return super().create(values_list)
@api.model
- def _consolidate_last_month(self):
- """ Consolidate last month. Used by a cron to cleanup tracking records. """
- previous_month_start = fields.Date.today() + relativedelta(months=-1, day=1)
- return self._process_consolidate(previous_month_start)
+ def _consolidate_cron(self):
+ """Consolidate the trackings 2 months ago. Used by a cron to cleanup tracking records."""
+ from_date = date_utils.start_of(fields.Datetime.today(), 'month') - relativedelta(months=2)
+ return self._process_consolidate(from_date)
+
+ def _process_consolidate(self, from_date, end_date=None):
+ """Consolidate the karma trackings.
+
+ The consolidation keeps, for each user, the oldest "old_value" and the most recent
+ "new_value", creates a new karma tracking with those values and removes all karma
+ trackings between those dates. The origin / reason is changed on the consolidated
+ records, so this information is lost in the process.
+ """
+ self.env['gamification.karma.tracking'].flush_model()
+
+ if not end_date:
+ end_date = date_utils.end_of(date_utils.end_of(from_date, 'month'), 'day')
- def _process_consolidate(self, from_date):
- """ Consolidate trackings into a single record for a given month, starting
- at a from_date (included). End date is set to last day of current month
- using a smart calendar.monthrange construction. """
- end_date = from_date + relativedelta(day=calendar.monthrange(from_date.year, from_date.month)[1])
select_query = """
-SELECT user_id,
-(
- SELECT old_value from gamification_karma_tracking old_tracking
- WHERE old_tracking.user_id = gamification_karma_tracking.user_id
- AND tracking_date::timestamp BETWEEN %(from_date)s AND %(to_date)s
- AND consolidated IS NOT TRUE
- ORDER BY tracking_date ASC LIMIT 1
-), (
- SELECT new_value from gamification_karma_tracking new_tracking
- WHERE new_tracking.user_id = gamification_karma_tracking.user_id
- AND tracking_date::timestamp BETWEEN %(from_date)s AND %(to_date)s
- AND consolidated IS NOT TRUE
- ORDER BY tracking_date DESC LIMIT 1
-)
-FROM gamification_karma_tracking
-WHERE tracking_date::timestamp BETWEEN %(from_date)s AND %(to_date)s
-AND consolidated IS NOT TRUE
-GROUP BY user_id """
+ WITH old_tracking AS (
+ SELECT DISTINCT ON (user_id) user_id, old_value, tracking_date
+ FROM gamification_karma_tracking
+ WHERE tracking_date BETWEEN %(from_date)s
+ AND %(end_date)s
+ AND consolidated IS NOT TRUE
+ ORDER BY user_id, tracking_date ASC, id ASC
+ )
+ INSERT INTO gamification_karma_tracking (
+ user_id,
+ old_value,
+ new_value,
+ tracking_date,
+ origin_ref,
+ consolidated,
+ reason)
+ SELECT DISTINCT ON (nt.user_id)
+ nt.user_id,
+ ot.old_value AS old_value,
+ nt.new_value AS new_value,
+ ot.tracking_date AS from_tracking_date,
+ %(origin_ref)s AS origin_ref,
+ TRUE,
+ %(reason)s
+ FROM gamification_karma_tracking AS nt
+ JOIN old_tracking AS ot
+ ON ot.user_id = nt.user_id
+ WHERE nt.tracking_date BETWEEN %(from_date)s
+ AND %(end_date)s
+ AND nt.consolidated IS NOT TRUE
+ ORDER BY nt.user_id, nt.tracking_date DESC, id DESC
+ """
+
self.env.cr.execute(select_query, {
'from_date': from_date,
- 'to_date': end_date,
+ 'end_date': end_date,
+ 'origin_ref': f'res.users,{self.env.user.id}',
+ 'reason': _('Consolidation from %s to %s', from_date.date(), end_date.date()),
})
- results = self.env.cr.dictfetchall()
- if results:
- for result in results:
- result['consolidated'] = True
- result['tracking_date'] = fields.Date.to_string(from_date)
- self.create(results)
- self.search([
- ('tracking_date', '>=', from_date),
- ('tracking_date', '<=', end_date),
- ('consolidated', '!=', True)]
- ).unlink()
+ self.search([
+ ('tracking_date', '>=', from_date),
+ ('tracking_date', '<=', end_date),
+ ('consolidated', '!=', True)]
+ ).with_context(skip_karma_computation=True).unlink()
return True
diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py
index 5f76b41e3fc..e23c15a749a 100644
--- a/addons/gamification/models/res_users.py
+++ b/addons/gamification/models/res_users.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, fields, models
+from odoo import _, api, fields, models
class Users(models.Model):
_inherit = 'res.users'
- karma = fields.Integer('Karma', default=0)
+ karma = fields.Integer('Karma', compute='_compute_karma', store=True, readonly=False)
karma_tracking_ids = fields.One2many('gamification.karma.tracking', 'user_id', string='Karma Changes', groups="base.group_system")
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")
@@ -16,6 +16,33 @@ class Users(models.Model):
rank_id = fields.Many2one('gamification.karma.rank', 'Rank')
next_rank_id = fields.Many2one('gamification.karma.rank', 'Next Rank')
+ @api.depends('karma_tracking_ids.new_value')
+ def _compute_karma(self):
+ if self.env.context.get('skip_karma_computation'):
+ # do not need to update the user karma
+ # e.g. during the tracking consolidation
+ return
+
+ self.env['gamification.karma.tracking'].flush_model()
+
+ select_query = """
+ SELECT DISTINCT ON (user_id) user_id, new_value
+ FROM gamification_karma_tracking
+ WHERE user_id = ANY(%(user_ids)s)
+ ORDER BY user_id, tracking_date DESC, id DESC
+ """
+ self.env.cr.execute(select_query, {'user_ids': self.ids})
+
+ user_karma_map = {
+ values['user_id']: values['new_value']
+ for values in self.env.cr.dictfetchall()
+ }
+
+ for user in self:
+ user.karma = user_karma_map.get(user.id, 0)
+
+ self.sudo()._recompute_rank()
+
@api.depends('badge_ids')
def _get_user_badge_level(self):
""" Return total badge per level of users
@@ -43,34 +70,56 @@ class Users(models.Model):
def create(self, values_list):
res = super(Users, self).create(values_list)
- karma_trackings = []
- for user in res:
- if user.karma:
- karma_trackings.append({'user_id': user.id, 'old_value': 0, 'new_value': user.karma})
- if karma_trackings:
- self.env['gamification.karma.tracking'].sudo().create(karma_trackings)
+ self._add_karma_batch({
+ user: {
+ 'gain': int(vals['karma']),
+ 'old_value': 0,
+ 'origin_ref': f'res.users,{self.env.uid}',
+ 'reason': _('User Creation'),
+ }
+ for user, vals in zip(res, values_list)
+ if vals.get('karma')
+ })
- res._recompute_rank()
return res
- def write(self, vals):
- karma_trackings = []
- if 'karma' in vals:
- for user in self:
- if user.karma != vals['karma']:
- karma_trackings.append({'user_id': user.id, 'old_value': user.karma, 'new_value': vals['karma']})
+ def write(self, values):
+ if 'karma' in values:
+ self._add_karma_batch({
+ user: {
+ 'gain': int(values['karma']) - user.karma,
+ 'origin_ref': f'res.users,{self.env.uid}',
+ }
+ for user in self
+ if int(values['karma']) != user.karma
+ })
+ return super().write(values)
- result = super(Users, self).write(vals)
+ def _add_karma(self, gain, source=None, reason=None):
+ self.ensure_one()
+ values = {'gain': gain, 'source': source, 'reason': reason}
+ return self._add_karma_batch({self: values})
- if karma_trackings:
- self.env['gamification.karma.tracking'].sudo().create(karma_trackings)
- if 'karma' in vals:
- self._recompute_rank()
- return result
+ def _add_karma_batch(self, values_per_user):
+ if not values_per_user:
+ return
- def add_karma(self, karma):
- for user in self:
- user.karma += karma
+ create_values = []
+ for user, values in values_per_user.items():
+ origin = values.get('source') or self.env.user
+ reason = values.get('reason') or _('Add Manually')
+ origin_description = f'{origin.display_name} #{origin.id}'
+ old_value = values.get('old_value', user.karma)
+
+ create_values.append({
+ 'new_value': old_value + values['gain'],
+ 'old_value': old_value,
+ 'origin_ref': f'{origin._name},{origin.id}',
+ 'reason': f'{reason} ({origin_description})',
+ 'user_id': user.id,
+ })
+
+ self.env['gamification.karma.tracking'].sudo().create(create_values)
return True
def _get_tracking_karma_gain_position(self, user_domain, from_date=None, to_date=None):
@@ -104,10 +153,10 @@ class Users(models.Model):
params = []
if from_date:
- date_from_condition = 'AND tracking.tracking_date::timestamp >= timestamp %s'
+ date_from_condition = 'AND tracking.tracking_date::DATE >= %s::DATE'
params.append(from_date)
if to_date:
- date_to_condition = 'AND tracking.tracking_date::timestamp <= timestamp %s'
+ date_to_condition = 'AND tracking.tracking_date::DATE <= %s::DATE'
params.append(to_date)
params.append(tuple(self.ids))
@@ -303,3 +352,18 @@ WHERE sub.user_id IN %%s""" % {
"""
self.ensure_one()
return []
+
+ def action_karma_report(self):
+ self.ensure_one()
+
+ return {
+ 'name': _('Karma Updates'),
+ 'res_model': 'gamification.karma.tracking',
+ 'target': 'current',
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'tree',
+ 'context': {
+ 'default_user_id': self.id,
+ 'search_default_user_id': self.id,
+ },
+ }
diff --git a/addons/gamification/tests/test_karma_tracking.py b/addons/gamification/tests/test_karma_tracking.py
index 75a210fa012..a0da39bd8ae 100644
--- a/addons/gamification/tests/test_karma_tracking.py
+++ b/addons/gamification/tests/test_karma_tracking.py
@@ -1,11 +1,12 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from datetime import date
+from datetime import datetime
from dateutil.relativedelta import relativedelta
+from freezegun import freeze_time
from unittest.mock import patch
-from odoo import exceptions, fields
+from odoo import exceptions, fields, _
from odoo.addons.mail.tests.common import mail_new_test_user
from odoo.tests import common
@@ -29,7 +30,9 @@ class TestKarmaTrackingCommon(common.TransactionCase):
)
cls.env['gamification.karma.tracking'].search([]).unlink()
- cls.test_date = fields.Date.today() + relativedelta(month=4, day=1)
+ cls.test_date = datetime(2021, 6, 1)
+ cls.first_day_of_test_date_month = '2021-06-01'
+ cls.first_day_of_test_date_next_month = '2021-07-01'
@classmethod
def _create_trackings(cls, user, karma, steps, track_date, days_delta=1):
@@ -41,7 +44,7 @@ class TestKarmaTrackingCommon(common.TransactionCase):
'old_value': old_value,
'new_value': new_value,
'consolidated': False,
- 'tracking_date': fields.Date.to_string(track_date)
+ 'tracking_date': fields.Datetime.to_string(track_date)
}])
old_value = new_value
track_date = track_date + relativedelta(days=days_delta)
@@ -77,25 +80,67 @@ class TestKarmaTrackingCommon(common.TransactionCase):
results = self.env['res.users']._get_tracking_karma_gain_position([])
self.assertEqual(len(results), 0)
+ @freeze_time('2021-02-02')
def test_consolidation_cron(self):
- self.patcher = patch('odoo.addons.gamification.models.gamification_karma_tracking.fields.Date', wraps=fields.Date)
- self.mock_datetime = self.startPatcher(self.patcher)
- self.mock_datetime.today.return_value = date(self.test_date.year, self.test_date.month + 1, self.test_date.day)
+ Tracking = self.env['gamification.karma.tracking']
- self._create_trackings(self.test_user, 20, 2, self.test_date, days_delta=30)
- self._create_trackings(self.test_user_2, 10, 20, self.test_date, days_delta=2)
- self.env['gamification.karma.tracking']._consolidate_last_month()
- consolidated = self.env['gamification.karma.tracking'].search([
- ('user_id', 'in', (self.test_user | self.test_user_2).ids),
- ('consolidated', '=', True),
- ('tracking_date', '=', self.test_date)
+ # Sanity check
+ self.assertFalse(Tracking.search_count([('user_id', 'in', (self.test_user | self.test_user_2).ids)]))
+
+ test_date = datetime(2020, 12, 15)
+ first_day_of_test_date_month = '2020-12-01'
+ first_day_of_test_date_next_month = '2021-01-01'
+
+ self._create_trackings(self.test_user, karma=20, steps=2, track_date=test_date, days_delta=30)
+ self._create_trackings(self.test_user_2, karma=10, steps=20, track_date=test_date, days_delta=2)
+
+ # Sanity check
+ self.assertEqual(Tracking.search_count([('user_id', '=', self.test_user.id)]), 2)
+ self.assertEqual(Tracking.search_count([('user_id', '=', self.test_user_2.id)]), 20)
+ self.assertEqual(self.test_user.karma, 40)
+ self.assertEqual(self.test_user_2.karma, 200)
+
+ with self.assertQueryCount(8), patch.object(type(self.env['res.users']), 'write') as patched_user_write:
+ Tracking._consolidate_cron()
+
+ # consolidation should not change user karma
+ self.assertFalse(patched_user_write.called, "User karma didn't change during consolidation, it should not be updated")
+ self.assertEqual(self.test_user.karma, 40)
+ self.assertEqual(self.test_user_2.karma, 200)
+
+ consolidated_1 = Tracking.search([
+ ('user_id', '=', self.test_user.id),
+ ('tracking_date', '>=', first_day_of_test_date_month),
+ ('tracking_date', '<', first_day_of_test_date_next_month),
])
- self.assertEqual(len(consolidated), 2)
- unconsolidated = self.env['gamification.karma.tracking'].search([
- ('user_id', 'in', (self.test_user | self.test_user_2).ids),
+ self.assertEqual(len(consolidated_1), 1)
+ self.assertTrue(consolidated_1.consolidated)
+ self.assertEqual(consolidated_1.old_value, 0)
+ self.assertEqual(consolidated_1.new_value, 20)
+ self.assertEqual(consolidated_1.reason, 'Consolidation from 2020-12-01 to 2020-12-31')
+
+ consolidated_2 = Tracking.search([
+ ('user_id', '=', self.test_user_2.id),
+ ('tracking_date', '>=', first_day_of_test_date_month),
+ ('tracking_date', '<', first_day_of_test_date_next_month),
+ ])
+ self.assertEqual(len(consolidated_2), 1)
+ self.assertTrue(consolidated_2.consolidated)
+ self.assertEqual(consolidated_2.old_value, 0)
+ self.assertEqual(consolidated_2.new_value, 10 * 9) # 9 records have been consolidated
+ self.assertEqual(consolidated_2.reason, 'Consolidation from 2020-12-01 to 2020-12-31')
+
+ unconsolidated_1 = Tracking.search_count([
+ ('user_id', '=', self.test_user.id),
('consolidated', '=', False),
])
- self.assertEqual(len(unconsolidated), 6) # 5 for test user 2, 1 for test user
+ self.assertEqual(unconsolidated_1, 1)
+
+ unconsolidated_2 = Tracking.search_count([
+ ('user_id', '=', self.test_user_2.id),
+ ('consolidated', '=', False),
+ ])
+ self.assertEqual(unconsolidated_2, 11)
def test_consolidation_monthly(self):
Tracking = self.env['gamification.karma.tracking']
@@ -107,11 +152,12 @@ class TestKarmaTrackingCommon(common.TransactionCase):
Tracking._process_consolidate(self.test_date)
consolidated = Tracking.search([
('user_id', '=', self.test_user_2.id),
- ('consolidated', '=', True),
- ('tracking_date', '=', self.test_date)
+ ('tracking_date', '>=', self.first_day_of_test_date_month),
+ ('tracking_date', '<', self.first_day_of_test_date_next_month),
])
self.assertEqual(len(consolidated), 1)
- self.assertEqual(consolidated.old_value, base_test_user_2_karma) # 15 2-days span, from 1 to 29 included = 15 steps -> 150 karma
+ self.assertTrue(consolidated.consolidated)
+ self.assertEqual(consolidated.old_value, base_test_user_2_karma)
self.assertEqual(consolidated.new_value, base_test_user_2_karma + 150) # 15 2-days span, from 1 to 29 included = 15 steps -> 150 karma
remaining = Tracking.search([
@@ -130,7 +176,7 @@ class TestKarmaTrackingCommon(common.TransactionCase):
self.assertEqual(len(consolidated), 2)
self.assertEqual(consolidated[0].new_value, base_test_user_2_karma + 200) # 5 remaining 2-days span, from 1 to 9 included = 5 steps -> 50 karma
self.assertEqual(consolidated[0].old_value, base_test_user_2_karma + 150) # coming from previous iteration
- self.assertEqual(consolidated[0].tracking_date, self.test_date + relativedelta(months=1)) # tracking set at beginning of month
+ self.assertEqual(consolidated[0].tracking_date.date(), self.test_date.date() + relativedelta(months=1)) # tracking set at beginning of month
self.assertEqual(consolidated[-1].new_value, base_test_user_2_karma + 150) # previously created one still present
self.assertEqual(consolidated[-1].old_value, base_test_user_2_karma) # previously created one still present
@@ -160,11 +206,17 @@ class TestKarmaTrackingCommon(common.TransactionCase):
with self.assertRaises(exceptions.AccessError):
user.read(['karma_tracking_ids'])
- user.write({'karma': 60})
- user.add_karma(10)
+ user._add_karma(38, source=self.test_user_2)
self.assertEqual(user.karma, 70)
- trackings = self.env['gamification.karma.tracking'].sudo().search([('user_id', '=', user.id)])
- self.assertEqual(len(trackings), 3) # create + write + add_karma
+ trackings = self.env['gamification.karma.tracking'].sudo().search(
+ [('user_id', '=', user.id)], order="create_date ASC, id ASC")
+ self.assertEqual(len(trackings), 2) # create + add_karma
+ self.assertEqual(trackings[0].origin_ref, self.test_user)
+ self.assertEqual(trackings[0].reason, "User Creation (Test User #%i)" % self.test_user.id)
+ self.assertEqual(trackings[1].origin_ref, self.test_user_2)
+ self.assertIn("Add Manually", trackings[1].reason)
+ self.assertIn(self.test_user_2.display_name, trackings[1].reason)
+ self.assertIn(str(self.test_user_2.id), trackings[1].reason)
def test_user_tracking(self):
self.test_user.write({'groups_id': [
@@ -180,17 +232,61 @@ class TestKarmaTrackingCommon(common.TransactionCase):
self.assertEqual(user.karma_tracking_ids.old_value, 0)
self.assertEqual(user.karma_tracking_ids.new_value, 32)
- user.write({'karma': 60})
- user.add_karma(10)
+ user._add_karma(38)
self.assertEqual(user.karma, 70)
- self.assertEqual(len(user.karma_tracking_ids), 3)
- self.assertEqual(user.karma_tracking_ids[2].old_value, 60)
- self.assertEqual(user.karma_tracking_ids[2].new_value, 70)
+ self.assertEqual(len(user.karma_tracking_ids), 2)
self.assertEqual(user.karma_tracking_ids[1].old_value, 32)
- self.assertEqual(user.karma_tracking_ids[1].new_value, 60)
+ self.assertEqual(user.karma_tracking_ids[1].new_value, 70)
+ self.assertIn(_('Add Manually'), user.karma_tracking_ids[1].reason)
+ self.assertIn(self.test_user.display_name, user.karma_tracking_ids[1].reason)
+ self.assertIn(str(self.test_user.id), user.karma_tracking_ids[1].reason)
self.assertEqual(user.karma_tracking_ids[0].old_value, 0)
self.assertEqual(user.karma_tracking_ids[0].new_value, 32)
+ user._add_karma(69, user, _('Test Reason'))
+ self.assertEqual(len(user.karma_tracking_ids), 3)
+ self.assertIn(_('Test Reason'), user.karma_tracking_ids[2].reason)
+ self.assertEqual(user.karma, 139)
+
+ # add manually karma to a user (e.g. from the technical view)
+ tracking = self.env['gamification.karma.tracking'].create({
+ 'user_id': user.id,
+ 'new_value': 150,
+ 'consolidated': False,
+ })
+ self.assertEqual(tracking.old_value, 139)
+ self.assertEqual(tracking.gain, 11)
+ self.assertEqual(user.karma, 150)
+
+ # write directly on the karma field, should generate
+ self.test_user_2.karma = 100 # won't change
+ last_tracking_3 = self.test_user_2.karma_tracking_ids[-1]
+
+ users = (user | self.test_user | self.test_user_2).with_user(self.test_user)
+ with self.assertQueryCount(12):
+ users.karma = 100
+
+ tracking_1 = user.karma_tracking_ids[-1]
+ tracking_2 = self.test_user.karma_tracking_ids[-1]
+ tracking_3 = self.test_user_2.karma_tracking_ids[-1]
+
+ self.assertEqual(user.karma, 100)
+ self.assertEqual(self.test_user.karma, 100)
+ self.assertEqual(tracking_1.new_value, 100)
+ self.assertEqual(tracking_1.old_value, 150)
+ self.assertEqual(tracking_1.gain, -50)
+ self.assertEqual(tracking_1.reason, "Add Manually (Test User #%i)" % self.test_user.id)
+ self.assertEqual(tracking_1.origin_ref, self.test_user)
+ self.assertEqual(tracking_2.new_value, 100)
+ self.assertEqual(tracking_2.old_value, 0)
+ self.assertEqual(tracking_2.gain, 100)
+ self.assertEqual(tracking_2.reason, "Add Manually (Test User #%i)" % self.test_user.id)
+ self.assertEqual(tracking_2.origin_ref, self.test_user)
+ self.assertEqual(last_tracking_3, tracking_3, "Shouldn't have created a new tracking for the third user")
+ self.assertEqual(tracking_3.new_value, 100)
+ self.assertEqual(tracking_3.old_value, 0)
+ self.assertEqual(tracking_3.gain, 100)
+
class TestComputeRankCommon(common.TransactionCase):
diff --git a/addons/gamification/views/gamification_karma_tracking_views.xml b/addons/gamification/views/gamification_karma_tracking_views.xml
index 0592bf3e1de..53e25a8f6f4 100644
--- a/addons/gamification/views/gamification_karma_tracking_views.xml
+++ b/addons/gamification/views/gamification_karma_tracking_views.xml
@@ -5,12 +5,22 @@
gamification.karma.tracking
-
+
+
+
+
+
-
+
+
@@ -21,12 +31,15 @@
gamification.karma.tracking.view.tree
gamification.karma.tracking
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -35,14 +48,18 @@
gamification.karma.tracking.view.form
gamification.karma.tracking
-
@@ -50,9 +67,17 @@
- Trackings
+ Karma Tracking
gamification.karma.tracking
tree,form
+
+
+ No Karma Tracking
+
+
+ Track the sources of the users karma and monitor
+
+
diff --git a/addons/gamification/views/res_users_views.xml b/addons/gamification/views/res_users_views.xml
index f1e9f085714..59517fa82e1 100644
--- a/addons/gamification/views/res_users_views.xml
+++ b/addons/gamification/views/res_users_views.xml
@@ -7,9 +7,12 @@
res.users
-
-
-
+
+
+
diff --git a/addons/website_forum/__manifest__.py b/addons/website_forum/__manifest__.py
index f03cd9a5763..84b315f51ff 100644
--- a/addons/website_forum/__manifest__.py
+++ b/addons/website_forum/__manifest__.py
@@ -29,6 +29,7 @@ Ask questions, get answers, no distractions
'views/snippets/snippets.xml',
'views/website_pages_views.xml',
'views/forum_forum_add.xml',
+ 'views/gamification_karma_tracking_views.xml',
'security/ir.model.access.csv',
'security/website_forum_security.xml',
'data/badges_question.xml',
diff --git a/addons/website_forum/data/forum_demo.xml b/addons/website_forum/data/forum_demo.xml
index 2ff3d8853bd..29643f01dfa 100644
--- a/addons/website_forum/data/forum_demo.xml
+++ b/addons/website_forum/data/forum_demo.xml
@@ -45,6 +45,7 @@ In simple words does Odoo became CMS+ERP platform?
]]>
+
Re: How to configure alerts for employee contract expiration
Just for posterity so other can see. Here are the steps to set automatic alerts on any contract.. i.e. HR Employee, or Fleet for example. I will use fleet as an example.
@@ -57,6 +58,7 @@ In simple words does Odoo became CMS+ERP platform?]]>
+
Re: CMS replacement for ERP and eCommerce
+
1
+
1
diff --git a/addons/website_forum/models/__init__.py b/addons/website_forum/models/__init__.py
index cb7e2438978..c9dfe8b0bf2 100644
--- a/addons/website_forum/models/__init__.py
+++ b/addons/website_forum/models/__init__.py
@@ -2,6 +2,7 @@
from . import forum
from . import gamification
+from . import gamification_karma_tracking
from . import ir_attachment
from . import res_users
from . import website
diff --git a/addons/website_forum/models/forum.py b/addons/website_forum/models/forum.py
index 83bddbd0f69..d6e3c0a3475 100644
--- a/addons/website_forum/models/forum.py
+++ b/addons/website_forum/models/forum.py
@@ -550,7 +550,7 @@ class Post(models.Model):
# add karma for posting new questions
if not post.parent_id and post.state == 'active':
- self.env.user.sudo().add_karma(post.forum_id.karma_gen_question_new)
+ post.create_uid.sudo()._add_karma(post.forum_id.karma_gen_question_new, post, _('Ask a new question'))
posts.post_notification()
return posts
@@ -592,8 +592,10 @@ class Post(models.Model):
# update karma except for self-acceptance
mult = 1 if vals['is_correct'] else -1
if vals['is_correct'] != post.is_correct and post.create_uid.id != self._uid:
- post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * mult)
- self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accept * mult)
+ post.create_uid.sudo()._add_karma(post.forum_id.karma_gen_answer_accepted * mult, post,
+ _('User answer accepted') if mult > 0 else _('Accepted answer removed'))
+ self.env.user.sudo()._add_karma(post.forum_id.karma_gen_answer_accept * mult, post,
+ _('Validate an answer') if mult > 0 else _('Remove validated answer'))
if tag_ids:
if set(post.tag_ids.ids) != tag_ids and self.env.user.karma < post.forum_id.karma_edit_retag:
raise AccessError(_('%d karma required to retag.', post.forum_id.karma_edit_retag))
@@ -667,7 +669,7 @@ class Post(models.Model):
count_post = post.search_count([('parent_id', '=', False), ('forum_id', '=', post.forum_id.id), ('create_uid', '=', post.create_uid.id)])
if count_post == 1:
karma *= 10
- post.create_uid.sudo().add_karma(karma * -1)
+ post.create_uid.sudo()._add_karma(karma * -1, post, _('Reopen a banned question'))
self.sudo().write({'state': 'active'})
@@ -687,7 +689,12 @@ class Post(models.Model):
count_post = post.search_count([('parent_id', '=', False), ('forum_id', '=', post.forum_id.id), ('create_uid', '=', post.create_uid.id)])
if count_post == 1:
karma *= 10
- post.create_uid.sudo().add_karma(karma)
+ message = (
+ _('Post is closed and marked as spam')
+ if reason_id == reason_spam else
+ _('Post is closed and marked as offensive content')
+ )
+ post.create_uid.sudo()._add_karma(karma, post, message)
self.write({
'state': 'close',
@@ -703,7 +710,11 @@ class Post(models.Model):
raise AccessError(_('%d karma required to validate a post.', post.forum_id.karma_moderate))
# if state == pending, no karma previously added for the new question
if post.state == 'pending':
- post.create_uid.sudo().add_karma(post.forum_id.karma_gen_question_new)
+ post.create_uid.sudo()._add_karma(
+ post.forum_id.karma_gen_question_new,
+ post,
+ _('Ask a question'),
+ )
post.write({
'state': 'active',
'active': True,
@@ -747,7 +758,7 @@ class Post(models.Model):
raise AccessError(_('%d karma required to mark a post as offensive.', post.forum_id.karma_moderate))
# remove some karma
_logger.info('Downvoting user <%s> for posting spam/offensive contents', post.create_uid)
- post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_flagged)
+ post.create_uid.sudo()._add_karma(post.forum_id.karma_gen_answer_flagged, post, _('Downvote for posting offensive contents'))
# TODO: potential bottleneck, could be done in batch
post.write({
'state': 'offensive',
@@ -781,8 +792,8 @@ class Post(models.Model):
# if unlinking an answer with accepted answer: remove provided karma
for post in self:
if post.is_correct:
- post.create_uid.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
- self.env.user.sudo().add_karma(post.forum_id.karma_gen_answer_accepted * -1)
+ post.create_uid.sudo()._add_karma(post.forum_id.karma_gen_answer_accepted * -1, post, _('The accepted answer is deleted'))
+ self.env.user.sudo()._add_karma(post.forum_id.karma_gen_answer_accepted * -1, post, _('Delete the accepted answer'))
return super(Post, self).unlink()
def bump(self):
@@ -1074,12 +1085,22 @@ class Vote(models.Model):
]
def _get_karma_value(self, old_vote, new_vote, up_karma, down_karma):
- _karma_upd = {
- '-1': {'-1': 0, '0': -1 * down_karma, '1': -1 * down_karma + up_karma},
- '0': {'-1': 1 * down_karma, '0': 0, '1': up_karma},
- '1': {'-1': -1 * up_karma + down_karma, '0': -1 * up_karma, '1': 0}
- }
- return _karma_upd[old_vote][new_vote]
+ """Return the karma to add / remove based on the old vote and on the new vote."""
+ karma_values = {'-1': down_karma, '0': 0, '1': up_karma}
+ karma = karma_values[new_vote] - karma_values[old_vote]
+
+ if old_vote == new_vote:
+ reason = _('no changes')
+ elif new_vote == '1':
+ reason = _('upvoted')
+ elif new_vote == '-1':
+ reason = _('downvoted')
+ elif old_vote == '1':
+ reason = _('no more upvoted')
+ else:
+ reason = _('no more downvoted')
+
+ return karma, reason
@api.model_create_multi
def create(self, vals_list):
@@ -1139,10 +1160,20 @@ class Vote(models.Model):
def _vote_update_karma(self, old_vote, new_vote):
if self.post_id.parent_id:
- karma_value = self._get_karma_value(old_vote, new_vote, self.forum_id.karma_gen_answer_upvote, self.forum_id.karma_gen_answer_downvote)
+ karma, reason = self._get_karma_value(
+ old_vote,
+ new_vote,
+ self.forum_id.karma_gen_answer_upvote,
+ self.forum_id.karma_gen_answer_downvote)
+ source = _('Answer %s', reason)
else:
- karma_value = self._get_karma_value(old_vote, new_vote, self.forum_id.karma_gen_question_upvote, self.forum_id.karma_gen_question_downvote)
- self.recipient_id.sudo().add_karma(karma_value)
+ karma, reason = self._get_karma_value(
+ old_vote,
+ new_vote,
+ self.forum_id.karma_gen_question_upvote,
+ self.forum_id.karma_gen_question_downvote)
+ source = _('Question %s', reason)
+ self.recipient_id.sudo()._add_karma(karma, self.post_id, source)
class Tags(models.Model):
@@ -1170,6 +1201,6 @@ class Tags(models.Model):
def create(self, vals_list):
for vals in vals_list:
forum = self.env['forum.forum'].browse(vals.get('forum_id'))
- if self.env.user.karma < forum.karma_tag_create:
+ if self.env.user.karma < forum.karma_tag_create and not self.env.is_admin():
raise AccessError(_('%d karma required to create a new Tag.', forum.karma_tag_create))
return super(Tags, self.with_context(mail_create_nolog=True, mail_create_nosubscribe=True)).create(vals_list)
diff --git a/addons/website_forum/models/gamification_karma_tracking.py b/addons/website_forum/models/gamification_karma_tracking.py
new file mode 100644
index 00000000000..86b309e720e
--- /dev/null
+++ b/addons/website_forum/models/gamification_karma_tracking.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import models
+
+
+class KarmaTracking(models.Model):
+ _inherit = 'gamification.karma.tracking'
+
+ def _get_origin_selection_values(self):
+ return super()._get_origin_selection_values() + [('forum.post', self.env['ir.model']._get('forum.post').display_name)]
diff --git a/addons/website_forum/views/gamification_karma_tracking_views.xml b/addons/website_forum/views/gamification_karma_tracking_views.xml
new file mode 100644
index 00000000000..e1b991bc42b
--- /dev/null
+++ b/addons/website_forum/views/gamification_karma_tracking_views.xml
@@ -0,0 +1,14 @@
+
+
+
+ gamification.karma.tracking.view.search.inherit.website.forum
+ gamification.karma.tracking
+
+
+
+
+
+
+
+
diff --git a/addons/website_slides/__manifest__.py b/addons/website_slides/__manifest__.py
index c55167437f0..24ce4d238f3 100644
--- a/addons/website_slides/__manifest__.py
+++ b/addons/website_slides/__manifest__.py
@@ -28,6 +28,7 @@ Featuring
'data': [
'security/website_slides_security.xml',
'security/ir.model.access.csv',
+ 'views/gamification_karma_tracking_views.xml',
'views/res_config_settings_views.xml',
'views/res_partner_views.xml',
'views/rating_rating_views.xml',
@@ -64,6 +65,7 @@ Featuring
'data/slide_channel_demo.xml',
'data/slide_slide_demo.xml',
'data/slide_user_demo.xml',
+ 'data/slide_user_gamification_demo.xml',
],
'installable': True,
'application': True,
diff --git a/addons/website_slides/controllers/mail.py b/addons/website_slides/controllers/mail.py
index 54b6ab7dd17..2f7706300aa 100644
--- a/addons/website_slides/controllers/mail.py
+++ b/addons/website_slides/controllers/mail.py
@@ -5,7 +5,7 @@ import werkzeug
from werkzeug.exceptions import NotFound, Forbidden
-from odoo import http
+from odoo import http, _
from odoo.http import request
from odoo.addons.portal.controllers.mail import _check_special_access, PortalChatter
from odoo.tools import plaintext2html, html2plaintext
@@ -28,7 +28,7 @@ class SlidesPortalChatter(PortalChatter):
slide_channel = request.env[res_model].sudo().browse(int(res_id))
if rating_value and slide_channel and request.env.user.partner_id.id == int(kw.get('pid')):
# apply karma gain rule only once
- request.env.user.add_karma(slide_channel.karma_gen_channel_rank)
+ request.env.user._add_karma(slide_channel.karma_gen_channel_rank, slide_channel, _('Course Ranked'))
result.update({
'default_rating_value': rating_value,
'rating_avg': slide_channel.rating_avg,
diff --git a/addons/website_slides/data/slide_user_gamification_demo.xml b/addons/website_slides/data/slide_user_gamification_demo.xml
new file mode 100644
index 00000000000..34767af7159
--- /dev/null
+++ b/addons/website_slides/data/slide_user_gamification_demo.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+ Quiz completed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Course Completed
+
+
+
+
+
+ Membership Removed
+
+
+
diff --git a/addons/website_slides/models/__init__.py b/addons/website_slides/models/__init__.py
index a0697b21214..f59266be842 100644
--- a/addons/website_slides/models/__init__.py
+++ b/addons/website_slides/models/__init__.py
@@ -1,6 +1,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import gamification_challenge
+from . import gamification_karma_tracking
from . import slide_slide
from . import slide_question
from . import slide_embed
diff --git a/addons/website_slides/models/gamification_karma_tracking.py b/addons/website_slides/models/gamification_karma_tracking.py
new file mode 100644
index 00000000000..e203a675df7
--- /dev/null
+++ b/addons/website_slides/models/gamification_karma_tracking.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo import _, models
+
+
+class KarmaTracking(models.Model):
+ _inherit = 'gamification.karma.tracking'
+
+ def _get_origin_selection_values(self):
+ return (
+ super(KarmaTracking, self)._get_origin_selection_values()
+ + [('slide.slide', _('Course Quiz')), ('slide.channel', self.env['ir.model']._get('slide.channel').display_name)]
+ )
diff --git a/addons/website_slides/models/slide_channel.py b/addons/website_slides/models/slide_channel.py
index e1ae3bcdd6c..434b1b83f85 100644
--- a/addons/website_slides/models/slide_channel.py
+++ b/addons/website_slides/models/slide_channel.py
@@ -4,7 +4,6 @@
import logging
import uuid
from collections import defaultdict
-
from dateutil.relativedelta import relativedelta
import ast
@@ -104,24 +103,21 @@ class ChannelUsersRelation(models.Model):
True if we make the slide as completed
False if we remove user completion
"""
- partner_karma = dict.fromkeys(self.mapped('partner_id').ids, 0)
- for record in self:
- record.completed = completed
- partner_karma[record.partner_id.id] += record.channel_id.karma_gen_channel_finish
+ for channel, memberships in self.grouped("channel_id").items():
+ memberships.completed = completed
+ karma = channel.karma_gen_channel_finish
+ if karma <= 0:
+ continue
- partner_karma = {
- partner_id: karma_to_add
- for partner_id, karma_to_add in partner_karma.items() if karma_to_add > 0
- }
+ karma_per_users = {}
+ for user in memberships.sudo().partner_id.user_ids:
+ karma_per_users[user] = {
+ 'gain': karma if completed else karma * -1,
+ 'source': channel,
+ 'reason': _('Course Finished') if completed else _('Course Set Uncompleted'),
+ }
- if partner_karma:
- users = self.env['res.users'].sudo().search([('partner_id', 'in', list(partner_karma.keys()))])
- for user in users:
- karma = partner_karma[user.partner_id.id]
- if not completed:
- # Mark the channel as not-completed, we remove the gained karma
- karma *= -1
- users.add_karma(karma)
+ self.env['res.users']._add_karma_batch(karma_per_users)
def _send_completed_mail(self):
""" Send an email to the attendee when they have successfully completed a course. """
@@ -745,7 +741,7 @@ class Channel(models.Model):
Warning: this count will not be accurate if the configuration has been
modified after the completion of a course!
"""
- total_karma = defaultdict(int)
+ total_karma = defaultdict(list)
slide_completed = self.env['slide.slide.partner'].sudo().search([
('partner_id', 'in', partner_ids),
@@ -757,21 +753,29 @@ class Channel(models.Model):
slide = partner_slide.slide_id
if not slide.question_ids:
continue
- gains = [slide.quiz_first_attempt_reward,
- slide.quiz_second_attempt_reward,
- slide.quiz_third_attempt_reward,
- slide.quiz_fourth_attempt_reward]
- attempts = min(partner_slide.quiz_attempts_count - 1, 3)
- total_karma[partner_slide.partner_id.id] += gains[attempts]
+ gains = [
+ slide.quiz_first_attempt_reward,
+ slide.quiz_second_attempt_reward,
+ slide.quiz_third_attempt_reward,
+ slide.quiz_fourth_attempt_reward,
+ ]
+ attempts = min(partner_slide.quiz_attempts_count, len(gains))
+ total_karma[partner_slide.partner_id.id].append({
+ 'karma': gains[attempts - 1],
+ 'channel_id': slide.channel_id,
+ })
channel_completed = self.env['slide.channel.partner'].sudo().search([
('partner_id', 'in', partner_ids),
('channel_id', 'in', self.ids),
- ('completed', '=', True)
+ ('completed', '=', True),
])
for partner_channel in channel_completed:
channel = partner_channel.channel_id
- total_karma[partner_channel.partner_id.id] += channel.karma_gen_channel_finish
+ total_karma[partner_channel.partner_id.id].append({
+ 'karma': channel.karma_gen_channel_finish,
+ 'channel_id': channel,
+ })
return total_karma
@@ -782,23 +786,29 @@ class Channel(models.Model):
if not partner_ids:
raise ValueError("Do not use this method with an empty partner_id recordset")
- earned_karma = self._get_earned_karma(partner_ids)
- users = self.env['res.users'].sudo().search([
- ('partner_id', 'in', list(earned_karma)),
- ])
- for user in users:
- if earned_karma[user.partner_id.id]:
- user.add_karma(-1 * earned_karma[user.partner_id.id])
-
removed_channel_partner_domain = []
for channel in self:
+ earned_karma = channel._get_earned_karma(partner_ids)
+ users = self.env['res.users'].sudo().search([('partner_id', 'in', list(earned_karma))])
+
+ karma_values = {}
+ for user in users:
+ karma = sum(values['karma'] for values in earned_karma[user.partner_id.id])
+ if karma:
+ karma_values[user] = {
+ 'gain': karma * -1,
+ 'source': channel,
+ 'reason': _('Membership Removed'),
+ }
+ self.env['res.users']._add_karma_batch(karma_values)
+
removed_channel_partner_domain = expression.OR([
removed_channel_partner_domain,
[('partner_id', 'in', partner_ids),
('channel_id', '=', channel.id)]
])
- self.message_unsubscribe(partner_ids=partner_ids)
+ self.message_unsubscribe(partner_ids=partner_ids)
if removed_channel_partner_domain:
self.env['slide.channel.partner'].sudo().search(removed_channel_partner_domain).unlink()
diff --git a/addons/website_slides/models/slide_slide.py b/addons/website_slides/models/slide_slide.py
index 8d7c1d69186..67b48c9028e 100644
--- a/addons/website_slides/models/slide_slide.py
+++ b/addons/website_slides/models/slide_slide.py
@@ -955,12 +955,16 @@ class Slide(models.Model):
slide.quiz_second_attempt_reward,
slide.quiz_third_attempt_reward,
slide.quiz_fourth_attempt_reward]
- points += gains[user_membership_sudo.quiz_attempts_count - 1] if user_membership_sudo.quiz_attempts_count <= len(gains) else gains[-1]
+ points = gains[min(user_membership_sudo.quiz_attempts_count, len(gains)) - 1]
+ if points:
+ if completed:
+ reason = _('Quiz Completed')
+ else:
+ points *= -1
+ reason = _('Quiz Set Uncompleted')
+ self.env.user.sudo()._add_karma(points, slide, reason)
- if not completed:
- points *= -1
-
- return self.env.user.sudo().add_karma(points)
+ return True
def action_view_embeds(self):
self.ensure_one()
diff --git a/addons/website_slides/tests/test_gamification_karma.py b/addons/website_slides/tests/test_gamification_karma.py
index 6cfc616249b..24c6fc097f1 100644
--- a/addons/website_slides/tests/test_gamification_karma.py
+++ b/addons/website_slides/tests/test_gamification_karma.py
@@ -115,3 +115,47 @@ class TestKarmaGain(common.SlidesCase):
computed_karma += self.channel.karma_gen_channel_finish + self.channel_2.karma_gen_channel_finish
(self.slide | self.slide_2 | self.slide_3 | self.slide_2_0 | self.slide_2_1).with_user(user)._action_mark_completed()
self.assertEqual(user.karma, computed_karma)
+
+ @mute_logger('odoo.models')
+ def test_karma_gain_multiple_course_multiple_users(self):
+ users = self.user_emp | self.user_portal
+ users.write({'karma': 0})
+
+ (self.channel | self.channel_2)._action_add_members(users.partner_id)
+ channel_partners = self.env['slide.channel.partner'].sudo().search([('partner_id', 'in', users.partner_id.ids)])
+ self.assertEqual(len(channel_partners), 4)
+
+ with self.assertQueryCount(53):
+ channel_partners._set_as_completed()
+
+ computed_karma = self.channel.karma_gen_channel_finish + self.channel_2.karma_gen_channel_finish
+
+ for user in users:
+ self.assertEqual(user.karma, computed_karma)
+ user_trackings = user.karma_tracking_ids
+ self.assertEqual(len(user_trackings), 2)
+
+ self.assertEqual(user_trackings[0].new_value, computed_karma)
+ self.assertEqual(user_trackings[0].old_value, self.channel_2.karma_gen_channel_finish)
+ self.assertEqual(user_trackings[0].origin_ref, self.channel_2)
+
+ self.assertEqual(user_trackings[1].new_value, self.channel.karma_gen_channel_finish)
+ self.assertEqual(user_trackings[1].old_value, 0)
+ self.assertEqual(user_trackings[1].origin_ref, self.channel)
+
+ # now, remove the membership in batch, on multiple users
+ with self.assertQueryCount(43):
+ (self.channel | self.channel_2)._remove_membership(users.partner_id.ids)
+
+ for user in users:
+ self.assertEqual(user.karma, 0)
+ user_trackings = user.karma_tracking_ids
+ self.assertEqual(len(user_trackings), 4)
+
+ self.assertEqual(user_trackings[0].new_value, 0)
+ self.assertEqual(user_trackings[0].old_value, self.channel.karma_gen_channel_finish)
+ self.assertEqual(user_trackings[0].origin_ref, self.channel_2)
+
+ self.assertEqual(user_trackings[1].new_value, self.channel.karma_gen_channel_finish)
+ self.assertEqual(user_trackings[1].old_value, computed_karma)
+ self.assertEqual(user_trackings[1].origin_ref, self.channel)
diff --git a/addons/website_slides/views/gamification_karma_tracking_views.xml b/addons/website_slides/views/gamification_karma_tracking_views.xml
new file mode 100644
index 00000000000..3fd692be014
--- /dev/null
+++ b/addons/website_slides/views/gamification_karma_tracking_views.xml
@@ -0,0 +1,16 @@
+
+
+
+ gamification.karma.tracking.view.search.inherit.website.slides
+ gamification.karma.tracking
+
+
+
+
+
+
+
+
+