summaryrefslogtreecommitdiff
path: root/addons/gamification/models/badge.py
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/gamification/models/badge.py
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/gamification/models/badge.py')
-rw-r--r--addons/gamification/models/badge.py267
1 files changed, 267 insertions, 0 deletions
diff --git a/addons/gamification/models/badge.py b/addons/gamification/models/badge.py
new file mode 100644
index 00000000..ed9a4c82
--- /dev/null
+++ b/addons/gamification/models/badge.py
@@ -0,0 +1,267 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+import logging
+from datetime import date
+
+from odoo import api, fields, models, _, exceptions
+
+_logger = logging.getLogger(__name__)
+
+
+class BadgeUser(models.Model):
+ """User having received a badge"""
+
+ _name = 'gamification.badge.user'
+ _description = 'Gamification User Badge'
+ _order = "create_date desc"
+ _rec_name = "badge_name"
+
+ user_id = fields.Many2one('res.users', string="User", required=True, ondelete="cascade", index=True)
+ sender_id = fields.Many2one('res.users', string="Sender", help="The user who has send the badge")
+ badge_id = fields.Many2one('gamification.badge', string='Badge', required=True, ondelete="cascade", index=True)
+ challenge_id = fields.Many2one('gamification.challenge', string='Challenge originating', help="If this badge was rewarded through a challenge")
+ comment = fields.Text('Comment')
+ badge_name = fields.Char(related='badge_id.name', string="Badge Name", readonly=False)
+ level = fields.Selection(
+ string='Badge Level', related="badge_id.level", store=True, readonly=True)
+
+ def _send_badge(self):
+ """Send a notification to a user for receiving a badge
+
+ Does not verify constrains on badge granting.
+ The users are added to the owner_ids (create badge_user if needed)
+ The stats counters are incremented
+ :param ids: list(int) of badge users that will receive the badge
+ """
+ template = self.env.ref('gamification.email_template_badge_received')
+
+ for badge_user in self:
+ self.env['mail.thread'].message_post_with_template(
+ template.id,
+ model=badge_user._name,
+ res_id=badge_user.id,
+ composition_mode='mass_mail',
+ # `website_forum` triggers `_cron_update` which triggers this method for template `Received Badge`
+ # for which `badge_user.user_id.partner_id.ids` equals `[8]`, which is then passed to `self.env['mail.compose.message'].create(...)`
+ # which expects a command list and not a list of ids. In master, this wasn't doing anything, at the end composer.partner_ids was [] and not [8]
+ # I believe this line is useless, it will take the partners to which the template must be send from the template itself (`partner_to`)
+ # The below line was therefore pointless.
+ # partner_ids=badge_user.user_id.partner_id.ids,
+ )
+
+ return True
+
+ @api.model
+ def create(self, vals):
+ self.env['gamification.badge'].browse(vals['badge_id']).check_granting()
+ return super(BadgeUser, self).create(vals)
+
+
+class GamificationBadge(models.Model):
+ """Badge object that users can send and receive"""
+
+ CAN_GRANT = 1
+ NOBODY_CAN_GRANT = 2
+ USER_NOT_VIP = 3
+ BADGE_REQUIRED = 4
+ TOO_MANY = 5
+
+ _name = 'gamification.badge'
+ _description = 'Gamification Badge'
+ _inherit = ['mail.thread', 'image.mixin']
+
+ name = fields.Char('Badge', required=True, translate=True)
+ active = fields.Boolean('Active', default=True)
+ description = fields.Text('Description', translate=True)
+ level = fields.Selection([
+ ('bronze', 'Bronze'), ('silver', 'Silver'), ('gold', 'Gold')],
+ string='Forum Badge Level', default='bronze')
+
+ rule_auth = fields.Selection([
+ ('everyone', 'Everyone'),
+ ('users', 'A selected list of users'),
+ ('having', 'People having some badges'),
+ ('nobody', 'No one, assigned through challenges'),
+ ], default='everyone',
+ string="Allowance to Grant", help="Who can grant this badge", required=True)
+ rule_auth_user_ids = fields.Many2many(
+ 'res.users', 'rel_badge_auth_users',
+ string='Authorized Users',
+ help="Only these people can give this badge")
+ rule_auth_badge_ids = fields.Many2many(
+ 'gamification.badge', 'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
+ string='Required Badges',
+ help="Only the people having these badges can give this badge")
+
+ rule_max = fields.Boolean('Monthly Limited Sending', help="Check to set a monthly limit per person of sending this badge")
+ rule_max_number = fields.Integer('Limitation Number', help="The maximum number of time this badge can be sent per month per person.")
+ challenge_ids = fields.One2many('gamification.challenge', 'reward_id', string="Reward of Challenges")
+
+ goal_definition_ids = fields.Many2many(
+ 'gamification.goal.definition', 'badge_unlocked_definition_rel',
+ string='Rewarded by', help="The users that have succeeded theses goals will receive automatically the badge.")
+
+ owner_ids = fields.One2many(
+ 'gamification.badge.user', 'badge_id',
+ string='Owners', help='The list of instances of this badge granted to users')
+
+ granted_count = fields.Integer("Total", compute='_get_owners_info', help="The number of time this badge has been received.")
+ granted_users_count = fields.Integer("Number of users", compute='_get_owners_info', help="The number of time this badge has been received by unique users.")
+ unique_owner_ids = fields.Many2many(
+ 'res.users', string="Unique Owners", compute='_get_owners_info',
+ help="The list of unique users having received this badge.")
+
+ stat_this_month = fields.Integer(
+ "Monthly total", compute='_get_badge_user_stats',
+ help="The number of time this badge has been received this month.")
+ stat_my = fields.Integer(
+ "My Total", compute='_get_badge_user_stats',
+ help="The number of time the current user has received this badge.")
+ stat_my_this_month = fields.Integer(
+ "My Monthly Total", compute='_get_badge_user_stats',
+ help="The number of time the current user has received this badge this month.")
+ stat_my_monthly_sending = fields.Integer(
+ 'My Monthly Sending Total',
+ compute='_get_badge_user_stats',
+ help="The number of time the current user has sent this badge this month.")
+
+ remaining_sending = fields.Integer(
+ "Remaining Sending Allowed", compute='_remaining_sending_calc',
+ help="If a maximum is set")
+
+ @api.depends('owner_ids')
+ def _get_owners_info(self):
+ """Return:
+ the list of unique res.users ids having received this badge
+ the total number of time this badge was granted
+ the total number of users this badge was granted to
+ """
+ defaults = {
+ 'granted_count': 0,
+ 'granted_users_count': 0,
+ 'unique_owner_ids': [],
+ }
+ if not self.ids:
+ self.update(defaults)
+ return
+
+ Users = self.env["res.users"]
+ query = Users._where_calc([])
+ Users._apply_ir_rules(query)
+ badge_alias = query.join("res_users", "id", "gamification_badge_user", "user_id", "badges")
+
+ tables, where_clauses, where_params = query.get_sql()
+
+ self.env.cr.execute(
+ f"""
+ SELECT {badge_alias}.badge_id, count(res_users.id) as stat_count,
+ count(distinct(res_users.id)) as stat_count_distinct,
+ array_agg(distinct(res_users.id)) as unique_owner_ids
+ FROM {tables}
+ WHERE {where_clauses}
+ AND {badge_alias}.badge_id IN %s
+ GROUP BY {badge_alias}.badge_id
+ """,
+ [*where_params, tuple(self.ids)]
+ )
+
+ mapping = {
+ badge_id: {
+ 'granted_count': count,
+ 'granted_users_count': distinct_count,
+ 'unique_owner_ids': owner_ids,
+ }
+ for (badge_id, count, distinct_count, owner_ids) in self.env.cr._obj
+ }
+ for badge in self:
+ badge.update(mapping.get(badge.id, defaults))
+
+ @api.depends('owner_ids.badge_id', 'owner_ids.create_date', 'owner_ids.user_id')
+ def _get_badge_user_stats(self):
+ """Return stats related to badge users"""
+ first_month_day = date.today().replace(day=1)
+
+ for badge in self:
+ owners = badge.owner_ids
+ badge.stat_my = sum(o.user_id == self.env.user for o in owners)
+ badge.stat_this_month = sum(o.create_date.date() >= first_month_day for o in owners)
+ badge.stat_my_this_month = sum(
+ o.user_id == self.env.user and o.create_date.date() >= first_month_day
+ for o in owners
+ )
+ badge.stat_my_monthly_sending = sum(
+ o.create_uid == self.env.user and o.create_date.date() >= first_month_day
+ for o in owners
+ )
+
+ @api.depends(
+ 'rule_auth',
+ 'rule_auth_user_ids',
+ 'rule_auth_badge_ids',
+ 'rule_max',
+ 'rule_max_number',
+ 'stat_my_monthly_sending',
+ )
+ def _remaining_sending_calc(self):
+ """Computes the number of badges remaining the user can send
+
+ 0 if not allowed or no remaining
+ integer if limited sending
+ -1 if infinite (should not be displayed)
+ """
+ for badge in self:
+ if badge._can_grant_badge() != self.CAN_GRANT:
+ # if the user cannot grant this badge at all, result is 0
+ badge.remaining_sending = 0
+ elif not badge.rule_max:
+ # if there is no limitation, -1 is returned which means 'infinite'
+ badge.remaining_sending = -1
+ else:
+ badge.remaining_sending = badge.rule_max_number - badge.stat_my_monthly_sending
+
+ def check_granting(self):
+ """Check the user 'uid' can grant the badge 'badge_id' and raise the appropriate exception
+ if not
+
+ Do not check for SUPERUSER_ID
+ """
+ status_code = self._can_grant_badge()
+ if status_code == self.CAN_GRANT:
+ return True
+ elif status_code == self.NOBODY_CAN_GRANT:
+ raise exceptions.UserError(_('This badge can not be sent by users.'))
+ elif status_code == self.USER_NOT_VIP:
+ raise exceptions.UserError(_('You are not in the user allowed list.'))
+ elif status_code == self.BADGE_REQUIRED:
+ raise exceptions.UserError(_('You do not have the required badges.'))
+ elif status_code == self.TOO_MANY:
+ raise exceptions.UserError(_('You have already sent this badge too many time this month.'))
+ else:
+ _logger.error("Unknown badge status code: %s" % status_code)
+ return False
+
+ def _can_grant_badge(self):
+ """Check if a user can grant a badge to another user
+
+ :param uid: the id of the res.users trying to send the badge
+ :param badge_id: the granted badge id
+ :return: integer representing the permission.
+ """
+ if self.env.is_admin():
+ return self.CAN_GRANT
+
+ if self.rule_auth == 'nobody':
+ return self.NOBODY_CAN_GRANT
+ elif self.rule_auth == 'users' and self.env.user not in self.rule_auth_user_ids:
+ return self.USER_NOT_VIP
+ elif self.rule_auth == 'having':
+ all_user_badges = self.env['gamification.badge.user'].search([('user_id', '=', self.env.uid)]).mapped('badge_id')
+ if self.rule_auth_badge_ids - all_user_badges:
+ return self.BADGE_REQUIRED
+
+ if self.rule_max and self.stat_my_monthly_sending >= self.rule_max_number:
+ return self.TOO_MANY
+
+ # badge.rule_auth == 'everyone' -> no check
+ return self.CAN_GRANT