[IMP] gamification, website_forum, website_slides: karma tracking improvement

Purpose
=======
Some users found a way to farm karma points, so we want to be able to
track the source of the karma gain / loss.

Specifications
==============
Now, we added a reference field `origin_ref` which store the record
responsible for the karma gain / loss (e.g. a slide we just completed).

In addition to this origin, we also have a new field to store the
reason (e.g. "Slide completed") so we know exactly what happened and
how the user gains his karma.

Before, the `old_value` of the karma tracking has to be set manually,
but now it's done automatically based on the value of the previous
tracking of the same user. That way, it will simplify other part of
the code.

Add the karma reason in the website modules. Adapt those modules due
to the changes in gamification.

Task-2234179

closes odoo/odoo#76430

Related: odoo/enterprise#23702
Related: odoo/upgrade#3299
Signed-off-by: Warnon Aurélien (awa) <awa@odoo.com>
Co-authored-by: std-odoo <std@odoo.com>
This commit is contained in:
Thomas Josse
2023-03-06 17:47:09 +01:00
committed by std-odoo
co-authored by std-odoo
parent f76e65118e
commit 0ffa2d64ba
23 changed files with 640 additions and 193 deletions
@@ -1,11 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo><data noupdate="1">
<!--Karma-->
<record id="base.user_root" model="res.users">
<field name="karma">2500</field>
<record id="karma_tracking_user_root" model="gamification.karma.tracking" forcecreate="0">
<field name="user_id" ref="base.user_root"/>
<field name="new_value">2500</field>
<field name="reason">I am the Root!</field>
</record>
<record id="base.user_admin" model="res.users">
<field name="karma">2500</field>
<record id="karma_tracking_user_admin" model="gamification.karma.tracking" forcecreate="0">
<field name="user_id" ref="base.user_admin"/>
<field name="new_value">2500</field>
<field name="reason">I am the Admin!</field>
</record>
<!--Ranks-->
@@ -32,9 +32,6 @@
<field name="new_value">2500</field>
<field name="tracking_date" eval="(DateTime.now()).strftime('%Y-%m-%d')"/>
</record>
<record id="base.user_demo" model="res.users">
<field name="karma">2500</field>
</record>
<function model="gamification.karma.tracking" name="unlink">
<value model="gamification.karma.tracking" eval="obj().search([
('user_id', '=', ref('base.user_demo')),
@@ -74,9 +71,6 @@
<field name="new_value">30</field>
<field name="tracking_date" eval="DateTime.now()"/>
</record>
<record id="base.demo_user0" model="res.users">
<field name="karma">30</field>
</record>
<function model="gamification.karma.tracking" name="unlink">
<value model="gamification.karma.tracking" eval="obj().search([
('user_id', '=', ref('base.demo_user0')),
+2 -2
View File
@@ -12,11 +12,11 @@
<field eval="False" name="doall" />
</record>
<record id="ir_cron_consolidate_last_month" model="ir.cron">
<record id="ir_cron_consolidate" model="ir.cron">
<field name="name">Gamification: Karma tracking consolidation</field>
<field name="model_id" ref="model_gamification_karma_tracking"/>
<field name="state">code</field>
<field name="code">model._consolidate_last_month()</field>
<field name="code">model._consolidate_cron()</field>
<field name="active" eval="True"/>
<field name="interval_number">1</field>
<field name="interval_type">months</field>
@@ -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
+90 -26
View File
@@ -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,
},
}
+128 -32
View File
@@ -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 <gamification.karma.tracking>
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):
@@ -5,12 +5,22 @@
<field name="model">gamification.karma.tracking</field>
<field name="arch" type="xml">
<search string="Search Trackings">
<field name="user_id"/>
<field name="user_id" string="Karma Owner"/>
<field name="tracking_date"/>
<field name="origin_ref_model_name"/>
<filter string="Consolidated" name="filter_consolidated"
domain="[('consolidated', '=', True)]"/>
<filter string="My Karma" name="filter_user_id"
domain="[('user_id', '=', uid)]"/>
<separator/>
<filter string="Manual" name="filter_res_users"
domain="[('origin_ref', 'ilike', 'res.users,')]"/>
<group string="Group By" expand="1">
<filter string="User" name="group_by_user_id"
<filter string="Granted By" name="group_by_create_uid"
context="{'group_by': 'create_uid'}"/>
<filter string="Source Type" name="group_by_origin_ref_model_name"
context="{'group_by': 'origin_ref_model_name'}"/>
<filter string="Karma Owner" name="group_by_user_id"
context="{'group_by': 'user_id'}"/>
</group>
</search>
@@ -21,12 +31,15 @@
<field name="name">gamification.karma.tracking.view.tree</field>
<field name="model">gamification.karma.tracking</field>
<field name="arch" type="xml">
<tree string="Trackings">
<field name="user_id"/>
<field name="tracking_date"/>
<field name="old_value"/>
<field name="new_value"/>
<field name="consolidated"/>
<tree string="Trackings" editable="top" delete="0" sample="1">
<field name="tracking_date" string="Date"/>
<field name="origin_ref" options="{'no_create': True}" required="1"/>
<field name="reason"/>
<field name="user_id" widget="many2one_avatar_user" string="Karma Owner"
attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="old_value" string="Previous Total" optional="hidden"/>
<field name="gain" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="new_value" string="Total" optional="show" readonly="1"/>
</tree>
</field>
</record>
@@ -35,14 +48,18 @@
<field name="name">gamification.karma.tracking.view.form</field>
<field name="model">gamification.karma.tracking</field>
<field name="arch" type="xml">
<form string="Tracking">
<form string="Tracking" delete="0">
<sheet>
<group>
<field name="user_id"/>
<field name="user_id" widget="many2one_avatar_user" string="Karma Owner"
attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="tracking_date"/>
<field name="gain" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="old_value"/>
<field name="new_value"/>
<field name="new_value" readonly="1"/>
<field name="consolidated"/>
<field name="origin_ref" options="{'no_create': True}" required="1"/>
<field name="reason"/>
</group>
</sheet>
</form>
@@ -50,9 +67,17 @@
</record>
<record id="gamification_karma_tracking_action" model="ir.actions.act_window">
<field name="name">Trackings</field>
<field name="name">Karma Tracking</field>
<field name="res_model">gamification.karma.tracking</field>
<field name="view_mode">tree,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
No Karma Tracking
</p>
<p>
Track the sources of the users karma and monitor
</p>
</field>
</record>
</data>
</odoo>
@@ -7,9 +7,12 @@
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<group name="messaging" position="inside">
<field name="karma"/>
</group>
<xpath expr="//div[@name='button_box']" position="inside">
<button type="object" class="oe_stat_button" groups="base.group_no_one"
name="action_karma_report" icon="fa-certificate">
<field string="Karma" name="karma" widget="statinfo"/>
</button>
</xpath>
</field>
</record>
+1
View File
@@ -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',
+4
View File
@@ -45,6 +45,7 @@ In simple words does Odoo became CMS+ERP platform?</p>]]></field>
<!-- Answer -->
<record id="answer_0" model="forum.post">
<field name="create_uid" ref="base.user_admin"/>
<field name="forum_id" ref="website_forum.forum_help"/>
<field name="name">Re: How to configure alerts for employee contract expiration</field>
<field name="content"><![CDATA[<p>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.</p>
@@ -57,6 +58,7 @@ In simple words does Odoo became CMS+ERP platform?</p>]]></field>
<field name="parent_id" ref="question_0" />
</record>
<record id="answer_1" model="forum.post">
<field name="create_uid" ref="base.user_admin"/>
<field name="forum_id" ref="website_forum.forum_help"/>
<field name="name">Re: CMS replacement for ERP and eCommerce</field>
<field name="content"><![CDATA[
@@ -69,11 +71,13 @@ The CMS editor in Odoo web is nice but I prefer Drupal for customization and the
<!-- Post Vote -->
<record id="post_vote_0" model="forum.post.vote">
<field name="post_id" ref="question_0"/>
<field name="create_uid" ref="base.user_admin"/>
<field name="user_id" ref="base.user_demo"/>
<field name="vote">1</field>
</record>
<record id="post_vote_1" model="forum.post.vote">
<field name="post_id" ref="answer_0"/>
<field name="create_uid" ref="base.user_admin"/>
<field name="user_id" ref="base.user_demo"/>
<field name="vote">1</field>
</record>
+1
View File
@@ -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
+50 -19
View File
@@ -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)
@@ -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)]
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gamification_karma_tracking_view_search" model="ir.ui.view">
<field name="name">gamification.karma.tracking.view.search.inherit.website.forum</field>
<field name="model">gamification.karma.tracking</field>
<field name="inherit_id" ref="gamification.gamification_karma_tracking_view_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='filter_res_users']" position='after'>
<filter string="Forum" name="filter_forum_post"
domain="[('origin_ref', 'ilike', 'forum.post,')]"/>
</xpath>
</field>
</record>
</odoo>
+2
View File
@@ -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,
+2 -2
View File
@@ -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,
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Completion of Quiz and Karma Generation -->
<record id="slide_slide_1_4_partner_admin" model="slide.slide.partner">
<field name="slide_id" ref="website_slides.slide_slide_demo_1_4"/>
<field name="partner_id" ref="base.partner_admin"/>
<field name="quiz_attempts_count">1</field>
<field name="completed" eval="True"/>
</record>
<function model="res.users" name="_add_karma">
<value eval="[ref('base.user_admin')]"/>
<value eval="20"/>
<value model="slide.slide" eval="obj().env.ref('website_slides.slide_slide_demo_1_4')"/>
<value>Quiz completed</value>
</function>
<!-- Completion of Course (Karma Generation Triggered in Python) -->
<record id="slide_slide_1_3_partner_admin" model="slide.slide.partner">
<field name="slide_id" ref="website_slides.slide_slide_demo_1_3"/>
<field name="partner_id" ref="base.partner_admin"/>
<field name="completed" eval="True"/>
</record>
<record id="slide_slide_1_5_partner_admin" model="slide.slide.partner">
<field name="slide_id" ref="website_slides.slide_slide_demo_1_5"/>
<field name="partner_id" ref="base.partner_admin"/>
<field name="completed" eval="True"/>
</record>
<!-- Demo join and leave a course -->
<function model="res.users" name="_add_karma">
<value eval="[ref('base.user_demo')]"/>
<value eval="30"/>
<value model="slide.channel" eval="obj().env.ref('website_slides.slide_channel_demo_1_gard1')"/>
<value>Course Completed</value>
</function>
<function model="res.users" name="_add_karma">
<value eval="[ref('base.user_demo')]"/>
<value eval="-30"/>
<value model="slide.channel" eval="obj().env.ref('website_slides.slide_channel_demo_1_gard1')"/>
<value>Membership Removed</value>
</function>
</data>
</odoo>
+1
View File
@@ -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
@@ -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)]
)
+45 -35
View File
@@ -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()
+9 -5
View File
@@ -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()
@@ -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)
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="gamification_karma_tracking_view_search" model="ir.ui.view">
<field name="name">gamification.karma.tracking.view.search.inherit.website.slides</field>
<field name="model">gamification.karma.tracking</field>
<field name="inherit_id" ref="gamification.gamification_karma_tracking_view_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='filter_res_users']" position='after'>
<filter string="Course" name="filter_slide_channel"
domain="[('origin_ref', 'ilike', 'slide.channel,')]"/>
<filter string="Quiz" name="filter_slide_slide"
domain="[('origin_ref', 'ilike', 'slide.slide,')]"/>
</xpath>
</field>
</record>
</odoo>