You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
9.6 KiB
283 lines
9.6 KiB
# -*- coding: utf-8 -*-
|
|
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
|
|
|
from odoo import fields, models, api, exceptions, tools, _
|
|
import logging
|
|
|
|
logger = logging.getLogger('[mail_digest]')
|
|
|
|
|
|
class MailDigest(models.Model):
|
|
_name = 'mail.digest'
|
|
_description = 'Mail digest'
|
|
_order = 'create_date desc'
|
|
|
|
name = fields.Char(
|
|
string="Name",
|
|
compute="_compute_name",
|
|
readonly=True,
|
|
)
|
|
partner_id = fields.Many2one(
|
|
string='Partner',
|
|
comodel_name='res.partner',
|
|
readonly=True,
|
|
required=True,
|
|
ondelete='cascade',
|
|
)
|
|
frequency = fields.Selection(
|
|
related='partner_id.notify_frequency',
|
|
readonly=True,
|
|
)
|
|
message_ids = fields.Many2many(
|
|
comodel_name='mail.message',
|
|
string='Messages'
|
|
)
|
|
mail_id = fields.Many2one(
|
|
'mail.mail',
|
|
'Mail',
|
|
ondelete='set null',
|
|
)
|
|
state = fields.Selection(related='mail_id.state', readonly=True)
|
|
# To my future self: never ever change this field to `template_id`.
|
|
# When creating digest records within the context of mail composer
|
|
# (and possibly other contexts) you'll have a `default_template_id`
|
|
# key in the context which is going to override our safe default.
|
|
# This is going to break email generation because the template
|
|
# will be completely wrong. Lesson learned :)
|
|
digest_template_id = fields.Many2one(
|
|
'ir.ui.view',
|
|
'Qweb mail template',
|
|
ondelete='set null',
|
|
default=lambda self: self._default_digest_template_id(),
|
|
domain=[('type', '=', 'qweb')],
|
|
oldname='template_id',
|
|
)
|
|
sanitize_msg_body = fields.Boolean(
|
|
string='Sanitize message body',
|
|
help='Collected messages can have different styles applied '
|
|
'on each element. If this flag is enabled (default) '
|
|
'each message content will be sanitized '
|
|
'before generating the email.',
|
|
default=True,
|
|
)
|
|
|
|
def _default_digest_template_id(self):
|
|
"""Retrieve default template to render digest."""
|
|
return self.env.ref('mail_digest.default_digest_tmpl',
|
|
raise_if_not_found=False)
|
|
|
|
@api.multi
|
|
@api.depends("partner_id", "partner_id.notify_frequency")
|
|
def _compute_name(self):
|
|
for rec in self:
|
|
rec.name = u'{} - {}'.format(
|
|
rec.partner_id.name, rec._get_subject())
|
|
|
|
@api.model
|
|
def create_or_update(self, partners, message, subtype_id=None):
|
|
"""Create or update digest.
|
|
|
|
:param partners: recipients as `res.partner` browse list
|
|
:param message: `mail.message` to include in digest
|
|
:param subtype_id: `mail.message.subtype` instance
|
|
"""
|
|
subtype_id = subtype_id or message.subtype_id
|
|
for partner in partners:
|
|
digest = self._get_or_create_by_partner(partner, message)
|
|
digest.message_ids |= message
|
|
return True
|
|
|
|
@api.model
|
|
def _get_by_partner(self, partner, mail_id=False):
|
|
"""Retrieve digest record for given partner.
|
|
|
|
:param partner: `res.partner` browse record
|
|
:param mail_id: `mail.mail` record for further filtering.
|
|
|
|
By default we lookup for pending digest without notification yet.
|
|
"""
|
|
domain = [
|
|
('partner_id', '=', partner.id),
|
|
('mail_id', '=', mail_id),
|
|
]
|
|
return self.search(domain, limit=1)
|
|
|
|
@api.model
|
|
def _get_or_create_by_partner(self, partner, message=None, mail_id=False):
|
|
"""Retrieve digest record or create it by partner.
|
|
|
|
:param partner: `res.partner` record to create/get digest for
|
|
:param message: `mail.message` to include in digest
|
|
:param mail_id: `mail.mail` record to set on digest
|
|
"""
|
|
existing = self._get_by_partner(partner, mail_id=mail_id)
|
|
if existing:
|
|
return existing
|
|
values = {'partner_id': partner.id, }
|
|
return self.create(values)
|
|
|
|
@api.model
|
|
def _message_group_by_key(self, msg):
|
|
"""Return the key to group messages by."""
|
|
return msg.subtype_id.id
|
|
|
|
@api.multi
|
|
def _message_group_by(self):
|
|
"""Group digest messages.
|
|
|
|
A digest can contain several messages.
|
|
To display them in a nice and organized form in your emails
|
|
we group them by subtype by default.
|
|
"""
|
|
self.ensure_one()
|
|
grouped = {}
|
|
for msg in self.message_ids:
|
|
grouped.setdefault(self._message_group_by_key(msg), []).append(msg)
|
|
return grouped
|
|
|
|
@api.model
|
|
def message_body(self, msg, strip_style=True):
|
|
"""Return body message prepared for email content.
|
|
|
|
Message's body can contains styles and other stuff
|
|
that can screw the look and feel of digests' mails.
|
|
|
|
Here we sanitize it if `sanitize_msg_body` is set on the digest.
|
|
"""
|
|
if not self.sanitize_msg_body:
|
|
return msg.body
|
|
return tools.html_sanitize(msg.body or '', strip_style=strip_style)
|
|
|
|
def _get_site_name(self):
|
|
"""Retrieve site name for meaningful mail subject.
|
|
|
|
If you run a website we get website's name
|
|
otherwise we default to current user's company name.
|
|
"""
|
|
# default to company
|
|
name = self.env.user.company_id.name
|
|
if 'website' in self.env:
|
|
# TODO: shall we make this configurable at digest or global level?
|
|
# Maybe you have a website but
|
|
# your digest msgs are not related to it at all or partially.
|
|
ws = None
|
|
try:
|
|
ws = self.env['website'].get_current_website()
|
|
name = ws.name
|
|
except RuntimeError:
|
|
# RuntimeError: object unbound -> no website request.
|
|
# Fallback to default website if any.
|
|
ws = self.env['website'].search([], limit=1)
|
|
if ws:
|
|
name = ws.name
|
|
return name
|
|
|
|
@api.multi
|
|
def _get_subject(self):
|
|
"""Build the full subject for digest's mail."""
|
|
# TODO: shall we move this to computed field?
|
|
self.ensure_one()
|
|
subject = u'[{}] '.format(self._get_site_name())
|
|
if self.partner_id.notify_frequency == 'daily':
|
|
subject += _('Daily update')
|
|
elif self.partner_id.notify_frequency == 'weekly':
|
|
subject += _('Weekly update')
|
|
return subject
|
|
|
|
@api.multi
|
|
def _get_template_values(self):
|
|
"""Collect variables to render digest's template."""
|
|
self.ensure_one()
|
|
subject = self._get_subject()
|
|
template_values = {
|
|
'digest': self,
|
|
'subject': subject,
|
|
'grouped_messages': self._message_group_by(),
|
|
'base_url':
|
|
self.env['ir.config_parameter'].get_param('web.base.url'),
|
|
}
|
|
return template_values
|
|
|
|
@api.multi
|
|
def _get_email_values(self, template=None):
|
|
"""Collect variables to create digest's mail message."""
|
|
self.ensure_one()
|
|
template = template or self.digest_template_id
|
|
if not template:
|
|
raise exceptions.UserError(_(
|
|
'You must pass a template or set one on the digest record.'
|
|
))
|
|
subject = self._get_subject()
|
|
template_values = self._get_template_values()
|
|
values = {
|
|
'email_from': self.env.user.company_id.email,
|
|
'recipient_ids': [(4, self.partner_id.id)],
|
|
'subject': subject,
|
|
'body_html': template.with_context(
|
|
**self._template_context()
|
|
).render(template_values),
|
|
}
|
|
return values
|
|
|
|
def _create_mail_context(self):
|
|
"""Inject context vars.
|
|
|
|
By default we make sure that digest's email
|
|
will have only digest's partner among recipients.
|
|
"""
|
|
return {
|
|
'notify_only_recipients': True,
|
|
}
|
|
|
|
@api.multi
|
|
def _template_context(self):
|
|
"""Rendering context for digest's template.
|
|
|
|
By default we enforce partner's language.
|
|
"""
|
|
self.ensure_one()
|
|
return {
|
|
'lang': self.partner_id.lang,
|
|
}
|
|
|
|
@api.multi
|
|
def create_email(self, template=None):
|
|
"""Create `mail.message` records for current digests.
|
|
|
|
:param template: qweb template instance to override default digest one.
|
|
"""
|
|
mail_model = self.env['mail.mail'].with_context(
|
|
**self._create_mail_context())
|
|
created = []
|
|
for item in self:
|
|
if not item.message_ids:
|
|
# useless to create a mail for a digest w/ messages
|
|
# messages could be deleted by admin for instance.
|
|
continue
|
|
values = item.with_context(
|
|
**item._template_context()
|
|
)._get_email_values(template=template)
|
|
item.mail_id = mail_model.create(values)
|
|
created.append(item.id)
|
|
if created:
|
|
logger.info('Create email for digest IDS=%s', str(created))
|
|
return created
|
|
|
|
@api.multi
|
|
def action_create_email(self):
|
|
return self.create_email()
|
|
|
|
@api.model
|
|
def process(self, frequency='daily', domain=None):
|
|
"""Process existing digest records to create emails via cron.
|
|
|
|
:param frequency: lookup digest records by partners' `notify_frequency`
|
|
:param domain: pass custom domain to lookup only specific digests
|
|
"""
|
|
if not domain:
|
|
domain = [
|
|
('mail_id', '=', False),
|
|
('partner_id.notify_frequency', '=', frequency),
|
|
]
|
|
self.search(domain).create_email()
|