From bfbc7c6a184b2b507b1bf4ad778430bc2f3a0e7f Mon Sep 17 00:00:00 2001 From: Patrick Hoste Date: Thu, 27 Jun 2019 07:54:04 +0000 Subject: [PATCH 1/4] [IMP] gamification: track karma change on users PURPOSE Allow karma gain tracking enabling notably display of top users based on weekly / monthly gain in website profile. SPECIFCIATIONS Each time a user gains karma a record is created in the gamification karma tracking model. Scheduled activity runs to consolidate the records into monthly gain records to avoid having crowdy table and unnecessary noise in karma gain. This model is made private and only accessible through some dedicated compute methods / controllers used in website profile. In website profile module buttons are added to see users ranking based on their total karma (like before) but also by last week and last month gains (using the newly introduced tracking model). LINKS Task ID 2003505 PR #34594 --- addons/gamification/__manifest__.py | 4 +- addons/gamification/data/cron.xml | 13 ++ .../data/gamification_karma_rank_demo.xml | 9 - .../data/gamification_karma_tracking_demo.xml | 83 ++++++++ addons/gamification/models/__init__.py | 1 + .../models/gamification_karma_tracking.py | 69 +++++++ addons/gamification/models/res_users.py | 88 +++++++- .../gamification/security/ir.model.access.csv | 4 +- addons/gamification/tests/__init__.py | 1 + .../gamification/tests/test_karma_tracking.py | 195 ++++++++++++++++++ .../gamification_karma_tracking_views.xml | 63 ++++++ addons/website_profile/controllers/main.py | 46 ++++- .../website_profile/views/website_profile.xml | 24 ++- 13 files changed, 572 insertions(+), 28 deletions(-) create mode 100644 addons/gamification/data/gamification_karma_tracking_demo.xml create mode 100644 addons/gamification/models/gamification_karma_tracking.py create mode 100644 addons/gamification/tests/test_karma_tracking.py create mode 100644 addons/gamification/views/gamification_karma_tracking_views.xml diff --git a/addons/gamification/__manifest__.py b/addons/gamification/__manifest__.py index 0270a25a56b..314dcf070a2 100644 --- a/addons/gamification/__manifest__.py +++ b/addons/gamification/__manifest__.py @@ -33,9 +33,11 @@ Both goals and badges are flexibles and can be adapted to a large range of modul 'data/gamification_karma_rank_data.xml', 'views/gamification.xml', 'views/gamification_karma_rank_views.xml', + 'views/gamification_karma_tracking_views.xml', 'views/mail_templates.xml' ], 'demo': [ - 'data/gamification_karma_rank_demo.xml' + 'data/gamification_karma_rank_demo.xml', + 'data/gamification_karma_tracking_demo.xml', ], } diff --git a/addons/gamification/data/cron.xml b/addons/gamification/data/cron.xml index 0ed59d39e11..4e59205647b 100644 --- a/addons/gamification/data/cron.xml +++ b/addons/gamification/data/cron.xml @@ -1,3 +1,4 @@ + @@ -10,5 +11,17 @@ -1 + + + Gamification: Karma tracking consolidation + + code + model._consolidate_last_month() + + 1 + months + -1 + + diff --git a/addons/gamification/data/gamification_karma_rank_demo.xml b/addons/gamification/data/gamification_karma_rank_demo.xml index 839e8750047..848e7fd68d0 100644 --- a/addons/gamification/data/gamification_karma_rank_demo.xml +++ b/addons/gamification/data/gamification_karma_rank_demo.xml @@ -1,13 +1,5 @@ - - - 20 - - - 10 - - @@ -44,5 +36,4 @@ - diff --git a/addons/gamification/data/gamification_karma_tracking_demo.xml b/addons/gamification/data/gamification_karma_tracking_demo.xml new file mode 100644 index 00000000000..01f4d8ac929 --- /dev/null +++ b/addons/gamification/data/gamification_karma_tracking_demo.xml @@ -0,0 +1,83 @@ + + + + + + + 0 + 1000 + + + + + 1000 + 1500 + + + + + 1500 + 2000 + + + + + 2000 + 2050 + + + + 2500 + + + + + + 0 + 5 + + + + + 5 + 10 + + + + + 10 + 20 + + + + + 20 + 25 + + + + 30 + + + + + + 0 + 2000 + + + + + 2000 + 2250 + + + + + 2250 + 2500 + + + + + diff --git a/addons/gamification/models/__init__.py b/addons/gamification/models/__init__.py index 926a5af2a07..659fcff70a1 100644 --- a/addons/gamification/models/__init__.py +++ b/addons/gamification/models/__init__.py @@ -5,4 +5,5 @@ from . import goal from . import challenge from . import badge from . import gamification_karma_rank +from . import gamification_karma_tracking from . import res_users diff --git a/addons/gamification/models/gamification_karma_tracking.py b/addons/gamification/models/gamification_karma_tracking.py new file mode 100644 index 00000000000..f7b184ad24d --- /dev/null +++ b/addons/gamification/models/gamification_karma_tracking.py @@ -0,0 +1,69 @@ +# -*- 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 + + +class KarmaTracking(models.Model): + _name = 'gamification.karma.tracking' + _description = 'Track Karma Changes' + _rec_name = 'user_id' + _order = 'tracking_date 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) + consolidated = fields.Boolean('Consolidated') + tracking_date = fields.Date(default=fields.Date.context_today) + + @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 _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 """ + self.env.cr.execute(select_query, { + 'from_date': from_date, + 'to_date': end_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() + return True diff --git a/addons/gamification/models/res_users.py b/addons/gamification/models/res_users.py index 3e8b6d8e1cd..3dc95de74a1 100644 --- a/addons/gamification/models/res_users.py +++ b/addons/gamification/models/res_users.py @@ -3,16 +3,12 @@ from odoo import api, fields, models -import logging - -_logger = logging.getLogger(__name__) - class Users(models.Model): _inherit = 'res.users' - karma = fields.Integer('Karma', default=0) + 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") silver_badge = fields.Integer('Silver badges count', compute="_get_user_badge_level") @@ -46,11 +42,28 @@ class Users(models.Model): @api.model_create_multi 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) + 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']}) + result = super(Users, self).write(vals) + + if karma_trackings: + self.env['gamification.karma.tracking'].sudo().create(karma_trackings) if 'karma' in vals: self._recompute_rank() return result @@ -60,6 +73,68 @@ class Users(models.Model): user.karma += karma return True + def _get_tracking_karma_gain_position(self, user_domain, from_date=None, to_date=None): + """ Get absolute position in term of gained karma for users. First a ranking + of all users is done given a user_domain; then the position of each user + belonging to the current record set is extracted. + + Example: in website profile, search users with name containing Norbert. Their + positions should not be 1 to 4 (assuming 4 results), but their actual position + in the karma gain ranking (with example user_domain being karma > 1, + website published True). + + :param user_domain: general domain (i.e. active, karma > 1, website, ...) + to compute the absolute position of the current record set + :param from_date: compute karma gained after this date (included) or from + beginning of time; + :param to_date: compute karma gained before this date (included) or until + end of time; + + :return list: [{ + 'user_id': user_id (belonging to current record set), + 'karma_gain_total': integer, karma gained in the given timeframe, + 'karma_position': integer, ranking position + }, {..}] ordered by karma_position desc + """ + if not self: + return [] + + where_query = self.env['res.users']._where_calc(user_domain) + user_from_clause, user_where_clause, where_clause_params = where_query.get_sql() + + params = [] + if from_date: + date_from_condition = 'AND tracking.tracking_date::timestamp >= timestamp %s' + params.append(from_date) + if to_date: + date_to_condition = 'AND tracking.tracking_date::timestamp <= timestamp %s' + params.append(to_date) + params.append(tuple(self.ids)) + + query = """ +SELECT final.user_id, final.karma_gain_total, final.karma_position +FROM ( + SELECT intermediate.user_id, intermediate.karma_gain_total, row_number() OVER (ORDER BY intermediate.karma_gain_total DESC) AS karma_position + FROM ( + SELECT "res_users".id as user_id, COALESCE(SUM("tracking".new_value - "tracking".old_value), 0) as karma_gain_total + FROM %(user_from_clause)s + LEFT JOIN "gamification_karma_tracking" as "tracking" + ON "res_users".id = "tracking".user_id AND "res_users"."active" = TRUE + WHERE %(user_where_clause)s %(date_from_condition)s %(date_to_condition)s + GROUP BY "res_users".id + ORDER BY karma_gain_total DESC + ) intermediate +) final +WHERE final.user_id IN %%s""" % { + 'user_from_clause': user_from_clause, + 'user_where_clause': user_where_clause or (not from_date and not to_date and 'TRUE') or '', + 'date_from_condition': date_from_condition if from_date else '', + 'date_to_condition': date_to_condition if to_date else '' + } + + self.env.cr.execute(query, tuple(where_clause_params + params)) + return self.env.cr.dictfetchall() + def _rank_changed(self): """ Method that can be called on a batch of users with the same new rank @@ -68,7 +143,7 @@ class Users(models.Model): if template: for u in self: if u.rank_id.karma_min > 0: - template.send_mail(u.id, force_send=len(self) == 1, notif_layout='mail.mail_notification_light') + template.send_mail(u.id, force_send=False, notif_layout='mail.mail_notification_light') def _recompute_rank(self): """ @@ -161,7 +236,6 @@ class Users(models.Model): 'next_rank_id': lower_rank.id, }) - def _get_next_rank(self): """ For fresh users with 0 karma that don't have a rank_id and next_rank_id yet this method returns the first karma rank (by karma ascending). This acts as a diff --git a/addons/gamification/security/ir.model.access.csv b/addons/gamification/security/ir.model.access.csv index 41f36a87a5f..728360ab386 100644 --- a/addons/gamification/security/ir.model.access.csv +++ b/addons/gamification/security/ir.model.access.csv @@ -27,4 +27,6 @@ badge_user_portal,"Badge-user Portal",gamification.model_gamification_badge_user badge_user_public,"Badge-user Public",gamification.model_gamification_badge_user,base.group_public,1,0,0,0 gamification_karma_rank_access_all,gamification.karma.rank.access.all,gamification.model_gamification_karma_rank,,1,0,0,0 -gamification_karma_rank_access_user_manager,gamification.karma.rank.access.user.manager,gamification.model_gamification_karma_rank,base.group_erp_manager,1,1,1,1 +gamification_karma_rank_access_user_manager,gamification.karma.rank.access.user.manager,gamification.model_gamification_karma_rank,base.group_system,1,1,1,1 +gamification_karma_tracking_access_all,gamification.karma.tracking.access.all,gamification.model_gamification_karma_tracking,,0,0,0,0 +gamification_karma_tracking_access_user_manager,gamification.karma.tracking.access.user.manager,gamification.model_gamification_karma_tracking,base.group_system,1,1,1,1 diff --git a/addons/gamification/tests/__init__.py b/addons/gamification/tests/__init__.py index a6a599bf6c8..963887eb670 100644 --- a/addons/gamification/tests/__init__.py +++ b/addons/gamification/tests/__init__.py @@ -3,3 +3,4 @@ from . import test_challenge from . import test_recompute_rank +from . import test_karma_tracking diff --git a/addons/gamification/tests/test_karma_tracking.py b/addons/gamification/tests/test_karma_tracking.py new file mode 100644 index 00000000000..145b745b436 --- /dev/null +++ b/addons/gamification/tests/test_karma_tracking.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from datetime import date +from dateutil.relativedelta import relativedelta +from unittest.mock import patch + +from odoo import exceptions, fields +from odoo.tests import common + + +class TestKarmaTrackingCommon(common.SavepointCase): + + @classmethod + def setUpClass(cls): + super(TestKarmaTrackingCommon, cls).setUpClass() + cls.test_user = cls.env['res.users'].with_context(no_reset_password=True, mail_create_nosubscribe=True).create({ + 'name': 'Test User', + 'login': 'test', + 'email': 'test@example.com', + 'karma': 0, + 'groups_id': [(4, cls.env.ref('base.group_user').id)] + }) + cls.test_user_2 = cls.env['res.users'].with_context(no_reset_password=True, mail_create_nosubscribe=True).create({ + 'name': 'Test User 2', + 'login': 'test2', + 'email': 'test2@example.com', + 'karma': 0, + 'groups_id': [(4, cls.env.ref('base.group_user').id)] + }) + cls.env['gamification.karma.tracking'].search([]).unlink() + + cls.test_date = fields.Date.today() + relativedelta(month=4, day=1) + + @classmethod + def _create_trackings(cls, user, karma, steps, date, days_delta=1): + old_value = user.karma + for step in range(steps): + new_value = old_value + karma + cls.env['gamification.karma.tracking'].create([{ + 'user_id': user.id, + 'old_value': old_value, + 'new_value': new_value, + 'consolidated': False, + 'tracking_date': fields.Date.to_string(date) + }]) + old_value = new_value + date = date + relativedelta(days=days_delta) + + def test_computation_gain(self): + 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) + + results = (self.test_user | self.test_user_2)._get_tracking_karma_gain_position([]) + self.assertEqual(results[0]['user_id'], self.test_user_2.id) + self.assertEqual(results[0]['karma_gain_total'], 200) + self.assertEqual(results[0]['karma_position'], 1) + self.assertEqual(results[1]['user_id'], self.test_user.id) + self.assertEqual(results[1]['karma_gain_total'], 40) + self.assertEqual(results[1]['karma_position'], 2) + + results = (self.test_user | self.test_user_2)._get_tracking_karma_gain_position([], to_date=self.test_date + relativedelta(day=2)) + self.assertEqual(results[0]['user_id'], self.test_user.id) + self.assertEqual(results[0]['karma_gain_total'], 20) + self.assertEqual(results[0]['karma_position'], 1) + self.assertEqual(results[1]['user_id'], self.test_user_2.id) + self.assertEqual(results[1]['karma_gain_total'], 10) + self.assertEqual(results[1]['karma_position'], 2) + + results = (self.test_user | self.test_user_2)._get_tracking_karma_gain_position([], from_date=self.test_date + relativedelta(months=1, day=1)) + self.assertEqual(results[0]['user_id'], self.test_user_2.id) + self.assertEqual(results[0]['karma_gain_total'], 50) + self.assertEqual(results[0]['karma_position'], 1) + self.assertEqual(results[1]['user_id'], self.test_user.id) + self.assertEqual(results[1]['karma_gain_total'], 20) + self.assertEqual(results[1]['karma_position'], 2) + + results = self.env['res.users']._get_tracking_karma_gain_position([]) + self.assertEqual(len(results), 0) + + def test_consolidation_cron(self): + self.patcher = patch('odoo.addons.gamification.models.gamification_karma_tracking.fields.Date', wraps=fields.Date) + self.mock_datetime = self.patcher.start() + self.mock_datetime.today.return_value = date(self.test_date.year, self.test_date.month + 1, self.test_date.day) + + 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) + ]) + self.assertEqual(len(consolidated), 2) + unconsolidated = self.env['gamification.karma.tracking'].search([ + ('user_id', 'in', (self.test_user | self.test_user_2).ids), + ('consolidated', '=', False), + ]) + self.assertEqual(len(unconsolidated), 6) # 5 for test user 2, 1 for test user + + self.patcher.stop() + + def test_consolidation_monthly(self): + Tracking = self.env['gamification.karma.tracking'] + base_test_user_karma = self.test_user.karma + base_test_user_2_karma = self.test_user_2.karma + 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) + + Tracking._process_consolidate(self.test_date) + consolidated = Tracking.search([ + ('user_id', '=', self.test_user_2.id), + ('consolidated', '=', True), + ('tracking_date', '=', self.test_date) + ]) + 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.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([ + ('user_id', '=', self.test_user_2.id), + ('consolidated', '=', False) + ]) + self.assertEqual(len(remaining), 5) # 15 steps consolidated, remaining 5 + self.assertEqual(remaining[0].tracking_date, self.test_date + relativedelta(months=1, day=9)) # ordering: last first + self.assertEqual(remaining[-1].tracking_date, self.test_date + relativedelta(months=1, day=1)) + + Tracking._process_consolidate(self.test_date + relativedelta(months=1)) + consolidated = Tracking.search([ + ('user_id', '=', self.test_user_2.id), + ('consolidated', '=', True), + ]) + 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[-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 + + remaining = Tracking.search([ + ('user_id', '=', self.test_user_2.id), + ('consolidated', '=', False) + ]) + self.assertFalse(remaining) + + # current user not-in-details tests + current_user_trackings = Tracking.search([ + ('user_id', '=', self.test_user.id), + ]) + self.assertEqual(len(current_user_trackings), 2) + self.assertEqual(current_user_trackings[0].new_value, base_test_user_karma + 40) + self.assertEqual(current_user_trackings[-1].old_value, base_test_user_karma) + + def test_user_as_erp_manager(self): + self.test_user.write({'groups_id': [ + (4, self.env.ref('base.group_partner_manager').id), + (4, self.env.ref('base.group_erp_manager').id) + ]}) + user = self.env['res.users'].with_user(self.test_user).create({ + 'name': 'Test Ostérone', 'karma': '32', + 'login': 'dummy', 'email': 'dummy@example.com', + }) + with self.assertRaises(exceptions.AccessError): + user.read(['karma_tracking_ids']) + + user.write({'karma': 60}) + user.add_karma(10) + 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 + + def test_user_tracking(self): + self.test_user.write({'groups_id': [ + (4, self.env.ref('base.group_partner_manager').id), + (4, self.env.ref('base.group_system').id) + ]}) + user = self.env['res.users'].with_user(self.test_user).create({ + 'name': 'Test Ostérone', 'karma': '32', + 'login': 'dummy', 'email': 'dummy@example.com', + }) + self.assertEqual(user.karma, 32) + self.assertEqual(len(user.karma_tracking_ids), 1) + 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) + 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(user.karma_tracking_ids[1].old_value, 32) + self.assertEqual(user.karma_tracking_ids[1].new_value, 60) + self.assertEqual(user.karma_tracking_ids[0].old_value, 0) + self.assertEqual(user.karma_tracking_ids[0].new_value, 32) diff --git a/addons/gamification/views/gamification_karma_tracking_views.xml b/addons/gamification/views/gamification_karma_tracking_views.xml new file mode 100644 index 00000000000..998d0ff91ec --- /dev/null +++ b/addons/gamification/views/gamification_karma_tracking_views.xml @@ -0,0 +1,63 @@ + + + + gamification.karma.tracking.view.search + gamification.karma.tracking + + + + + + + + + + + + + + gamification.karma.tracking.view.tree + gamification.karma.tracking + + + + + + + + + + + + + gamification.karma.tracking.view.form + gamification.karma.tracking + +
+ + + + + + + + + +
+
+
+ + + Trackings + gamification.karma.tracking + tree,form + + + +
+
diff --git a/addons/website_profile/controllers/main.py b/addons/website_profile/controllers/main.py index eef5d1d4dc8..703fc250f96 100644 --- a/addons/website_profile/controllers/main.py +++ b/addons/website_profile/controllers/main.py @@ -8,7 +8,10 @@ import werkzeug.urls import werkzeug.wrappers import math -from odoo import http, modules, tools +from dateutil.relativedelta import relativedelta +from operator import itemgetter + +from odoo import fields, http, modules, tools from odoo.http import request from odoo.osv import expression @@ -205,12 +208,13 @@ class WebsiteProfile(http.Controller): @http.route(['/profile/users', '/profile/users/page/'], type='http', auth="public", website=True, sitemap=True) - def view_all_users_page(self, page=1, **searches): + def view_all_users_page(self, page=1, **kwargs): User = request.env['res.users'] dom = [('karma', '>', 1), ('website_published', '=', True)] # Searches - search_term = searches.get('search') + search_term = kwargs.get('search') + group_by = kwargs.get('group_by', False) if search_term: dom = expression.AND([['|', ('name', 'ilike', search_term), ('company_id.name', 'ilike', search_term)], dom]) @@ -225,12 +229,24 @@ class WebsiteProfile(http.Controller): user_values = self._prepare_all_users_values(users) # Get karma position for users (only website_published) - position_domain = [('karma', '>', 1), ('website_published', '=', True)] - position_map = self._get_users_karma_position(position_domain, users.ids) - for user in user_values: - user['position'] = position_map.get(user['id'], 0) + if group_by: + position_domain = [('karma', '>', 1), ('website_published', '=', True)] + position_map = self._get_user_tracking_karma_gain_position(position_domain, users.ids, group_by) + max_position = max([user_data['karma_position'] for user_data in position_map.values()], default=1) + for user in user_values: + user_data = position_map.get(user['id'], dict()) + user['position'] = user_data.get('karma_position', max_position + 1) + user['karma_gain'] = user_data.get('karma_gain_total', 0) + user_values.sort(key=itemgetter('position')) + else: + position_domain = [('karma', '>', 1), ('website_published', '=', True)] + position_map = self._get_users_karma_position(position_domain, users.ids) + for user in user_values: + user['position'] = position_map.get(user['id'], 0) + user['karma_gain'] = False values = { + 'group_by': group_by or 'all', 'top3_users': user_values[:3] if not search_term and page == 1 else None, 'users': user_values[3:] if not search_term and page == 1 else user_values, 'pager': pager @@ -244,8 +260,7 @@ class WebsiteProfile(http.Controller): if not user_ids: return {} - Users = request.env['res.users'] - where_query = Users._where_calc(domain) + where_query = request.env['res.users']._where_calc(domain) from_clause, where_clause, where_clause_params = where_query.get_sql() # we search on every user in the DB to get the real positioning (not the one inside the subset) @@ -264,6 +279,19 @@ class WebsiteProfile(http.Controller): return {item['id']: item['karma_position'] for item in request.env.cr.dictfetchall()} + def _get_user_tracking_karma_gain_position(self, domain, user_ids, group_by): + """ Helper method computing boundaries to give to _get_tracking_karma_gain_position. + See that method for more details. """ + to_date = fields.Date.today() + if group_by == 'week': + from_date = to_date - relativedelta(weeks=1) + elif group_by == 'month': + from_date = to_date - relativedelta(months=1) + else: + from_date = None + results = request.env['res.users'].browse(user_ids)._get_tracking_karma_gain_position(domain, from_date=from_date, to_date=to_date) + return dict((item['user_id'], dict(item)) for item in results) + # User and validation # -------------------------------------------------- diff --git a/addons/website_profile/views/website_profile.xml b/addons/website_profile/views/website_profile.xml index 629119b98d0..9a8dacb8e2a 100644 --- a/addons/website_profile/views/website_profile.xml +++ b/addons/website_profile/views/website_profile.xml @@ -481,7 +481,15 @@
-

All Users

+

All Users

+
+ Rank by : +
+ + + +
+
@@ -519,6 +527,9 @@

Unpublished +
+ + XP +
@@ -506,7 +509,7 @@ - +
No user found for . Try another search.
From 60bd39ae38c5e320b3e2ab4c267db594345e6f1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 5 Nov 2019 11:45:56 +0000 Subject: [PATCH 3/4] [FIX] website_profile: ensure top3 users cards have same height Task ID 2003505 PR #34594 --- addons/website_profile/views/website_profile.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/website_profile/views/website_profile.xml b/addons/website_profile/views/website_profile.xml index ba7e13efb8f..881bcb7f0da 100644 --- a/addons/website_profile/views/website_profile.xml +++ b/addons/website_profile/views/website_profile.xml @@ -500,7 +500,7 @@