[FW][FIX] digest: use email queue to send digest emails (and improve tests)
When there is an issue updating digest state (concurrent access, or some other error that may happen), digest emails may be sent in loop. Indeed they are currently sent in the same transaction that the one that updates digest state. This means that emails may be sent even if digest update fails. As we do not think timing is so important, we now use the email queue to send digest emails. That way email creation is rollbacked and they are not sent if digest update fails for some reason. Tests are updated to take into account we now create outgoing mail.mail. Some tests are also added about mail values and subscription, and cleaned in a more general way. Task-2641394 (Digest emails sending improvement) Task-2582128 (Digest onbarding and usage improvement) X-original-commit: 96a14816cb48d9324482eec3de0ed3fccc0d7ecf Part-of: odoo/odoo#79877
This commit is contained in:
@@ -156,14 +156,15 @@ class Digest(models.Model):
|
||||
)
|
||||
# create a mail_mail based on values, without attachments
|
||||
mail_values = {
|
||||
'subject': '%s: %s' % (user.company_id.name, self.name),
|
||||
'auto_delete': True,
|
||||
'author_id': self.env.user.partner_id.id,
|
||||
'email_from': self.company_id.partner_id.email_formatted if self.company_id else self.env.user.email_formatted,
|
||||
'email_to': user.email_formatted,
|
||||
'body_html': full_mail,
|
||||
'auto_delete': True,
|
||||
'state': 'outgoing',
|
||||
'subject': '%s: %s' % (user.company_id.name, self.name),
|
||||
}
|
||||
mail = self.env['mail.mail'].sudo().create(mail_values)
|
||||
mail.send(raise_exception=False)
|
||||
self.env['mail.mail'].sudo().create(mail_values)
|
||||
return True
|
||||
|
||||
@api.model
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import test_digest
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
import itertools
|
||||
import random
|
||||
|
||||
@@ -6,41 +9,37 @@ from lxml import html
|
||||
|
||||
from odoo import fields
|
||||
from odoo.addons.mail.tests import common as mail_test
|
||||
from odoo.tests.common import users
|
||||
|
||||
|
||||
class TestDigest(mail_test.MailCommon):
|
||||
def test_digest_numbers(self):
|
||||
self._setup_messages()
|
||||
|
||||
digest = self.env['digest.digest'].create({
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestDigest, cls).setUpClass()
|
||||
cls._activate_multi_company()
|
||||
|
||||
# clean messages
|
||||
cls.env['mail.message'].search([
|
||||
('subtype_id', '=', cls.env.ref('mail.mt_comment').id),
|
||||
('message_type', 'in', ['comment', 'email']),
|
||||
]).unlink()
|
||||
cls._setup_messages()
|
||||
|
||||
# clean demo users so that we keep only the test users
|
||||
cls.env['res.users'].search([('login', 'in', ['demo', 'portal'])]).action_archive()
|
||||
# clean logs so that town down is activated
|
||||
cls.env['res.users.log'].search([('create_uid', 'in', (cls.user_admin + cls.user_employee).ids)]).unlink()
|
||||
|
||||
cls.test_digest = cls.env['digest.digest'].create({
|
||||
'kpi_mail_message_total': True,
|
||||
'kpi_res_users_connected': True,
|
||||
'name': "My Digest",
|
||||
'kpi_mail_message_total': True
|
||||
'periodicity': 'daily',
|
||||
})
|
||||
|
||||
digest_user = digest.with_user(self.user_employee)
|
||||
# subscribe a user so at least one mail gets sent
|
||||
digest_user.action_subscribe()
|
||||
self.assertTrue(
|
||||
digest_user.is_subscribed,
|
||||
"check the user was subscribed as action_subscribe will silently "
|
||||
"ignore subs of non-employees"
|
||||
)
|
||||
|
||||
# digest creates its mails in auto_delete mode so we need to capture
|
||||
# the formatted body during the sending process
|
||||
with self.mock_mail_gateway():
|
||||
digest.action_send()
|
||||
|
||||
self.assertEqual(len(self._mails), 1, "a mail has been created for the digest")
|
||||
body = self._mails[0]['body']
|
||||
|
||||
kpi_message_values = html.fromstring(body).xpath('//div[@data-field="kpi_mail_message_total"]//*[hasclass("kpi_value")]/text()')
|
||||
self.assertEqual(
|
||||
[t.strip() for t in kpi_message_values],
|
||||
['3', '8', '15']
|
||||
)
|
||||
|
||||
def _setup_messages(self):
|
||||
@classmethod
|
||||
def _setup_messages(cls):
|
||||
""" Remove all existing messages, then create a bunch of them on random
|
||||
partners with the correct types in correct time-bucket:
|
||||
|
||||
@@ -50,25 +49,63 @@ class TestDigest(mail_test.MailCommon):
|
||||
based around weeks and months not days), for a total of 15 in the
|
||||
previous month
|
||||
"""
|
||||
self.env['mail.message'].search([]).unlink()
|
||||
now = fields.Datetime.now()
|
||||
# regular employee can't necessarily access "private" addresses
|
||||
partners = self.env['res.partner'].search([('type', '!=', 'private')])
|
||||
partners = cls.env['res.partner'].search([('type', '!=', 'private')])
|
||||
messages = cls.env['mail.message']
|
||||
counter = itertools.count()
|
||||
|
||||
# pylint: disable=bad-whitespace
|
||||
for count, (low, high) in [
|
||||
(3, (0 * 24, 1 * 24)),
|
||||
(5, (1 * 24, 7 * 24)),
|
||||
(7, (7 * 24, 27 * 24)),
|
||||
]:
|
||||
now = fields.Datetime.now()
|
||||
for count, (low, high) in [(3, (0 * 24, 1 * 24)),
|
||||
(5, (1 * 24, 7 * 24)),
|
||||
(7, (7 * 24, 27 * 24)),
|
||||
]:
|
||||
for _ in range(count):
|
||||
create_date = now - relativedelta(hours=random.randint(low + 1, high - 1))
|
||||
random.choice(partners).message_post(
|
||||
messages += random.choice(partners).message_post(
|
||||
author_id=cls.partner_admin.id,
|
||||
body=f"Awesome Partner! ({next(counter)})",
|
||||
email_from=cls.partner_admin.email_formatted,
|
||||
message_type='comment',
|
||||
subtype_xmlid='mail.mt_comment',
|
||||
# adjust top and bottom by 1h to avoid overlapping with the
|
||||
# range limit and dropping out of the digest's selection thing
|
||||
create_date=create_date
|
||||
create_date=create_date,
|
||||
)
|
||||
messages.flush()
|
||||
|
||||
@users('admin')
|
||||
def test_digest_numbers(self):
|
||||
digest = self.env['digest.digest'].browse(self.test_digest.ids)
|
||||
digest._action_subscribe_users(self.user_employee)
|
||||
|
||||
# digest creates its mails in auto_delete mode so we need to capture
|
||||
# the formatted body during the sending process
|
||||
digest.flush()
|
||||
with self.mock_mail_gateway():
|
||||
digest.action_send()
|
||||
|
||||
self.assertEqual(len(self._new_mails), 1, "A new mail.mail should have been created")
|
||||
mail = self._new_mails[0]
|
||||
# check mail.mail content
|
||||
self.assertEqual(mail.author_id, self.partner_admin)
|
||||
self.assertEqual(mail.email_from, self.company_admin.email_formatted)
|
||||
self.assertEqual(mail.state, 'outgoing', 'Mail should use the queue')
|
||||
|
||||
kpi_message_values = html.fromstring(mail.body_html).xpath('//div[@data-field="kpi_mail_message_total"]//*[hasclass("kpi_value")]/text()')
|
||||
self.assertEqual(
|
||||
[t.strip() for t in kpi_message_values],
|
||||
['3', '8', '15']
|
||||
)
|
||||
|
||||
@users('admin')
|
||||
def test_digest_subscribe(self):
|
||||
digest_user = self.test_digest.with_user(self.user_employee)
|
||||
self.assertFalse(digest_user.is_subscribed)
|
||||
|
||||
# subscribe a user so at least one mail gets sent
|
||||
digest_user.action_subscribe()
|
||||
self.assertTrue(
|
||||
digest_user.is_subscribed,
|
||||
"check the user was subscribed as action_subscribe will silently "
|
||||
"ignore subs of non-employees"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user