[IMP] digest: improve digest by adding daily sending and preferences
PURPOSE
Make digest email and tips more appealing. The goals of these tips are
* to encourage the adoption of other apps (Did you know ?);
* to make Odoo look more fun (Fun tips and tricks, young and dynamic style);
* to show social proof and increase trust (emphasis on already existing
projects / customers to);
SPECIFICATIONS
Add a daily digest option, allowing to send digests on a daily basis. It
will be the default settings to help users coming back to odoo.
Add a slowdown heuristics. If not any user targeted by a daily digest logs
himself within 3 days, digest is slowed down to a weekly setting to avoid
spam.
Add a preference section in template, to hold notably links to some
configuration steps sent mainly for admin / CEOs.
Improve access on models.
LINKS
Task ID 2197417
PR odoo/odoo#51619
This commit is contained in:
@@ -1,15 +1,36 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from werkzeug.exceptions import Forbidden
|
||||
from werkzeug.urls import url_encode
|
||||
|
||||
from odoo import _
|
||||
from odoo.http import Controller, request, route
|
||||
|
||||
|
||||
class DigestController(Controller):
|
||||
|
||||
@route('/digest/<int:digest_id>/unsubscribe', type='http', website=True, auth='user')
|
||||
def digest_unsubscribe(self, digest_id, **post):
|
||||
digest = request.env['digest.digest'].sudo().browse(digest_id)
|
||||
def digest_unsubscribe(self, digest_id):
|
||||
digest = request.env['digest.digest'].browse(digest_id).exists()
|
||||
digest.action_unsubcribe()
|
||||
return request.render('digest.portal_digest_unsubscribed', {
|
||||
'digest': digest,
|
||||
})
|
||||
|
||||
@route('/digest/<int:digest_id>/set_periodicity', type='http', website=True, auth='user')
|
||||
def digest_set_periodicity(self, digest_id, periodicity='weekly'):
|
||||
if not request.env.user.has_group('base.group_erp_manager'):
|
||||
raise Forbidden()
|
||||
if periodicity not in ('daily', 'weekly', 'monthly', 'quarterly'):
|
||||
raise ValueError(_('Invalid periodicity set on digest'))
|
||||
|
||||
digest = request.env['digest.digest'].browse(digest_id).exists()
|
||||
digest.action_set_periodicity(periodicity)
|
||||
|
||||
url_params = {
|
||||
'model': digest._name,
|
||||
'id': digest.id,
|
||||
'active_id': digest.id,
|
||||
}
|
||||
return request.redirect('/web?#%s' % url_encode(url_params))
|
||||
|
||||
@@ -53,4 +53,16 @@
|
||||
</body>
|
||||
</html>
|
||||
</template>
|
||||
|
||||
<!-- DIGEST PART: PREFERENCES -->
|
||||
<template id="digest_section_preferences">
|
||||
<p t-if="object._context.get('digest_slowdown')" class="font-12" style="font-size: 15px; color: #6b6d70">
|
||||
We've noticed you did not connect these last few days so we've automatically switched your preference to weekly Digests.
|
||||
</p>
|
||||
<p t-elif="user.has_group('base.group_erp_manager') and object.periodicity == 'daily'" class="font-12" style="font-size: 15px; color: #6b6d70">
|
||||
Prefer a broader overview ?
|
||||
<a t-att-href="'/digest/%s/set_periodicity?periodicity=weekly' % object.id" target="_blank" style="color:#875A7B; font-weight: bold;">Switch to weekly Digests.</a>
|
||||
</p>
|
||||
<p t-else=""/>
|
||||
</template>
|
||||
</data></odoo>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
% set data = object.compute_kpis(company, user)
|
||||
% set tips = object.compute_tips(company, user, tips_count=ctx.get('tips_count', 1))
|
||||
% set kpi_actions = object.compute_kpis_actions(company, user)
|
||||
% set preferences = object.compute_preferences(company, user)
|
||||
% set kpis = data.yesterday.keys()
|
||||
<table cellspacing="0" cellpadding="0" align="center" border="0" bgcolor="#eeeeee" style="width:100%; font-family: Arial,Helvetica,Verdana,sans-serif;">
|
||||
<tr class="mobile-hide" bgcolor="#875a7b"><td height="70"> </td></tr>
|
||||
@@ -46,6 +47,7 @@
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border: 1px solid #eeeeee; border-bottom: none;width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr>
|
||||
<td>
|
||||
% for tip in tips:
|
||||
<table cellspacing="0" cellpadding="0" border="0" width="580" align="center" style="width:100%; max-width:580px;">
|
||||
<tr>
|
||||
<td valign="top">
|
||||
@@ -53,6 +55,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endfor
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -137,6 +140,15 @@
|
||||
</tr>
|
||||
</table>
|
||||
% endfor
|
||||
% if preferences:
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border-left: 1px solid #eeeeee; border-right: 1px solid #eeeeee; width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr>
|
||||
<td bgcolor="#fafafa" width="100%" height="50" align="center">
|
||||
${preferences | safe}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
% endif
|
||||
% if user.has_group('base.group_system'):
|
||||
<table bgcolor="#ffffff" cellspacing="0" cellpadding="0" width="650" align="center" border="0" style="border-left: 1px solid #eeeeee; border-right: 1px solid #eeeeee; width: 100%; max-width: 650px; padding:30px 30px">
|
||||
<tr>
|
||||
|
||||
@@ -2,9 +2,10 @@
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="digest_digest_default" model="digest.digest">
|
||||
<field name="name">Weekly Stats in Odoo</field>
|
||||
<field name="name">Awesome Stats with Odoo</field>
|
||||
<field name="periodicity">daily</field>
|
||||
<field name="user_ids" eval="[(4, ref('base.user_admin'))]"/>
|
||||
<field name="next_run_date" eval="(DateTime.now() + timedelta(days=7)).strftime('%Y-%m-%d')"/>
|
||||
<field name="next_run_date" eval="(DateTime.now() + timedelta(days=1)).strftime('%Y-%m-%d')"/>
|
||||
<field name="kpi_res_users_connected">True</field>
|
||||
<field name="kpi_mail_message_total">True</field>
|
||||
</record>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import logging
|
||||
import math
|
||||
import pytz
|
||||
|
||||
from datetime import datetime, date
|
||||
@@ -23,10 +22,11 @@ class Digest(models.Model):
|
||||
# Digest description
|
||||
name = fields.Char(string='Name', required=True, translate=True)
|
||||
user_ids = fields.Many2many('res.users', string='Recipients', domain="[('share', '=', False)]")
|
||||
periodicity = fields.Selection([('weekly', 'Weekly'),
|
||||
periodicity = fields.Selection([('daily', 'Daily'),
|
||||
('weekly', 'Weekly'),
|
||||
('monthly', 'Monthly'),
|
||||
('quarterly', 'Quarterly')],
|
||||
string='Periodicity', default='weekly', required=True)
|
||||
string='Periodicity', default='daily', required=True)
|
||||
next_run_date = fields.Date(string='Next Send Date')
|
||||
template_id = fields.Many2one('mail.template', string='Email Template',
|
||||
domain="[('model','=','digest.digest')]",
|
||||
@@ -77,19 +77,21 @@ class Digest(models.Model):
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
vals['next_run_date'] = date.today() + relativedelta(days=3)
|
||||
return super(Digest, self).create(vals)
|
||||
digest = super(Digest, self).create(vals)
|
||||
if not digest.next_run_date:
|
||||
digest.next_run_date = digest._get_next_run_date()
|
||||
return digest
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ACTIONS
|
||||
# ------------------------------------------------------------
|
||||
|
||||
def action_subscribe(self):
|
||||
if self.env.user not in self.user_ids:
|
||||
if self.env.user.has_group('base.group_user') and self.env.user not in self.user_ids:
|
||||
self.sudo().user_ids |= self.env.user
|
||||
|
||||
def action_unsubcribe(self):
|
||||
if self.env.user in self.user_ids:
|
||||
if self.env.user.has_group('base.group_user') and self.env.user in self.user_ids:
|
||||
self.sudo().user_ids -= self.env.user
|
||||
|
||||
def action_activate(self):
|
||||
@@ -98,10 +100,16 @@ class Digest(models.Model):
|
||||
def action_deactivate(self):
|
||||
self.state = 'deactivated'
|
||||
|
||||
def action_set_periodicity(self, periodicity):
|
||||
self.periodicity = periodicity
|
||||
|
||||
def action_send(self):
|
||||
to_slowdown = self._check_daily_logs()
|
||||
for digest in self:
|
||||
for user in digest.user_ids:
|
||||
digest._action_send_to_user(user, tips_count=1)
|
||||
digest.with_context(digest_slowdown=digest in to_slowdown)._action_send_to_user(user, tips_count=1)
|
||||
if digest in to_slowdown:
|
||||
digest.write({'periodicity': 'weekly'})
|
||||
digest.next_run_date = digest._get_next_run_date()
|
||||
|
||||
def _action_send_to_user(self, user, tips_count=1):
|
||||
@@ -167,7 +175,7 @@ class Digest(models.Model):
|
||||
self.env['mail.render.mixin']._render_template(tools.html_sanitize(tip.tip_description), 'digest.tip', tip.ids, post_process=True)[tip.id]
|
||||
for tip in tips
|
||||
]
|
||||
# tip.user_ids += user
|
||||
tip.user_ids += user
|
||||
return tip_descriptions
|
||||
|
||||
def compute_kpis_actions(self, company, user):
|
||||
@@ -178,8 +186,27 @@ class Digest(models.Model):
|
||||
"""
|
||||
return {}
|
||||
|
||||
def compute_preferences(self, company, user):
|
||||
""" Give an optional text for preferences, like a shortcut for configuration.
|
||||
|
||||
:return string: html to put in template
|
||||
"""
|
||||
preferences = self.env['mail.render.mixin']._render_template(
|
||||
'digest.digest_section_preferences',
|
||||
'digest.digest',
|
||||
self.ids,
|
||||
engine='qweb',
|
||||
add_context={
|
||||
'company': company,
|
||||
'user': user,
|
||||
},
|
||||
post_process=True)[self.id]
|
||||
return preferences
|
||||
|
||||
def _get_next_run_date(self):
|
||||
self.ensure_one()
|
||||
if self.periodicity == 'daily':
|
||||
delta = relativedelta(days=1)
|
||||
if self.periodicity == 'weekly':
|
||||
delta = relativedelta(weeks=1)
|
||||
elif self.periodicity == 'monthly':
|
||||
@@ -216,6 +243,18 @@ class Digest(models.Model):
|
||||
margin = float_round((float(value-previous_value) / previous_value or 1) * 100, precision_digits=2)
|
||||
return margin
|
||||
|
||||
def _check_daily_logs(self):
|
||||
three_days_ago = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) - relativedelta(days=3)
|
||||
to_slowdown = self.env['digest.digest']
|
||||
for digest in self.filtered(lambda digest: digest.periodicity == 'daily'):
|
||||
users_logs = self.env['res.users.log'].sudo().search_count([
|
||||
('create_uid', 'in', digest.user_ids.ids),
|
||||
('create_date', '>=', three_days_ago)
|
||||
])
|
||||
if not users_logs:
|
||||
to_slowdown += digest
|
||||
return to_slowdown
|
||||
|
||||
def _format_currency_amount(self, amount, currency_id):
|
||||
pre = currency_id.position == 'before'
|
||||
symbol = u'{symbol}'.format(symbol=currency_id.symbol or '')
|
||||
|
||||
@@ -2,4 +2,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_digest_digest_system,digest.digest.administration,model_digest_digest,base.group_erp_manager,1,1,1,1
|
||||
access_digest_digest_user,digest.digest.user,model_digest_digest,base.group_user,1,0,0,0
|
||||
access_digest_tip_system,digest.tip.administration,model_digest_tip,base.group_erp_manager,1,1,1,1
|
||||
access_digest_tip_user,digest.tip.user,model_digest_tip,base.group_user,0,0,0,0
|
||||
access_digest_tip_user,digest.tip.user,model_digest_tip,base.group_user,1,0,0,0
|
||||
|
||||
|
Reference in New Issue
Block a user