mirror of https://github.com/OCA/social.git
[MIG][11.0] mail_digest
parent
84d34e8af4
commit
6a576a5427
|
@ -31,9 +31,11 @@ all the messages are collected inside a `mail.digest` container.
|
|||
A daily cron and a weekly cron will take care of creating a single email per each digest,
|
||||
which will be sent as a standard email.
|
||||
|
||||
If the message has a specific subtype, all of this will work only
|
||||
if personal settings allow to receive notification for that specific subtype.
|
||||
Specifically:
|
||||
**Rules**
|
||||
|
||||
Given that the user has `Notification management = Handle by Emails`...
|
||||
|
||||
a message with subtype assigned *will be sent* via digest if:
|
||||
|
||||
* no record for type: message passes
|
||||
* record disabled for type: message don't pass
|
||||
|
@ -42,6 +44,11 @@ Specifically:
|
|||
NOTE: under the hood the digest notification logic excludes followers to be notified,
|
||||
since you really want to notify only mail.digest's partner.
|
||||
|
||||
a message with subtype assigned *will NOT be sent* via digest if:
|
||||
|
||||
* global: `mail_digest_enabled_message_types` param disables the message type
|
||||
* user: digest mode is OFF for the recipient
|
||||
* user: recipient's user has disabled the subtype in her/his settings
|
||||
|
||||
Global settings
|
||||
---------------
|
||||
|
@ -66,7 +73,7 @@ Bug Tracker
|
|||
Bugs are tracked on `GitHub Issues
|
||||
<https://github.com/OCA/social/issues>`_. In case of trouble, please
|
||||
check there if your issue has already been reported. If you spotted it first,
|
||||
help us smashing it by providing a detailed and welcomed feedback.
|
||||
help us smash it by providing a detailed and welcomed feedback.
|
||||
|
||||
Credits
|
||||
=======
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
{
|
||||
'name': 'Mail digest',
|
||||
'summary': """Basic digest mail handling.""",
|
||||
'version': '10.0.1.0.1',
|
||||
'version': '11.0.1.0.0',
|
||||
'license': 'AGPL-3',
|
||||
'author': 'Camptocamp,Odoo Community Association (OCA)',
|
||||
'author': 'Camptocamp, Odoo Community Association (OCA)',
|
||||
'website': 'https://github.com/OCA/social',
|
||||
'depends': [
|
||||
'mail',
|
||||
|
@ -18,7 +17,7 @@
|
|||
'security/ir.model.access.csv',
|
||||
'security/record_rules.xml',
|
||||
'views/mail_digest_views.xml',
|
||||
'views/partner_views.xml',
|
||||
'views/user_notification_views.xml',
|
||||
'views/user_views.xml',
|
||||
'templates/digest_default.xml',
|
||||
],
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="mail_digest_enabled_message_types" model="ir.config_parameter">
|
||||
<field name="key">mail_digest.enabled_message_types</field>
|
||||
<!-- enabled by default for each message type -->
|
||||
<field name="value">email,notification,comment</field>
|
||||
</record>
|
||||
</data>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo noupdate="1">
|
||||
<record id="mail_digest_enabled_message_types" model="ir.config_parameter">
|
||||
<field name="key">mail_digest.enabled_message_types</field>
|
||||
<!-- enabled by default for each message type -->
|
||||
<field name="value">email,notification,comment</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
@ -1,27 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record forcecreate="True" id="ir_cron_mail_digest_daily_action" model="ir.cron">
|
||||
<field name="name">Digest mail process - daily</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
<field eval="'mail.digest'" name="model"/>
|
||||
<field eval="'process'" name="function"/>
|
||||
<field eval="'()'" name="args"/>
|
||||
</record>
|
||||
<record forcecreate="True" id="ir_cron_mail_digest_weekly_action" model="ir.cron">
|
||||
<field name="name">Digest mail process - weekly</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">weeks</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
<field eval="'mail.digest'" name="model"/>
|
||||
<field eval="'process'" name="function"/>
|
||||
<field eval="'(\'weekly\',)'" name="args"/>
|
||||
</record>
|
||||
</data>
|
||||
<odoo noupdate="1">
|
||||
<record forcecreate="True" id="ir_cron_mail_digest_daily_action" model="ir.cron">
|
||||
<field name="name">Digest mail process - daily</field>
|
||||
<field name="model_id" ref="model_mail_digest"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">days</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field eval="False" name="doall"/>
|
||||
<field name="code">model.process()</field>
|
||||
</record>
|
||||
<record forcecreate="True" id="ir_cron_mail_digest_weekly_action" model="ir.cron">
|
||||
<field name="name">Digest mail process - weekly</field>
|
||||
<field name="model_id" ref="model_mail_digest"/>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="interval_number">1</field>
|
||||
<field name="interval_type">weeks</field>
|
||||
<field name="numbercall">-1</field>
|
||||
<field name="doall" eval="False"/>
|
||||
<field name="code">model.process(frequency='weekly')</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<template id="view_email_template_corporate_identity">
|
||||
<body>
|
||||
<html>
|
||||
<img style="float: right" t-attf-src="data:image;base64,{{env.user.company_id.logo}}" />
|
||||
<!-- if some template calling us sets this variable,
|
||||
we print a h1 tag /-->
|
||||
<h1 t-if="email_heading"><t t-esc="email_heading" /></h1>
|
||||
<t t-raw="0" />
|
||||
<!-- use some standard footer if the user doesn't have
|
||||
a signature /-->
|
||||
<footer t-if="not email_use_user_signature">
|
||||
<p>
|
||||
<a t-att-href="env.user.company_id.website">
|
||||
<t t-esc="env.user.company_id.name" />
|
||||
</a>
|
||||
</p>
|
||||
<p><t t-esc="env.user.company_id.phone" /></p>
|
||||
</footer>
|
||||
<footer t-if="email_use_user_signature">
|
||||
<t t-raw="env.user.signature" />
|
||||
</footer>
|
||||
</html>
|
||||
</body>
|
||||
</template>
|
||||
<template id="view_email_template_demo1">
|
||||
<!-- because we can simply call the ci here, we don't need to
|
||||
repeat it /-->
|
||||
<t t-call="email_template_qweb.view_email_template_corporate_identity">
|
||||
<!-- the template we call uses this as title if we set it /-->
|
||||
<t t-set="email_heading" t-value="email_template.subject" />
|
||||
<h2>Dear <t t-esc="object.name" />,</h2>
|
||||
<p>
|
||||
This is an email template using qweb.
|
||||
</p>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
|
@ -1,10 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<odoo>
|
||||
<record id="email_template_demo1" model="mail.template">
|
||||
<field name="name">QWeb demo</field>
|
||||
<field name="body_type">qweb</field>
|
||||
<field name="body_view_id" ref="view_email_template_demo1" />
|
||||
<field name="model_id" ref="base.model_res_users" />
|
||||
<field name="subject">QWeb demo email</field>
|
||||
</record>
|
||||
</odoo>
|
|
@ -1,3 +1,4 @@
|
|||
from . import mail_digest
|
||||
from . import user_notification_conf
|
||||
from . import res_partner
|
||||
from . import res_users
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo import fields, models, api, exceptions, _
|
||||
|
@ -19,15 +18,15 @@ class MailDigest(models.Model):
|
|||
compute="_compute_name",
|
||||
readonly=True,
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
string='Partner',
|
||||
comodel_name='res.partner',
|
||||
user_id = fields.Many2one(
|
||||
string='User',
|
||||
comodel_name='res.users',
|
||||
readonly=True,
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
)
|
||||
frequency = fields.Selection(
|
||||
related='partner_id.notify_frequency',
|
||||
related='user_id.digest_frequency',
|
||||
readonly=True,
|
||||
)
|
||||
message_ids = fields.Many2many(
|
||||
|
@ -52,7 +51,6 @@ class MailDigest(models.Model):
|
|||
ondelete='set null',
|
||||
default=lambda self: self._default_digest_template_id(),
|
||||
domain=[('type', '=', 'qweb')],
|
||||
oldname='template_id',
|
||||
)
|
||||
|
||||
def _default_digest_template_id(self):
|
||||
|
@ -61,53 +59,47 @@ class MailDigest(models.Model):
|
|||
raise_if_not_found=False)
|
||||
|
||||
@api.multi
|
||||
@api.depends("partner_id", "partner_id.notify_frequency")
|
||||
@api.depends("user_id", "user_id.digest_frequency")
|
||||
def _compute_name(self):
|
||||
for rec in self:
|
||||
rec.name = u'{} - {}'.format(
|
||||
rec.partner_id.name, rec._get_subject())
|
||||
rec.name = '{} - {}'.format(
|
||||
rec.user_id.name, rec._get_subject())
|
||||
|
||||
@api.model
|
||||
def create_or_update(self, partners, message, subtype_id=None):
|
||||
def create_or_update(self, partners, message):
|
||||
"""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 = self._get_or_create_by_user(partner.real_user_id)
|
||||
digest.message_ids |= message
|
||||
return True
|
||||
|
||||
@api.model
|
||||
def _get_by_partner(self, partner, mail_id=False):
|
||||
"""Retrieve digest record for given partner.
|
||||
def _get_by_user(self, user):
|
||||
"""Retrieve digest record for given user.
|
||||
|
||||
:param partner: `res.partner` browse record
|
||||
:param mail_id: `mail.mail` record for further filtering.
|
||||
:param user: `res.users` browse record
|
||||
|
||||
By default we lookup for pending digest without notification yet.
|
||||
"""
|
||||
domain = [
|
||||
('partner_id', '=', partner.id),
|
||||
('mail_id', '=', mail_id),
|
||||
('user_id', '=', user.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.
|
||||
def _get_or_create_by_user(self, user):
|
||||
"""Retrieve digest record or create it by user.
|
||||
|
||||
: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
|
||||
:param user: `res.users` record to create/get digest for
|
||||
"""
|
||||
existing = self._get_by_partner(partner, mail_id=mail_id)
|
||||
existing = self._get_by_user(user)
|
||||
if existing:
|
||||
return existing
|
||||
values = {'partner_id': partner.id, }
|
||||
values = {'user_id': user.id, }
|
||||
return self.create(values)
|
||||
|
||||
@api.model
|
||||
|
@ -158,10 +150,10 @@ class MailDigest(models.Model):
|
|||
"""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 = '[{}] '.format(self._get_site_name())
|
||||
if self.user_id.digest_frequency == 'daily':
|
||||
subject += _('Daily update')
|
||||
elif self.partner_id.notify_frequency == 'weekly':
|
||||
elif self.user_id.digest_frequency == 'weekly':
|
||||
subject += _('Weekly update')
|
||||
return subject
|
||||
|
||||
|
@ -192,7 +184,7 @@ class MailDigest(models.Model):
|
|||
template_values = self._get_template_values()
|
||||
values = {
|
||||
'email_from': self.env.user.company_id.email,
|
||||
'recipient_ids': [(4, self.partner_id.id)],
|
||||
'recipient_ids': [(4, self.user_id.partner_id.id)],
|
||||
'subject': subject,
|
||||
'body_html': template.with_context(
|
||||
**self._template_context()
|
||||
|
@ -204,7 +196,7 @@ class MailDigest(models.Model):
|
|||
"""Inject context vars.
|
||||
|
||||
By default we make sure that digest's email
|
||||
will have only digest's partner among recipients.
|
||||
will have only digest's user among recipients.
|
||||
"""
|
||||
return {
|
||||
'notify_only_recipients': True,
|
||||
|
@ -214,11 +206,11 @@ class MailDigest(models.Model):
|
|||
def _template_context(self):
|
||||
"""Rendering context for digest's template.
|
||||
|
||||
By default we enforce partner's language.
|
||||
By default we enforce user's language.
|
||||
"""
|
||||
self.ensure_one()
|
||||
return {
|
||||
'lang': self.partner_id.lang,
|
||||
'lang': self.user_id.lang,
|
||||
}
|
||||
|
||||
@api.multi
|
||||
|
@ -252,12 +244,12 @@ class MailDigest(models.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 frequency: lookup digest records by users' `digest_frequency`
|
||||
:param domain: pass custom domain to lookup only specific digests
|
||||
"""
|
||||
if not domain:
|
||||
domain = [
|
||||
('mail_id', '=', False),
|
||||
('partner_id.notify_frequency', '=', frequency),
|
||||
('user_id.digest_frequency', '=', frequency),
|
||||
]
|
||||
self.search(domain).create_email()
|
||||
|
|
|
@ -1,88 +1,19 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo import models, fields, api, _
|
||||
from odoo import models, api
|
||||
|
||||
|
||||
class ResPartner(models.Model):
|
||||
_inherit = 'res.partner'
|
||||
|
||||
notify_email = fields.Selection(selection_add=[('digest', _('Digest'))])
|
||||
notify_frequency = fields.Selection(
|
||||
string='Frequency',
|
||||
selection=[
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly')
|
||||
],
|
||||
default='weekly',
|
||||
required=True,
|
||||
)
|
||||
notify_conf_ids = fields.One2many(
|
||||
string='Notifications',
|
||||
inverse_name='partner_id',
|
||||
comodel_name='partner.notification.conf',
|
||||
)
|
||||
enabled_notify_subtype_ids = fields.Many2many(
|
||||
string='Partner enabled subtypes',
|
||||
comodel_name='mail.message.subtype',
|
||||
compute='_compute_enabled_notify_subtype_ids',
|
||||
search='_search_enabled_notify_subtype_ids',
|
||||
)
|
||||
disabled_notify_subtype_ids = fields.Many2many(
|
||||
string='Partner disabled subtypes',
|
||||
comodel_name='mail.message.subtype',
|
||||
compute='_compute_disabled_notify_subtype_ids',
|
||||
search='_search_disabled_notify_subtype_ids',
|
||||
)
|
||||
|
||||
@api.multi
|
||||
def _compute_notify_subtypes(self, enabled):
|
||||
self.ensure_one()
|
||||
query = (
|
||||
'SELECT subtype_id FROM partner_notification_conf '
|
||||
'WHERE partner_id=%s AND enabled = %s'
|
||||
)
|
||||
self.env.cr.execute(
|
||||
query, (self.id, enabled))
|
||||
return [x[0] for x in self.env.cr.fetchall()]
|
||||
|
||||
@api.multi
|
||||
@api.depends('notify_conf_ids.subtype_id')
|
||||
def _compute_enabled_notify_subtype_ids(self):
|
||||
for partner in self:
|
||||
partner.enabled_notify_subtype_ids = \
|
||||
partner._compute_notify_subtypes(True)
|
||||
|
||||
@api.multi
|
||||
@api.depends('notify_conf_ids.subtype_id')
|
||||
def _compute_disabled_notify_subtype_ids(self):
|
||||
for partner in self:
|
||||
partner.disabled_notify_subtype_ids = \
|
||||
partner._compute_notify_subtypes(False)
|
||||
|
||||
def _search_notify_subtype_ids_domain(self, operator, value, enabled):
|
||||
"""Build domain to search notification subtypes by partner settings."""
|
||||
if operator in ('in', 'not in') and \
|
||||
not isinstance(value, (tuple, list)):
|
||||
value = [value, ]
|
||||
conf_value = value
|
||||
if isinstance(conf_value, int):
|
||||
# we search conf records always w/ 'in'
|
||||
conf_value = [conf_value]
|
||||
_value = self.env['partner.notification.conf'].search([
|
||||
('subtype_id', 'in', conf_value),
|
||||
('enabled', '=', enabled),
|
||||
]).mapped('partner_id').ids
|
||||
return [('id', operator, _value)]
|
||||
|
||||
def _search_enabled_notify_subtype_ids(self, operator, value):
|
||||
return self._search_notify_subtype_ids_domain(
|
||||
operator, value, True)
|
||||
|
||||
def _search_disabled_notify_subtype_ids(self, operator, value):
|
||||
return self._search_notify_subtype_ids_domain(
|
||||
operator, value, False)
|
||||
# Shortcut to bypass this weird thing of odoo:
|
||||
# `partner.user_id` is the "saleman"
|
||||
# while the user is stored into `user_ids`
|
||||
# but in the majority of the cases we have one real user per partner.
|
||||
@property
|
||||
def real_user_id(self):
|
||||
return self.user_ids[0] if self.user_ids else False
|
||||
|
||||
@api.multi
|
||||
def _notify(self, message,
|
||||
|
@ -94,17 +25,14 @@ class ResPartner(models.Model):
|
|||
# the reason should be that anybody can write messages to a partner
|
||||
# and you really want to find all ppl to be notified
|
||||
partners = self.sudo().search(email_domain)
|
||||
partners._notify_by_email(
|
||||
super(ResPartner, partners)._notify(
|
||||
message, force_send=force_send,
|
||||
send_after_commit=send_after_commit, user_signature=user_signature)
|
||||
send_after_commit=send_after_commit,
|
||||
user_signature=user_signature)
|
||||
# notify_by_digest
|
||||
digest_domain = self._get_notify_by_email_domain(
|
||||
message, digest=True)
|
||||
digest_domain = self._get_notify_by_email_domain(message, digest=True)
|
||||
partners = self.sudo().search(digest_domain)
|
||||
partners._notify_by_digest(message)
|
||||
|
||||
# notify_by_chat
|
||||
self._notify_by_chat(message)
|
||||
return True
|
||||
|
||||
def _digest_enabled_message_types(self):
|
||||
|
@ -153,12 +81,10 @@ class ResPartner(models.Model):
|
|||
'|',
|
||||
('id', 'in', ids),
|
||||
('channel_ids', 'in', channels.ids),
|
||||
('email', '!=', email)
|
||||
('email', '!=', email),
|
||||
('user_ids.digest_mode', '=', digest),
|
||||
('user_ids.notification_type', '=', 'email'),
|
||||
]
|
||||
if not digest:
|
||||
domain.append(('notify_email', 'not in', ('none', 'digest')))
|
||||
else:
|
||||
domain.append(('notify_email', '=', 'digest'))
|
||||
if message.subtype_id:
|
||||
domain.extend(self._get_domain_subtype_leaf(message.subtype_id))
|
||||
return domain
|
||||
|
@ -167,65 +93,6 @@ class ResPartner(models.Model):
|
|||
def _get_domain_subtype_leaf(self, subtype):
|
||||
return [
|
||||
'|',
|
||||
('disabled_notify_subtype_ids', 'not in', (subtype.id, )),
|
||||
('enabled_notify_subtype_ids', 'in', (subtype.id, )),
|
||||
('user_ids.disabled_notify_subtype_ids', 'not in', (subtype.id, )),
|
||||
('user_ids.enabled_notify_subtype_ids', 'in', (subtype.id, )),
|
||||
]
|
||||
|
||||
@api.multi
|
||||
def _notify_update_subtype(self, subtype, enable):
|
||||
"""Update notification settings by subtype.
|
||||
|
||||
:param subtype: `mail.message.subtype` to enable or disable
|
||||
:param enable: boolean to enable or disable given subtype
|
||||
"""
|
||||
self.ensure_one()
|
||||
exists = self.env['partner.notification.conf'].search([
|
||||
('subtype_id', '=', subtype.id),
|
||||
('partner_id', '=', self.id)
|
||||
], limit=1)
|
||||
if exists:
|
||||
exists.enabled = enable
|
||||
else:
|
||||
self.write({
|
||||
'notify_conf_ids': [
|
||||
(0, 0, {'enabled': enable, 'subtype_id': subtype.id})]
|
||||
})
|
||||
|
||||
@api.multi
|
||||
def _notify_enable_subtype(self, subtype):
|
||||
"""Enable given subtype."""
|
||||
self._notify_update_subtype(subtype, True)
|
||||
|
||||
@api.multi
|
||||
def _notify_disable_subtype(self, subtype):
|
||||
"""Disable given subtype."""
|
||||
self._notify_update_subtype(subtype, False)
|
||||
|
||||
|
||||
class PartnerNotificationConf(models.Model):
|
||||
"""Hold partner's single notification configuration."""
|
||||
_name = 'partner.notification.conf'
|
||||
_description = 'Partner notification configuration'
|
||||
# TODO: add friendly onchange to not yield errors when editin via UI
|
||||
_sql_constraints = [
|
||||
('unique_partner_subtype_conf',
|
||||
'unique (partner_id,subtype_id)',
|
||||
'You can have only one configuration per subtype!')
|
||||
]
|
||||
|
||||
partner_id = fields.Many2one(
|
||||
string='Partner',
|
||||
comodel_name='res.partner',
|
||||
readonly=True,
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
index=True,
|
||||
)
|
||||
subtype_id = fields.Many2one(
|
||||
'mail.message.subtype',
|
||||
'Notification type',
|
||||
ondelete='cascade',
|
||||
required=True,
|
||||
index=True,
|
||||
)
|
||||
enabled = fields.Boolean(default=True, index=True)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo import models
|
||||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class Users(models.Model):
|
||||
|
@ -18,11 +17,111 @@ class Users(models.Model):
|
|||
[copied from mail.models.users]
|
||||
"""
|
||||
super(Users, self).__init__(pool, cr)
|
||||
new_fields = [
|
||||
'digest_mode',
|
||||
'digest_frequency',
|
||||
'notify_conf_ids',
|
||||
]
|
||||
# duplicate list to avoid modifying the original reference
|
||||
type(self).SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
|
||||
type(self).SELF_WRITEABLE_FIELDS.extend(['notify_frequency'])
|
||||
type(self).SELF_WRITEABLE_FIELDS.extend(['notify_conf_ids'])
|
||||
type(self).SELF_WRITEABLE_FIELDS.extend(new_fields)
|
||||
# duplicate list to avoid modifying the original reference
|
||||
type(self).SELF_READABLE_FIELDS = list(self.SELF_READABLE_FIELDS)
|
||||
type(self).SELF_READABLE_FIELDS.extend(['notify_frequency'])
|
||||
type(self).SELF_READABLE_FIELDS.extend(['notify_conf_ids'])
|
||||
type(self).SELF_READABLE_FIELDS.extend(new_fields)
|
||||
|
||||
digest_mode = fields.Boolean(
|
||||
default=False,
|
||||
help='If enabled, email notifications will be sent in digest mode.'
|
||||
)
|
||||
digest_frequency = fields.Selection(
|
||||
string='Frequency',
|
||||
selection=[
|
||||
('daily', 'Daily'),
|
||||
('weekly', 'Weekly')
|
||||
],
|
||||
default='weekly',
|
||||
required=True,
|
||||
)
|
||||
notify_conf_ids = fields.One2many(
|
||||
string='Notifications',
|
||||
inverse_name='user_id',
|
||||
comodel_name='user.notification.conf',
|
||||
)
|
||||
enabled_notify_subtype_ids = fields.Many2many(
|
||||
string='User enabled subtypes',
|
||||
comodel_name='mail.message.subtype',
|
||||
compute='_compute_notify_subtype_ids',
|
||||
search='_search_enabled_notify_subtype_ids',
|
||||
)
|
||||
disabled_notify_subtype_ids = fields.Many2many(
|
||||
string='User disabled subtypes',
|
||||
comodel_name='mail.message.subtype',
|
||||
compute='_compute_notify_subtype_ids',
|
||||
search='_search_disabled_notify_subtype_ids',
|
||||
)
|
||||
|
||||
def _notify_subtypes_by_state(self, enabled):
|
||||
self.ensure_one()
|
||||
return self.notify_conf_ids.filtered(
|
||||
lambda x: x.enabled == enabled).mapped('subtype_id')
|
||||
|
||||
@api.multi
|
||||
@api.depends('notify_conf_ids.subtype_id', 'notify_conf_ids.enabled')
|
||||
def _compute_notify_subtype_ids(self):
|
||||
for rec in self:
|
||||
rec.enabled_notify_subtype_ids = \
|
||||
rec._notify_subtypes_by_state(True)
|
||||
rec.disabled_notify_subtype_ids = \
|
||||
rec._notify_subtypes_by_state(False)
|
||||
|
||||
def _search_notify_subtype_ids_domain(self, operator, value, enabled):
|
||||
"""Build domain to search notification subtypes by user conf."""
|
||||
if operator in ('in', 'not in') and \
|
||||
not isinstance(value, (tuple, list)):
|
||||
value = [value, ]
|
||||
conf_value = value
|
||||
if isinstance(conf_value, int):
|
||||
# we search conf records always w/ 'in'
|
||||
conf_value = [conf_value]
|
||||
_value = self.env['user.notification.conf'].search([
|
||||
('subtype_id', 'in', conf_value),
|
||||
('enabled', '=', enabled),
|
||||
]).mapped('user_id').ids
|
||||
return [('id', operator, _value)]
|
||||
|
||||
def _search_enabled_notify_subtype_ids(self, operator, value):
|
||||
return self._search_notify_subtype_ids_domain(operator, value, True)
|
||||
|
||||
def _search_disabled_notify_subtype_ids(self, operator, value):
|
||||
return self._search_notify_subtype_ids_domain(operator, value, False)
|
||||
|
||||
def _notify_update_subtype(self, subtype, enable):
|
||||
"""Update notification settings by subtype.
|
||||
|
||||
:param subtype: `mail.message.subtype` to enable or disable
|
||||
:param enable: boolean to enable or disable given subtype
|
||||
"""
|
||||
self.ensure_one()
|
||||
exists = self.env['user.notification.conf'].search([
|
||||
('subtype_id', '=', subtype.id),
|
||||
('user_id', '=', self.id)
|
||||
], limit=1)
|
||||
if exists:
|
||||
exists.enabled = enable
|
||||
else:
|
||||
self.write({
|
||||
'notify_conf_ids': [
|
||||
(0, 0, {'enabled': enable, 'subtype_id': subtype.id})]
|
||||
})
|
||||
|
||||
@api.multi
|
||||
def _notify_enable_subtype(self, subtype):
|
||||
"""Enable given subtype."""
|
||||
for rec in self:
|
||||
rec._notify_update_subtype(subtype, True)
|
||||
|
||||
@api.multi
|
||||
def _notify_disable_subtype(self, subtype):
|
||||
"""Disable given subtype."""
|
||||
for rec in self:
|
||||
rec._notify_update_subtype(subtype, False)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo import models, fields
|
||||
|
||||
|
||||
class UserNotificationConf(models.Model):
|
||||
"""Hold user's single notification configuration."""
|
||||
_name = 'user.notification.conf'
|
||||
_description = 'User notification configuration'
|
||||
# TODO: add friendly onchange to not yield errors when editin via UI
|
||||
_sql_constraints = [
|
||||
('unique_user_subtype_conf',
|
||||
'unique (user_id,subtype_id)',
|
||||
'You can have only one configuration per subtype!')
|
||||
]
|
||||
|
||||
user_id = fields.Many2one(
|
||||
string='User',
|
||||
comodel_name='res.users',
|
||||
readonly=True,
|
||||
required=True,
|
||||
ondelete='cascade',
|
||||
index=True,
|
||||
)
|
||||
subtype_id = fields.Many2one(
|
||||
'mail.message.subtype',
|
||||
'Notification type',
|
||||
ondelete='cascade',
|
||||
required=True,
|
||||
index=True,
|
||||
)
|
||||
enabled = fields.Boolean(default=True, index=True)
|
|
@ -1,4 +1,4 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_partner_notification_conf_user,partner.notification.user,model_partner_notification_conf,base.group_user,1,1,1,1
|
||||
access_partner_notification_conf_user,partner.notification.user,model_user_notification_conf,base.group_user,1,1,1,1
|
||||
access_mail_digest_system,mail.digest.sys,model_mail_digest,base.group_system,1,1,1,1
|
||||
access_partner_notification_conf_system,partner.notification.sys,model_partner_notification_conf,base.group_system,1,1,1,1
|
||||
access_partner_notification_conf_system,partner.notification.sys,model_user_notification_conf,base.group_system,1,1,1,1
|
||||
|
|
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.rule" id="partner_notification_conf_owner">
|
||||
<field name="name">Partners can edit their own notification settings</field>
|
||||
<field name="model_id" ref="model_partner_notification_conf"/>
|
||||
<record model="ir.rule" id="user_notification_conf_owner">
|
||||
<field name="name">Users can edit their own notification settings</field>
|
||||
<field name="model_id" ref="model_user_notification_conf"/>
|
||||
<field name="perm_read" eval="False"/>
|
||||
<field name="perm_create" eval="False"/>
|
||||
<field name="perm_write" eval="True"/>
|
||||
<field name="perm_unlink" eval="True"/>
|
||||
<field name="domain_force">['|',('partner_id', '=', user.partner_id.id), ('create_uid', '=', user.id)]</field>
|
||||
<field name="domain_force">['|',('user_id', '=', user.id), ('create_uid', '=', user.id)]</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
<div id="mail_content">
|
||||
<h2>Hello,</h2>
|
||||
<div id="mail_inner_content">
|
||||
<t t-foreach="grouped_messages.iterkeys()" t-as="gkey">
|
||||
<t t-foreach="grouped_messages.keys()" t-as="gkey">
|
||||
<t t-set="messages" t-value="grouped_messages[gkey]" />
|
||||
<t t-foreach="messages" t-as="msg">
|
||||
<div style="margin:20px">
|
||||
|
|
|
@ -1,59 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests.common import SavepointCase
|
||||
from odoo import exceptions
|
||||
|
||||
|
||||
class DigestCase(TransactionCase):
|
||||
class DigestCase(SavepointCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DigestCase, self).setUp()
|
||||
self.partner_model = self.env['res.partner']
|
||||
self.message_model = self.env['mail.message']
|
||||
self.subtype_model = self.env['mail.message.subtype']
|
||||
self.digest_model = self.env['mail.digest']
|
||||
self.conf_model = self.env['partner.notification.conf']
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(DigestCase, cls).setUpClass()
|
||||
cls.message_model = cls.env['mail.message']
|
||||
cls.subtype_model = cls.env['mail.message.subtype']
|
||||
cls.digest_model = cls.env['mail.digest']
|
||||
cls.conf_model = cls.env['user.notification.conf']
|
||||
|
||||
self.partner1 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 1',
|
||||
'email': 'partner1@test.foo.com',
|
||||
})
|
||||
self.partner2 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 2',
|
||||
'email': 'partner2@test.foo.com',
|
||||
})
|
||||
self.partner3 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 3',
|
||||
'email': 'partner3@test.foo.com',
|
||||
})
|
||||
self.subtype1 = self.subtype_model.create({'name': 'Type 1'})
|
||||
self.subtype2 = self.subtype_model.create({'name': 'Type 2'})
|
||||
user_model = cls.env['res.users'].with_context(
|
||||
no_reset_password=True, tracking_disable=True)
|
||||
cls.user1 = user_model.create({
|
||||
'name': 'User 1',
|
||||
'login': 'testuser1',
|
||||
'email': 'testuser1@email.com',
|
||||
})
|
||||
cls.user2 = user_model.create({
|
||||
'name': 'User 2',
|
||||
'login': 'testuser2',
|
||||
'email': 'testuser2@email.com',
|
||||
})
|
||||
cls.user3 = user_model.create({
|
||||
'name': 'User 3',
|
||||
'login': 'testuser3',
|
||||
'email': 'testuser3@email.com',
|
||||
})
|
||||
cls.subtype1 = cls.subtype_model.create({'name': 'Type 1'})
|
||||
cls.subtype2 = cls.subtype_model.create({'name': 'Type 2'})
|
||||
|
||||
def test_get_or_create_digest(self):
|
||||
message1 = self.message_model.create({
|
||||
self.message_model.create({
|
||||
'body': 'My Body 1',
|
||||
'subtype_id': self.subtype1.id,
|
||||
})
|
||||
message2 = self.message_model.create({
|
||||
self.message_model.create({
|
||||
'body': 'My Body 2',
|
||||
'subtype_id': self.subtype2.id,
|
||||
})
|
||||
# 2 messages, 1 digest container
|
||||
dig1 = self.digest_model._get_or_create_by_partner(
|
||||
self.partner1, message1)
|
||||
dig2 = self.digest_model._get_or_create_by_partner(
|
||||
self.partner1, message2)
|
||||
dig1 = self.digest_model._get_or_create_by_user(self.user1)
|
||||
dig2 = self.digest_model._get_or_create_by_user(self.user1)
|
||||
self.assertEqual(dig1, dig2)
|
||||
|
||||
def test_create_or_update_digest(self):
|
||||
partners = self.partner_model
|
||||
partners |= self.partner1
|
||||
partners |= self.partner2
|
||||
partners = self.env['res.partner']
|
||||
partners |= self.user1.partner_id
|
||||
partners |= self.user2.partner_id
|
||||
message1 = self.message_model.create({
|
||||
'body': 'My Body 1',
|
||||
'subtype_id': self.subtype1.id,
|
||||
|
@ -63,72 +62,75 @@ class DigestCase(TransactionCase):
|
|||
'subtype_id': self.subtype2.id,
|
||||
})
|
||||
# partner 1
|
||||
self.digest_model.create_or_update(self.partner1, message1)
|
||||
self.digest_model.create_or_update(self.partner1, message2)
|
||||
p1dig = self.digest_model._get_or_create_by_partner(self.partner1)
|
||||
self.digest_model.create_or_update(self.user1.partner_id, message1)
|
||||
self.digest_model.create_or_update(self.user1.partner_id, message2)
|
||||
p1dig = self.digest_model._get_or_create_by_user(self.user1)
|
||||
self.assertIn(message1, p1dig.message_ids)
|
||||
self.assertIn(message2, p1dig.message_ids)
|
||||
# partner 2
|
||||
self.digest_model.create_or_update(self.partner2, message1)
|
||||
self.digest_model.create_or_update(self.partner2, message2)
|
||||
p2dig = self.digest_model._get_or_create_by_partner(self.partner2)
|
||||
self.digest_model.create_or_update(self.user2.partner_id, message1)
|
||||
self.digest_model.create_or_update(self.user2.partner_id, message2)
|
||||
p2dig = self.digest_model._get_or_create_by_user(self.user2)
|
||||
self.assertIn(message1, p2dig.message_ids)
|
||||
self.assertIn(message2, p2dig.message_ids)
|
||||
|
||||
def test_notify_partner_digest(self):
|
||||
def test_notify_user_digest(self):
|
||||
message = self.message_model.create({
|
||||
'body': 'My Body 1',
|
||||
'subtype_id': self.subtype1.id,
|
||||
})
|
||||
self.partner1.notify_email = 'digest'
|
||||
self.user1.digest_mode = True
|
||||
# notify partner
|
||||
self.partner1._notify(message)
|
||||
self.user1.partner_id._notify(message)
|
||||
# we should find the message in its digest
|
||||
dig1 = self.digest_model._get_or_create_by_partner(
|
||||
self.partner1, message)
|
||||
dig1 = self.digest_model._get_or_create_by_user(self.user1)
|
||||
self.assertIn(message, dig1.message_ids)
|
||||
|
||||
def test_notify_partner_digest_followers(self):
|
||||
self.partner3.message_subscribe(self.partner2.ids)
|
||||
self.partner1.notify_email = 'digest'
|
||||
self.partner2.notify_email = 'digest'
|
||||
partners = self.partner1 + self.partner2
|
||||
# subscribe a partner to the other one
|
||||
self.user3.partner_id.message_subscribe(
|
||||
partner_ids=self.user2.partner_id.ids)
|
||||
self.user1.digest_mode = True
|
||||
self.user2.digest_mode = True
|
||||
partners = self.user1.partner_id + self.user2.partner_id
|
||||
message = self.message_model.create({
|
||||
'body': 'My Body 1',
|
||||
'subtype_id': self.subtype1.id,
|
||||
'res_id': self.partner3.id,
|
||||
'res_id': self.user3.partner_id.id,
|
||||
'model': 'res.partner',
|
||||
'partner_ids': [(4, self.partner1.id)]
|
||||
'partner_ids': [(4, self.user1.partner_id.id)]
|
||||
})
|
||||
# notify partners
|
||||
partners._notify(message)
|
||||
partners.with_context(foo=1)._notify(message)
|
||||
# we should find the a digest for each partner
|
||||
dig1 = self.digest_model._get_by_partner(self.partner1)
|
||||
dig2 = self.digest_model._get_by_partner(self.partner2)
|
||||
dig1 = self.digest_model._get_by_user(self.user1)
|
||||
dig2 = self.digest_model._get_by_user(self.user2)
|
||||
# and the message in them
|
||||
self.assertIn(message, dig1.message_ids)
|
||||
self.assertIn(message, dig2.message_ids)
|
||||
# now we exclude followers
|
||||
dig1.unlink()
|
||||
dig2.unlink()
|
||||
partners.with_context(notify_only_recipients=1)._notify(message)
|
||||
partners.with_context(notify_only_recipients=True)._notify(message)
|
||||
# we should find the a digest for each partner
|
||||
self.assertTrue(self.digest_model._get_by_partner(self.partner1))
|
||||
self.assertFalse(self.digest_model._get_by_partner(self.partner2))
|
||||
self.assertTrue(self.digest_model._get_by_user(self.user1))
|
||||
self.assertFalse(self.digest_model._get_by_user(self.user2))
|
||||
|
||||
def test_global_conf(self):
|
||||
for k in ('email', 'comment', 'notification'):
|
||||
self.assertIn(k, self.partner1._digest_enabled_message_types())
|
||||
self.assertIn(
|
||||
k, self.env['res.partner']._digest_enabled_message_types())
|
||||
self.env['ir.config_parameter'].set_param(
|
||||
'mail_digest.enabled_message_types',
|
||||
'email,notification'
|
||||
)
|
||||
for k in ('email', 'notification'):
|
||||
self.assertIn(k, self.partner1._digest_enabled_message_types())
|
||||
self.assertIn(
|
||||
k, self.env['res.partner']._digest_enabled_message_types())
|
||||
self.assertNotIn(
|
||||
'comment', self.partner1._digest_enabled_message_types())
|
||||
'comment', self.env['res.partner']._digest_enabled_message_types())
|
||||
|
||||
def test_notify_partner_digest_global_disabled(self):
|
||||
def test_notify_user_digest_global_disabled(self):
|
||||
# change global conf
|
||||
self.env['ir.config_parameter'].set_param(
|
||||
'mail_digest.enabled_message_types',
|
||||
|
@ -140,46 +142,46 @@ class DigestCase(TransactionCase):
|
|||
# globally disabled type
|
||||
'message_type': 'notification',
|
||||
})
|
||||
self.partner1.notify_email = 'digest'
|
||||
self.user1.digest_mode = True
|
||||
# notify partner
|
||||
self.partner1._notify(message)
|
||||
self.user1.partner_id._notify(message)
|
||||
# we should not find any digest
|
||||
self.assertFalse(self.digest_model._get_by_partner(self.partner1))
|
||||
self.assertFalse(self.digest_model._get_by_user(self.user1))
|
||||
|
||||
def _create_for_partner(self, partner):
|
||||
messages = {}
|
||||
for type_id in (self.subtype1.id, self.subtype2.id):
|
||||
for k in xrange(1, 3):
|
||||
for k in range(1, 3):
|
||||
key = '{}_{}'.format(type_id, k)
|
||||
messages[key] = self.message_model.create({
|
||||
'subject': 'My Subject {}'.format(key),
|
||||
'body': 'My Body {}'.format(key),
|
||||
'subtype_id': type_id,
|
||||
})
|
||||
self.digest_model.create_or_update(
|
||||
partner, messages[key])
|
||||
return self.digest_model._get_or_create_by_partner(partner)
|
||||
self.digest_model.create_or_update(partner, messages[key])
|
||||
return self.digest_model._get_by_user(partner.real_user_id)
|
||||
|
||||
def test_digest_group_messages(self):
|
||||
dig = self._create_for_partner(self.partner1)
|
||||
dig = self._create_for_partner(self.user1.partner_id)
|
||||
grouped = dig._message_group_by()
|
||||
for type_id in (self.subtype1.id, self.subtype2.id):
|
||||
self.assertIn(type_id, grouped)
|
||||
self.assertEqual(len(grouped[type_id]), 2)
|
||||
|
||||
def test_digest_mail_values(self):
|
||||
dig = self._create_for_partner(self.partner1)
|
||||
dig = self._create_for_partner(self.user1.partner_id)
|
||||
values = dig._get_email_values()
|
||||
expected = ('recipient_ids', 'subject', 'body_html')
|
||||
for k in expected:
|
||||
self.assertIn(k, values)
|
||||
|
||||
self.assertEqual(self.env.user.company_id.email, values['email_from'])
|
||||
self.assertEqual([(4, self.partner1.id)], values['recipient_ids'])
|
||||
self.assertEqual(
|
||||
[(4, self.user1.partner_id.id)], values['recipient_ids'])
|
||||
|
||||
def test_digest_template(self):
|
||||
default = self.env.ref('mail_digest.default_digest_tmpl')
|
||||
dig = self._create_for_partner(self.partner1)
|
||||
dig = self._create_for_partner(self.user1.partner_id)
|
||||
# check default
|
||||
self.assertEqual(dig.digest_template_id, default)
|
||||
self.assertTrue(dig._get_email_values())
|
||||
|
|
|
@ -1,38 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class PartnerDomainCase(TransactionCase):
|
||||
class PartnerDomainCase(SavepointCase):
|
||||
|
||||
def setUp(self):
|
||||
super(PartnerDomainCase, self).setUp()
|
||||
self.partner_model = self.env['res.partner']
|
||||
self.message_model = self.env['mail.message']
|
||||
self.subtype_model = self.env['mail.message.subtype']
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(PartnerDomainCase, cls).setUpClass()
|
||||
|
||||
self.partner1 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 1',
|
||||
'email': 'partner1@test.foo.com',
|
||||
})
|
||||
self.partner2 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 2',
|
||||
'email': 'partner2@test.foo.com',
|
||||
})
|
||||
self.partner3 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 3',
|
||||
'email': 'partner3@test.foo.com',
|
||||
})
|
||||
self.subtype1 = self.subtype_model.create({'name': 'Type 1'})
|
||||
self.subtype2 = self.subtype_model.create({'name': 'Type 2'})
|
||||
cls.partner_model = cls.env['res.partner']
|
||||
cls.message_model = cls.env['mail.message']
|
||||
cls.subtype_model = cls.env['mail.message.subtype']
|
||||
|
||||
def _assert_found(self, domain, not_found=False, partner=None):
|
||||
partner = partner or self.partner1
|
||||
user_model = cls.env['res.users'].with_context(
|
||||
no_reset_password=True, tracking_disable=True)
|
||||
cls.user1 = user_model.create({
|
||||
'name': 'User 1',
|
||||
'login': 'testuser1',
|
||||
'email': 'testuser1@email.com',
|
||||
})
|
||||
cls.user2 = user_model.create({
|
||||
'name': 'User 2',
|
||||
'login': 'testuser2',
|
||||
'email': 'testuser2@email.com',
|
||||
})
|
||||
cls.user3 = user_model.create({
|
||||
'name': 'User 3',
|
||||
'login': 'testuser3',
|
||||
'email': 'testuser3@email.com',
|
||||
})
|
||||
cls.partner1 = cls.user1.partner_id
|
||||
cls.partner2 = cls.user2.partner_id
|
||||
cls.partner3 = cls.user3.partner_id
|
||||
|
||||
cls.subtype1 = cls.subtype_model.create({'name': 'Type 1'})
|
||||
cls.subtype2 = cls.subtype_model.create({'name': 'Type 2'})
|
||||
|
||||
def _assert_found(self, partner, domain, not_found=False):
|
||||
if not_found:
|
||||
self.assertNotIn(partner, self.partner_model.search(domain))
|
||||
else:
|
||||
|
@ -43,17 +49,17 @@ class PartnerDomainCase(TransactionCase):
|
|||
# because we call `_get_notify_by_email_domain` directly
|
||||
message = self.message_model.create({'body': 'My Body', })
|
||||
partner = self.partner1
|
||||
partner.notify_email = 'always'
|
||||
partner.real_user_id.notification_type = 'email'
|
||||
domain = partner._get_notify_by_email_domain(message)
|
||||
self._assert_found(domain)
|
||||
domain = partner._get_notify_by_email_domain(message, digest=1)
|
||||
self._assert_found(domain, not_found=1)
|
||||
self._assert_found(partner, domain)
|
||||
domain = partner._get_notify_by_email_domain(message, digest=True)
|
||||
self._assert_found(partner, domain, not_found=True)
|
||||
|
||||
def test_notify_domains_only_recipients(self):
|
||||
# we don't set recipients
|
||||
# because we call `_get_notify_by_email_domain` directly
|
||||
self.partner1.notify_email = 'always'
|
||||
self.partner2.notify_email = 'always'
|
||||
self.partner1.real_user_id.notification_type = 'email'
|
||||
self.partner2.real_user_id.notification_type = 'email'
|
||||
partners = self.partner1 + self.partner2
|
||||
# followers
|
||||
self.partner3.message_subscribe(self.partner2.ids)
|
||||
|
@ -66,42 +72,47 @@ class PartnerDomainCase(TransactionCase):
|
|||
})
|
||||
domain = partners._get_notify_by_email_domain(message)
|
||||
# we find both of them since partner2 is a follower
|
||||
self._assert_found(domain)
|
||||
self._assert_found(domain, partner=self.partner2)
|
||||
self._assert_found(self.partner1, domain)
|
||||
self._assert_found(self.partner2, domain)
|
||||
# no one here in digest mode
|
||||
domain = partners._get_notify_by_email_domain(message, digest=1)
|
||||
self._assert_found(domain, not_found=1)
|
||||
self._assert_found(domain, not_found=1, partner=self.partner2)
|
||||
domain = partners._get_notify_by_email_domain(message, digest=True)
|
||||
self._assert_found(self.partner1, domain, not_found=True)
|
||||
self._assert_found(self.partner2, domain, not_found=True)
|
||||
|
||||
# include only recipients
|
||||
domain = partners.with_context(
|
||||
notify_only_recipients=1)._get_notify_by_email_domain(message)
|
||||
self._assert_found(domain)
|
||||
self._assert_found(domain, partner=self.partner2, not_found=1)
|
||||
self._assert_found(self.partner1, domain)
|
||||
self._assert_found(self.partner2, domain, not_found=True)
|
||||
|
||||
def test_notify_domains_digest(self):
|
||||
# we don't set recipients
|
||||
# because we call `_get_notify_by_email_domain` directly
|
||||
message = self.message_model.create({'body': 'My Body', })
|
||||
partner = self.partner1
|
||||
partner.notify_email = 'digest'
|
||||
partner.real_user_id.write({
|
||||
'notification_type': 'email',
|
||||
'digest_mode': True,
|
||||
})
|
||||
domain = partner._get_notify_by_email_domain(message)
|
||||
self._assert_found(domain, not_found=1)
|
||||
domain = partner._get_notify_by_email_domain(message, digest=1)
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain, not_found=True)
|
||||
domain = partner._get_notify_by_email_domain(message, digest=True)
|
||||
self._assert_found(partner, domain)
|
||||
|
||||
def test_notify_domains_none(self):
|
||||
message = self.message_model.create({'body': 'My Body', })
|
||||
partner = self.partner1
|
||||
partner.notify_email = 'none'
|
||||
partner.real_user_id.write({
|
||||
'notification_type': 'inbox',
|
||||
})
|
||||
domain = partner._get_notify_by_email_domain(message)
|
||||
self._assert_found(domain, not_found=1)
|
||||
domain = partner._get_notify_by_email_domain(message, digest=1)
|
||||
self._assert_found(domain, not_found=1)
|
||||
self._assert_found(partner, domain, not_found=True)
|
||||
domain = partner._get_notify_by_email_domain(message, digest=True)
|
||||
self._assert_found(partner, domain, not_found=True)
|
||||
|
||||
def test_notify_domains_match_type_digest(self):
|
||||
# Test message subtype matches partner settings.
|
||||
# The partner can have several `partner.notification.conf` records.
|
||||
# The partner can have several `user.notification.conf` records.
|
||||
# Each records establish notification rules by type.
|
||||
# If you don't have any record in it, you allow all subtypes.
|
||||
# Record `typeX` with `enable=True` enables notification for `typeX`.
|
||||
|
@ -109,7 +120,10 @@ class PartnerDomainCase(TransactionCase):
|
|||
|
||||
partner = self.partner1
|
||||
# enable digest
|
||||
partner.notify_email = 'digest'
|
||||
partner.real_user_id.write({
|
||||
'notification_type': 'email',
|
||||
'digest_mode': True,
|
||||
})
|
||||
message_t1 = self.message_model.create({
|
||||
'body': 'My Body',
|
||||
'subtype_id': self.subtype1.id,
|
||||
|
@ -119,25 +133,25 @@ class PartnerDomainCase(TransactionCase):
|
|||
'subtype_id': self.subtype2.id,
|
||||
})
|
||||
# enable subtype on partner
|
||||
partner._notify_enable_subtype(self.subtype1)
|
||||
partner.real_user_id._notify_enable_subtype(self.subtype1)
|
||||
domain = partner._get_notify_by_email_domain(
|
||||
message_t1, digest=True)
|
||||
# notification enabled: we find the partner.
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain)
|
||||
# for subtype2 we don't have any explicit rule: we find the partner
|
||||
domain = partner._get_notify_by_email_domain(
|
||||
message_t2, digest=True)
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain)
|
||||
# enable subtype2: find the partner anyway
|
||||
partner._notify_enable_subtype(self.subtype2)
|
||||
partner.real_user_id._notify_enable_subtype(self.subtype2)
|
||||
domain = partner._get_notify_by_email_domain(
|
||||
message_t2, digest=True)
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain)
|
||||
# disable subtype2: we don't find the partner anymore
|
||||
partner._notify_disable_subtype(self.subtype2)
|
||||
partner.real_user_id._notify_disable_subtype(self.subtype2)
|
||||
domain = partner._get_notify_by_email_domain(
|
||||
message_t2, digest=True)
|
||||
self._assert_found(domain, not_found=1)
|
||||
self._assert_found(partner, domain, not_found=True)
|
||||
|
||||
def test_notify_domains_match_type_always(self):
|
||||
message_t1 = self.message_model.create({
|
||||
|
@ -150,20 +164,20 @@ class PartnerDomainCase(TransactionCase):
|
|||
})
|
||||
# enable always
|
||||
partner = self.partner1
|
||||
partner.notify_email = 'always'
|
||||
partner.real_user_id.notification_type = 'email'
|
||||
# enable subtype on partner
|
||||
partner._notify_enable_subtype(self.subtype1)
|
||||
partner.real_user_id._notify_enable_subtype(self.subtype1)
|
||||
domain = partner._get_notify_by_email_domain(message_t1)
|
||||
# notification enabled: we find the partner.
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain)
|
||||
# for subtype2 we don't have any explicit rule: we find the partner
|
||||
domain = partner._get_notify_by_email_domain(message_t2)
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain)
|
||||
# enable subtype2: find the partner anyway
|
||||
partner._notify_enable_subtype(self.subtype2)
|
||||
partner.real_user_id._notify_enable_subtype(self.subtype2)
|
||||
domain = partner._get_notify_by_email_domain(message_t2)
|
||||
self._assert_found(domain)
|
||||
self._assert_found(partner, domain)
|
||||
# disable subtype2: we don't find the partner anymore
|
||||
partner._notify_disable_subtype(self.subtype2)
|
||||
partner.real_user_id._notify_disable_subtype(self.subtype2)
|
||||
domain = partner._get_notify_by_email_domain(message_t2)
|
||||
self._assert_found(domain, not_found=1)
|
||||
self._assert_found(partner, domain, not_found=True)
|
||||
|
|
|
@ -1,136 +1,109 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
|
||||
# Copyright 2017-2018 Camptocamp - Simone Orsi
|
||||
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
|
||||
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.tests.common import SavepointCase
|
||||
|
||||
|
||||
class SubtypesCase(TransactionCase):
|
||||
class SubtypesCase(SavepointCase):
|
||||
|
||||
def setUp(self):
|
||||
super(SubtypesCase, self).setUp()
|
||||
self.partner_model = self.env['res.partner']
|
||||
self.message_model = self.env['mail.message']
|
||||
self.subtype_model = self.env['mail.message.subtype']
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(SubtypesCase, cls).setUpClass()
|
||||
|
||||
self.partner1 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 1!',
|
||||
'email': 'partner1@test.foo.com',
|
||||
})
|
||||
self.partner2 = self.partner_model.with_context(
|
||||
tracking_disable=1).create({
|
||||
'name': 'Partner 2!',
|
||||
'email': 'partner2@test.foo.com',
|
||||
})
|
||||
self.subtype1 = self.subtype_model.create({'name': 'Type 1'})
|
||||
self.subtype2 = self.subtype_model.create({'name': 'Type 2'})
|
||||
user_model = cls.env['res.users'].with_context(
|
||||
no_reset_password=True, tracking_disable=True)
|
||||
cls.user1 = user_model.create({
|
||||
'name': 'User 1',
|
||||
'login': 'testuser1',
|
||||
'email': 'testuser1@email.com',
|
||||
})
|
||||
cls.user2 = user_model.create({
|
||||
'name': 'User 2',
|
||||
'login': 'testuser2',
|
||||
'email': 'testuser2@email.com',
|
||||
})
|
||||
subtype_model = cls.env['mail.message.subtype']
|
||||
cls.subtype1 = subtype_model.create({'name': 'Type 1'})
|
||||
cls.subtype2 = subtype_model.create({'name': 'Type 2'})
|
||||
cls.subtype3 = subtype_model.create({'name': 'Type 3'})
|
||||
cls.subtype4 = subtype_model.create({'name': 'Type 4'})
|
||||
|
||||
def _test_subtypes_rel(self):
|
||||
# setup:
|
||||
# t1, t2 enabled
|
||||
# t3 disabled
|
||||
# t4 no conf
|
||||
self.subtype3 = self.subtype_model.create({'name': 'Type 3'})
|
||||
self.subtype4 = self.subtype_model.create({'name': 'Type 4'})
|
||||
# enable t1 t2
|
||||
self.partner1._notify_enable_subtype(self.subtype1)
|
||||
self.partner1._notify_enable_subtype(self.subtype2)
|
||||
self.user1._notify_enable_subtype(self.subtype1)
|
||||
self.user1._notify_enable_subtype(self.subtype2)
|
||||
# disable t3
|
||||
self.partner1._notify_disable_subtype(self.subtype3)
|
||||
self.user1._notify_disable_subtype(self.subtype3)
|
||||
|
||||
def test_partner_computed_subtype(self):
|
||||
def test_user_computed_subtype(self):
|
||||
self._test_subtypes_rel()
|
||||
# check computed fields
|
||||
self.assertIn(
|
||||
self.subtype1, self.partner1.enabled_notify_subtype_ids)
|
||||
self.subtype1, self.user1.enabled_notify_subtype_ids)
|
||||
self.assertNotIn(
|
||||
self.subtype1, self.partner1.disabled_notify_subtype_ids)
|
||||
self.subtype1, self.user1.disabled_notify_subtype_ids)
|
||||
self.assertIn(
|
||||
self.subtype2, self.partner1.enabled_notify_subtype_ids)
|
||||
self.subtype2, self.user1.enabled_notify_subtype_ids)
|
||||
self.assertNotIn(
|
||||
self.subtype2, self.partner1.disabled_notify_subtype_ids)
|
||||
self.subtype2, self.user1.disabled_notify_subtype_ids)
|
||||
self.assertIn(
|
||||
self.subtype3, self.partner1.disabled_notify_subtype_ids)
|
||||
self.subtype3, self.user1.disabled_notify_subtype_ids)
|
||||
self.assertNotIn(
|
||||
self.subtype3, self.partner1.enabled_notify_subtype_ids)
|
||||
self.subtype3, self.user1.enabled_notify_subtype_ids)
|
||||
self.assertNotIn(
|
||||
self.subtype4,
|
||||
self.partner1.enabled_notify_subtype_ids)
|
||||
self.user1.enabled_notify_subtype_ids)
|
||||
self.assertNotIn(
|
||||
self.subtype4,
|
||||
self.partner1.disabled_notify_subtype_ids)
|
||||
self.user1.disabled_notify_subtype_ids)
|
||||
|
||||
def test_partner_find_by_subtype_incl(self):
|
||||
def test_find_user_by_subtype_incl(self):
|
||||
self._test_subtypes_rel()
|
||||
domain = [(
|
||||
'enabled_notify_subtype_ids',
|
||||
'in', (self.subtype1.id, self.subtype2.id),
|
||||
)]
|
||||
self.assertIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'disabled_notify_subtype_ids', 'in', self.subtype3.id,
|
||||
)]
|
||||
self.assertIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'enabled_notify_subtype_ids', 'in', (self.subtype3.id, ),
|
||||
)]
|
||||
self.assertNotIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertNotIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'enabled_notify_subtype_ids', 'in', (self.subtype4.id, ),
|
||||
)]
|
||||
self.assertNotIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertNotIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'disabled_notify_subtype_ids', 'in', (self.subtype4.id, ),
|
||||
)]
|
||||
self.assertNotIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertNotIn(self.user1, self.env['res.users'].search(domain))
|
||||
|
||||
def test_partner_find_by_subtype_escl(self):
|
||||
def test_find_user_by_subtype_escl(self):
|
||||
self._test_subtypes_rel()
|
||||
domain = [(
|
||||
'enabled_notify_subtype_ids',
|
||||
'not in', (self.subtype4.id, ),
|
||||
)]
|
||||
self.assertIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'disabled_notify_subtype_ids',
|
||||
'not in', (self.subtype4.id, ),
|
||||
)]
|
||||
self.assertIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'enabled_notify_subtype_ids',
|
||||
'not in', (self.subtype3.id, ),
|
||||
)]
|
||||
self.assertIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertIn(self.user1, self.env['res.users'].search(domain))
|
||||
domain = [(
|
||||
'disabled_notify_subtype_ids',
|
||||
'not in', (self.subtype1.id, self.subtype2.id),
|
||||
)]
|
||||
self.assertIn(
|
||||
self.partner1,
|
||||
self.partner_model.search(domain)
|
||||
)
|
||||
self.assertIn(self.user1, self.env['res.users'].search(domain))
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<field name="model">mail.digest</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mail digest">
|
||||
<field name="partner_id" />
|
||||
<field name="user_id" />
|
||||
<field name="mail_id" />
|
||||
<field name="state" />
|
||||
</tree>
|
||||
|
@ -27,7 +27,7 @@
|
|||
</div>
|
||||
<group name="main" col="2">
|
||||
<field name="name" />
|
||||
<field name="partner_id" />
|
||||
<field name="user_id" />
|
||||
</group>
|
||||
<group name="settings" col="2">
|
||||
<field name="frequency" />
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
<record id="notifications_emails_partner_info_form" model="ir.ui.view">
|
||||
<field name="name">mail.notifications res.partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="mail.view_emails_partner_info_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notify_email']" position="after">
|
||||
<field name="notify_conf_ids" attrs="{'invisible': [('notify_email','=', 'none')]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="notification_form">
|
||||
<field name="name">partner.notification.conf form</field>
|
||||
<field name="model">partner.notification.conf</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Notification">
|
||||
<group name="main">
|
||||
<field name="enabled" />
|
||||
<field name="subtype_id" options="{'no_create': True}" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="notification_tree">
|
||||
<field name="name">partner.notification.conf tree</field>
|
||||
<field name="model">partner.notification.conf</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Notifications" editable="top">
|
||||
<field name="enabled" />
|
||||
<field name="subtype_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -0,0 +1,28 @@
|
|||
<?xml version="1.0"?>
|
||||
<odoo>
|
||||
|
||||
<record model="ir.ui.view" id="notification_form">
|
||||
<field name="name">user.notification.conf form</field>
|
||||
<field name="model">user.notification.conf</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Notification">
|
||||
<group name="main">
|
||||
<field name="enabled" />
|
||||
<field name="subtype_id" options="{'no_create': True}" />
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="notification_tree">
|
||||
<field name="name">user.notification.conf tree</field>
|
||||
<field name="model">user.notification.conf</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Notifications" editable="top">
|
||||
<field name="enabled" />
|
||||
<field name="subtype_id" />
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
|
@ -5,13 +5,14 @@
|
|||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="mail.view_users_form_mail"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="notify_email" position="replace">
|
||||
<field name="notification_type" position="replace">
|
||||
<group name="notif_left">
|
||||
<field name="notify_email" widget="radio" readonly="0"/>
|
||||
<field name="notify_frequency" readonly="0"
|
||||
attrs="{'invisible': [('notify_email', '!=', 'digest')]}" />
|
||||
<field name="notification_type" widget="radio" readonly="0"/>
|
||||
<field name="digest_mode" />
|
||||
<field name="digest_frequency" readonly="0"
|
||||
attrs="{'invisible': [('digest_mode', '=', False)]}" />
|
||||
</group>
|
||||
<group name="notif_right" attrs="{'invisible': [('notify_email','=', 'none')]}">
|
||||
<group name="notif_right" attrs="{'invisible': [('notification_type','=', 'inbox')]}">
|
||||
<label string="Enable/disable notifications by type" colspan="4" />
|
||||
<field name="notify_conf_ids" nolabel="1" colspan="4" readonly="0" />
|
||||
</group>
|
||||
|
@ -25,17 +26,19 @@
|
|||
<field name="model">res.users</field>
|
||||
<field name="inherit_id" ref="mail.view_users_form_simple_modif_mail"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="notify_email" position="replace">
|
||||
<field name="notification_type" position="replace">
|
||||
<group name="notif_left">
|
||||
<field name="notify_email" widget="radio" readonly="0"/>
|
||||
<field name="notify_frequency" readonly="0"
|
||||
attrs="{'invisible': [('notify_email', '!=', 'digest')]}" />
|
||||
<field name="digest_mode" />
|
||||
<field name="notification_type" widget="radio" readonly="0"/>
|
||||
<field name="digest_frequency" readonly="0"
|
||||
attrs="{'invisible': [('digest_mode', '=', False)]}" />
|
||||
</group>
|
||||
<group name="notif_right" attrs="{'invisible': [('notify_email','=', 'none')]}">
|
||||
<group name="notif_right" attrs="{'invisible': [('notification_type','=', 'inbox')]}">
|
||||
<label string="Enable/disable notifications by type" colspan="4" />
|
||||
<field name="notify_conf_ids" nolabel="1" colspan="4" readonly="0" />
|
||||
</group>
|
||||
</field>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
Loading…
Reference in New Issue