[IMP] gamification : add ranks based on karma
To encourage forum and slides users to be more active ranks are now added. They are directly linked to karma. The more the user has karma the more his rank will be high. The default rank is Newbie, with 1 point of karma. Users with 0 karma are considered as inactive on forum or slides. When a user reach a new rank a mail is sent to him to congratulate him with his new rank. To add a button in the mail template to allow users to go directly on a website section (like forum or slides) simply override get_gamification_redirection_data to add the target url. Partial commit linked to eLearning project. Main specifications related to gamification and user profile can be found on task 1922159 (PR #30514). Main specifications related to eLearning can be found on task 1902304 (PR #29876).
This commit is contained in:
committed by
Thibault Delavallée
parent
3b9dcb6d8d
commit
9e082662af
@@ -30,6 +30,12 @@ Both goals and badges are flexibles and can be adapted to a large range of modul
|
||||
'security/ir.model.access.csv',
|
||||
'data/goal_base.xml',
|
||||
'data/badge.xml',
|
||||
'data/gamification_karma_rank_data.xml',
|
||||
'views/gamification.xml',
|
||||
'views/gamification_karma_rank_views.xml',
|
||||
'views/mail_templates.xml'
|
||||
],
|
||||
'demo': [
|
||||
'data/gamification_karma_rank_demo.xml'
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<!--Ranks-->
|
||||
<record id="rank_newbie" model="gamification.karma.rank">
|
||||
<field name="name">Newbie</field>
|
||||
<field name="description" type="html"><p>You just began the adventure! Welcome!</p></field>
|
||||
<field name="description_reach_next" type="html"><p>Keep going !</p></field>
|
||||
<field name="karma_required">1</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_newbie.png"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo><data>
|
||||
<!--Ranks-->
|
||||
<record id="rank_student" model="gamification.karma.rank">
|
||||
<field name="name">Student</field>
|
||||
<field name="description" type="html"><p>You begin to understand things ! Keep going!</p></field>
|
||||
<field name="description_reach_next" type="html"><p>Keep going !</p></field>
|
||||
<field name="karma_required">100</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_student.png"/>
|
||||
</record>
|
||||
|
||||
<record id="rank_bachelor" model="gamification.karma.rank">
|
||||
<field name="name">Bachelor</field>
|
||||
<field name="description" type="html"><p>Hey ! You're not so dumb after all !</p></field>
|
||||
<field name="description_reach_next" type="html"><p>Keep going !</p></field>
|
||||
<field name="karma_required">500</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_bachelor.png"/>
|
||||
</record>
|
||||
|
||||
<record id="rank_master" model="gamification.karma.rank">
|
||||
<field name="name">Master</field>
|
||||
<field name="description" type="html"><p>You're good ! Really Good !</p></field>
|
||||
<field name="description_reach_next" type="html"><p>Keep going !</p></field>
|
||||
<field name="karma_required">2000</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_master.png"/>
|
||||
</record>
|
||||
|
||||
<record id="rank_doctor" model="gamification.karma.rank">
|
||||
<field name="name">Doctor</field>
|
||||
<field name="description" type="html"><p>Ok, RESPECT !</p></field>
|
||||
<field name="description_reach_next" type="html"><p>Keep going !</p></field>
|
||||
<field name="karma_required">10000</field>
|
||||
<field name="image" type="base64" file="gamification/static/img/rank_doctor.png"/>
|
||||
</record>
|
||||
|
||||
</data></odoo>
|
||||
@@ -3,7 +3,52 @@
|
||||
|
||||
from werkzeug.exceptions import Forbidden
|
||||
|
||||
from odoo import api, tools, fields, models
|
||||
from odoo.tools.translate import html_translate
|
||||
|
||||
|
||||
class KarmaError(Forbidden):
|
||||
""" Karma-related error, used for forum and posts. """
|
||||
pass
|
||||
|
||||
|
||||
class KarmaRank(models.Model):
|
||||
_name = 'gamification.karma.rank'
|
||||
_description = 'Rank based on karma'
|
||||
_order = 'karma_required'
|
||||
|
||||
name = fields.Text(string='Rank Name', translate=True, required=True)
|
||||
description = fields.Html(string='Description', translate=html_translate, sanitize_attributes=False,)
|
||||
description_reach_next = fields.Html(
|
||||
string='Motivation phrase', translate=html_translate, sanitize_attributes=False,
|
||||
help="Motivation phrase to reach next rank")
|
||||
karma_required = fields.Integer(string='Required Karma', help='Minimum karma needed to reach this rank')
|
||||
user_ids = fields.One2many('res.users', 'rank_id', string='Users', help="Users having this rank")
|
||||
image = fields.Binary('Rank Icon')
|
||||
image_medium = fields.Binary(
|
||||
"Medium-sized rank icon",
|
||||
help="Medium-sized icon of the rank. It is automatically "
|
||||
"resized as a 128x128px image, with aspect ratio preserved. "
|
||||
"Use this field in form views or some kanban views.")
|
||||
image_small = fields.Binary(
|
||||
"Small-sized rank icon",
|
||||
help="Small-sized icon of the rank. It is automatically "
|
||||
"resized as a 64x64px image, with aspect ratio preserved. "
|
||||
"Use this field anywhere a small image is required.")
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
for vals in values_list:
|
||||
tools.image_resize_images(vals)
|
||||
res = super(KarmaRank, self).create(values_list)
|
||||
users = self.env['res.users'].sudo().search([('karma', '>', 0)])
|
||||
users._recompute_rank()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
tools.image_resize_images(vals)
|
||||
res = super(KarmaRank, self).write(vals)
|
||||
users = self.env['res.users'].sudo().search([('karma', '>', 0)])
|
||||
users._recompute_rank()
|
||||
return res
|
||||
|
||||
@@ -12,12 +12,8 @@ class Users(models.Model):
|
||||
gold_badge = fields.Integer('Gold badges count', compute="_get_user_badge_level")
|
||||
silver_badge = fields.Integer('Silver badges count', compute="_get_user_badge_level")
|
||||
bronze_badge = fields.Integer('Bronze badges count', compute="_get_user_badge_level")
|
||||
|
||||
@api.multi
|
||||
def add_karma(self, karma):
|
||||
for user in self:
|
||||
user.karma += karma
|
||||
return True
|
||||
rank_id = fields.Many2one('gamification.karma.rank', 'Rank', index=False)
|
||||
next_rank_id = fields.Many2one('gamification.karma.rank', 'Next Rank', index=False)
|
||||
|
||||
@api.multi
|
||||
@api.depends('badge_ids')
|
||||
@@ -42,3 +38,52 @@ class Users(models.Model):
|
||||
for (user_id, level, count) in self.env.cr.fetchall():
|
||||
# levels are gold, silver, bronze but fields have _badge postfix
|
||||
self.browse(user_id)['{}_badge'.format(level)] = count
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, values_list):
|
||||
res = super(Users, self).create(values_list)
|
||||
res._recompute_rank()
|
||||
return res
|
||||
|
||||
@api.multi
|
||||
def write(self, vals):
|
||||
if 'karma' in vals:
|
||||
self._recompute_rank()
|
||||
return super(Users, self).write(vals)
|
||||
|
||||
@api.multi
|
||||
def add_karma(self, karma):
|
||||
for user in self:
|
||||
user.karma += karma
|
||||
return True
|
||||
|
||||
def _rank_changed(self):
|
||||
if self.rank_id.karma_required > 0:
|
||||
template = self.env.ref('gamification.mail_template_data_new_rank_reached', raise_if_not_found=False)
|
||||
if template:
|
||||
template.send_mail(self.id, force_send=True, notif_layout='mail.mail_notification_light')
|
||||
|
||||
def _recompute_rank(self):
|
||||
"""
|
||||
The caller should filter the users on karma > 0 before calling this method
|
||||
to avoid looping on every single users
|
||||
"""
|
||||
ranks = [{'rank': rank, 'karma_required': rank.karma_required} for rank in
|
||||
self.env['gamification.karma.rank'].search([], order="karma_required DESC")]
|
||||
for user in self:
|
||||
for i in range(0, len(ranks)):
|
||||
if user.karma >= ranks[i]['karma_required']:
|
||||
if user.rank_id != ranks[i]['rank']:
|
||||
user.rank_id = ranks[i]['rank'].id
|
||||
user.next_rank_id = ranks[i - 1]['rank'].id if i > 0 else False
|
||||
user._rank_changed()
|
||||
break
|
||||
|
||||
def get_gamification_redirection_data(self):
|
||||
"""
|
||||
Hook for other modules to add redirect button(s) in new rank reached mail
|
||||
Must return a list of dictionnary including url and label.
|
||||
E.g. return [{'url': '/forum', label: 'Go to Forum'}]
|
||||
"""
|
||||
self.ensure_one()
|
||||
return []
|
||||
|
||||
@@ -25,3 +25,6 @@ badge_user_employee,"Badge-user Employee",model_gamification_badge_user,base.gro
|
||||
badge_user_manager,"Badge-user Manager",model_gamification_badge_user,base.group_erp_manager,1,1,1,1
|
||||
badge_user_portal,"Badge-user Portal",gamification.model_gamification_badge_user,base.group_portal,1,1,1,0
|
||||
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
|
||||
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,71 @@
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- Ranks views -->
|
||||
<record id="gamification_karma_ranks_action" model="ir.actions.act_window">
|
||||
<field name="name">Ranks</field>
|
||||
<field name="res_model">gamification.karma.rank</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a new rank
|
||||
</p><p>
|
||||
A rank correspond to a fixed karma level. The more you have karma, the more your rank is high.
|
||||
This is used to quickly know which user is new or old or highly or not active.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="gamification_karma_ranks_view_search" model="ir.ui.view">
|
||||
<field name="name">gamification.karma.ranks.view.search</field>
|
||||
<field name="model">gamification.karma.rank</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Ranks">
|
||||
<field name="name"/>
|
||||
<field name="karma_required"/>
|
||||
<field name="description"/>
|
||||
<field name="user_ids"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="gamification_karma_ranks_view_tree" model="ir.ui.view">
|
||||
<field name="name">gamification.karma.ranks.view.tree</field>
|
||||
<field name="model">gamification.karma.rank</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Ranks List">
|
||||
<field name="name"/>
|
||||
<field name="karma_required"/>
|
||||
<field name="description"/>
|
||||
<field name="user_ids"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="gamification_karma_rank_view_form" model="ir.ui.view">
|
||||
<field name="name">gamification.karma.rank.view.form</field>
|
||||
<field name="model">gamification.karma.rank</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Rank">
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box"/>
|
||||
<field name="image" widget='image' class="oe_avatar"/>
|
||||
<div class="oe_title">
|
||||
<label for="name" class="oe_edit_only"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Master Chief"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<field name="karma_required"/>
|
||||
<field name="description" placeholder="e.g. A Master Chief knows quite everything on the forum! You cannot beat him!"/>
|
||||
<field name="create_date" invisible="1"/>
|
||||
</group>
|
||||
<group string="Users to that rank" attrs="{'invisible': [('create_date','=',False)]}">
|
||||
<field name="user_ids" readonly="1" />
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<record id="mail_template_data_new_rank_reached" model="mail.template">
|
||||
<field name="name">New rank reached</field>
|
||||
<field name="model_id" ref="base.model_res_users"/>
|
||||
<field name="subject">Reception of ${object.name}</field>
|
||||
<field name="email_to">${(object.partner_id.email_formatted or object.email) |safe}</field>
|
||||
<field name="partner_to">${object.partner_id.id}</field>
|
||||
<field name="subject">New rank: ${object.rank_id.name}</field>
|
||||
<field name="lang">${object.partner_id.lang}</field>
|
||||
<field name="auto_delete" eval="True" />
|
||||
<field name="body_html" type="html">
|
||||
<div style="background:#F0F0F0;color:#515166;padding:10px 0px;font-family:Arial,Helvetica,sans-serif;font-size:14px;">
|
||||
<table style="width:600px;margin:0px auto;background:white;border:1px solid #e1e1e1;">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td style="padding:15px 20px 10px 20px;">
|
||||
<p>
|
||||
Congratulations
|
||||
<span>${object.partner_id.name or ''}</span>!
|
||||
</p>
|
||||
<p>
|
||||
You just reached a new rank : <strong>${object.rank_id.name}</strong>
|
||||
</p>
|
||||
<p>Continue your work to become a <strong>${object.next_rank_id.name}</strong> !</p>
|
||||
<div style="margin: 16px 0px 16px 0px;">
|
||||
% set gamification_redirection_data = object.get_gamification_redirection_data()
|
||||
% for data in gamification_redirection_data
|
||||
% set url = data['url']
|
||||
% set label = data['label']
|
||||
<a href="${url}" style="background-color: #875A7B; padding: 8px 16px 8px 16px; text-decoration: none; color: #fff; border-radius: 5px; font-size:13px;">${label}</a>
|
||||
% endfor
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding:15px 20px 10px 20px;">
|
||||
<p style="text-align: center;">
|
||||
<img src="/web/image/gamification.karma.rank/${object.rank_id.id}/image_medium"/>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
Thank you,<br/>
|
||||
</div>
|
||||
</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
@@ -293,6 +293,6 @@
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_forum_tag_global" parent="menu_website_forum_global" name="Tag" action="forum_tag_action" sequence="30"/>
|
||||
|
||||
<menuitem id="menu_forum_rank_global" parent="menu_website_forum_global" name="Ranks" action="gamification.gamification_karma_ranks_action" sequence="5"/>
|
||||
</data>
|
||||
</odoo>
|
||||
|
||||
Reference in New Issue
Block a user