Browse Source

Merge pull request #273 from simahawk/11-imp-mail_digest

[11.0] mail_digest: fwd port improvements from v10
pull/294/head
Pedro M. Baeza 6 years ago
committed by GitHub
parent
commit
34f65e9c11
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      mail_digest/README.rst
  2. 1
      mail_digest/__init__.py
  3. 3
      mail_digest/__manifest__.py
  4. 1
      mail_digest/controllers/__init__.py
  5. 139
      mail_digest/controllers/digest_layout_preview.py
  6. BIN
      mail_digest/images/digest_layout_preview.png
  7. 24
      mail_digest/models/mail_digest.py
  8. 35
      mail_digest/templates/digest_default.xml
  9. 18
      mail_digest/templates/digest_layout_preview.xml
  10. 1
      mail_digest/tests/__init__.py
  11. 29
      mail_digest/tests/test_digest.py
  12. 62
      mail_digest/tests/test_preview.py

8
mail_digest/README.rst

@ -65,6 +65,14 @@ NOTE: under the hood the digest notification logic excludes followers to be noti
since you really want to notify only mail.digest's partner.
Digest rendering preview
------------------------
You can check how messages are formatted per each mail subtype by going to `/digest/layout-preview` in your browser.
.. image:: ./images/digest_layout_preview.png
Known issues / Roadmap
======================

1
mail_digest/__init__.py

@ -1 +1,2 @@
from . import controllers
from . import models

3
mail_digest/__manifest__.py

@ -4,7 +4,7 @@
{
'name': 'Mail digest',
'summary': """Basic digest mail handling.""",
'version': '11.0.1.0.2',
'version': '11.0.1.1.0',
'license': 'AGPL-3',
'author': 'Camptocamp, Odoo Community Association (OCA)',
'website': 'https://github.com/OCA/social',
@ -20,6 +20,7 @@
'views/user_notification_views.xml',
'views/user_views.xml',
'templates/digest_default.xml',
'templates/digest_layout_preview.xml',
],
'images': [
'static/description/preview.png',

1
mail_digest/controllers/__init__.py

@ -0,0 +1 @@
from . import digest_layout_preview

139
mail_digest/controllers/digest_layout_preview.py

@ -0,0 +1,139 @@
# Copyright 2018 Simone Orsi <simone.orsi@camptocamp.com>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
from odoo import http
from odoo.http import request
import random
FAKE_NAMES = [
'Madison Castillo',
'Destiny Frost',
'Dennis Parrish',
'Christy Moore',
'Larry James',
'David Simmons',
'Dr. Francis Ramos',
'Michelle Williams',
'Allison Montgomery',
'Michelle Rodriguez',
'Gina Patel',
'Corey Ray',
'Brent Myers',
'Sydney Hicks',
'Austin Buckley',
'Patricia Jones DDS',
'Dylan Davila',
'Christopher Bolton',
'James Cline',
'Gary Johnson',
'Jennifer Reese',
'Kevin Davis',
'Sandra Robinson',
'Sara Warner',
'Jaime Dunn',
'Mark Austin',
'Kendra Nelson',
'Matthew White',
'Rebecca Berger',
'Amanda Thornton',
'Lorraine Schultz',
'Chelsea Daniel',
'Kayla Jackson',
'Melanie Grant',
'Oscar Jones',
'Jon Sanchez',
'Kevin Anderson',
'Yvonne Mullen',
'Jonathan King',
'Wendy Hernandez'
]
FAKE_NUMBERS = range(1, 30)
class DigestPreview(http.Controller):
digest_test_template = 'mail_digest.digest_layout_preview'
@http.route([
'/digest/layout-preview',
], type='http', auth='user')
def digest_test(self):
digest = self._fake_digest()
mail_values = digest._get_email_values()
values = {
'env': request.env,
'digest_html': mail_values['body_html'],
}
return request.render(self.digest_test_template, values)
def _fake_digest(self):
user = request.env.user
digest_model = request.env['mail.digest'].sudo()
digest = digest_model.new()
digest.partner_id = user.partner_id
digest.digest_template_id = digest._default_digest_template_id()
digest.message_ids = self._fake_messages()
digest.sanitize_msg_body = True
return digest
def _fake_messages(self):
messages = request.env['mail.message'].sudo()
subtype_model = request.env['mail.message.subtype'].sudo()
subtypes = subtype_model.search([])
records = request.env['res.partner'].sudo().search([])
# TODO: filter subtypes?
for i, subtype in enumerate(subtypes):
# generate a couple of messages for each type
for x in range(1, 3):
msg = messages.new()
msg.subtype_id = subtype
subject, body = self._fake_content(subtype, i, x)
msg.subject = subject
msg.message_type = random.choice(
('email', 'comment', 'notification'))
msg.email_from = 'random@user%d.com' % i
msg.partner_ids = [(6, 0, request.env.user.partner_id.ids)]
if i + x % 2 == 0:
# relate a document
msg.model = records._name
msg.res_id = random.choice(records.ids)
# simulate messages w/ no body but tracking values
if x == random.choice([1, 2]):
msg.tracking_value_ids = self._fake_tracking_vals()
else:
msg.body = body
messages += msg
return messages
def _fake_content(self, subtype, i, x):
subject = 'Lorem ipsum %d / %d' % (i, x)
body = 'Random text here lorem ipsum %d / %d' % (i, x)
if i % 2 == 0 and x > 1:
# simulate also random styles that are goin to be stripped
body = """
<p style="font-size: 13px; font-family: &quot;Lucida Grande&quot;, Helvetica, Verdana, Arial, sans-serif; margin: 0px 0px 9px 0px">Lorem ipsum dolor sit amet, cetero menandri mel id.</p>
<p>Ad modus tantas qui, quo choro facete delicata te.
Epicurei accusata vix eu, prima erant graeci sit te,
vivendum molestiae an mel.</p>
<p>Sed apeirian atomorum id, no ius possit antiopam molestiae.</p>
""" # noqa
return subject, body.strip()
def _fake_tracking_vals(self):
tracking_model = request.env['mail.tracking.value'].sudo()
track_vals1 = tracking_model.create_tracking_values(
random.choice(FAKE_NAMES), random.choice(FAKE_NAMES),
'name', {'type': 'char', 'string': 'Name'},
)
track_vals2 = tracking_model.create_tracking_values(
random.choice(FAKE_NUMBERS), random.choice(FAKE_NUMBERS),
'count', {'type': 'integer', 'string': 'Count'},
)
return [
(0, 0, track_vals1),
(0, 0, track_vals2),
]

BIN
mail_digest/images/digest_layout_preview.png

After

Width: 691  |  Height: 1022  |  Size: 69 KiB

24
mail_digest/models/mail_digest.py

@ -1,8 +1,7 @@
# 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, _
from odoo import fields, models, api, exceptions, tools, _
import logging
logger = logging.getLogger('[mail_digest]')
@ -52,6 +51,14 @@ class MailDigest(models.Model):
default=lambda self: self._default_digest_template_id(),
domain=[('type', '=', 'qweb')],
)
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."""
@ -121,6 +128,19 @@ class MailDigest(models.Model):
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.

35
mail_digest/templates/digest_default.xml

@ -24,14 +24,39 @@
<template id="default_digest_tmpl" name="Mail digest default template">
<t t-call="mail_digest.digest_layout">
<div id="mail_content">
<h2>Hello,</h2>
<p>Hello <t t-esc="digest.user_id.name" />,</p>
<div id="mail_inner_content">
<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">
<h3 t-esc="msg.subject" />
<t t-raw="msg.body" />
<t t-if="messages">
<div class="subtype-wrapper"
t-attf-style="marging-bottom:10px;padding-bottom:10px;#{not gkey_last and 'border-bottom:2px solid #aaa;margin-bottom:20px'}">
<t t-set="subtype" t-value="messages[0].subtype_id" />
<h3 style="margin-bottom:5px">
<span t-field="subtype.name" style="font-weight:bold" />
<t t-if="subtype.description">
<span t-field="subtype.description"
style="display:block;margin-top:10px;font-size:80%;color: #777"/>
</t>
</h3>
<t t-foreach="messages" t-as="msg">
<div class="message-wrapper"
t-attf-style="margin:0 10px;padding:5px 10px;#{not msg_first and 'border-top:1px solid #ccc'}">
<t t-raw="digest.message_body(msg)" />
<t t-if="msg.tracking_value_ids">
<h4>Changed values</h4>
<ul class="tracking-values">
<t t-foreach="msg.tracking_value_ids" t-as="tracked">
<li>
<strong class="fname"><span t-field="tracked.field_desc" />:</strong>
<span class="fvalue-old" t-esc="tracked.get_old_display_value()[0]" /> &#8594;
<span class="fvalue-new" t-esc="tracked.get_new_display_value()[0]" />
</li>
</t>
</ul>
</t>
</div>
</t>
</div>
</t>
</t>

18
mail_digest/templates/digest_layout_preview.xml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<template id="digest_layout_preview" name="Mail digest layout preview">
<style>
#digest_layout_wrapper {
max-width: 600px;
margin: 0 auto;
border: 1px dashed #ddd;
padding: 4em;
}
</style>
<div id="digest_layout_wrapper">
<t t-raw="digest_html" />
</div>
</template>
</odoo>

1
mail_digest/tests/__init__.py

@ -1,3 +1,4 @@
from . import test_digest
from . import test_partner_domains
from . import test_subtypes_conf
from . import test_preview

29
mail_digest/tests/test_digest.py

@ -192,3 +192,32 @@ class DigestCase(SavepointCase):
# raise error if no template found
with self.assertRaises(exceptions.UserError):
dig._get_email_values()
def test_digest_message_body_sanitize(self):
dig = self._create_for_partner(self.user1.partner_id)
message = self.message_model.create({
'body': '<p style="font-weight: bold">Body!</p>',
'subtype_id': self.subtype1.id,
'res_id': self.user3.partner_id.id,
'model': 'res.partner',
'partner_ids': [(4, self.user1.partner_id.id)]
})
body = dig.message_body(message)
self.assertEqual(body, '<p>Body!</p>')
def test_digest_message_body_no_sanitize(self):
dig = self._create_for_partner(self.user1.partner_id)
dig.sanitize_msg_body = False
message = self.message_model.create({
'body': '<p style="font-weight: bold">Body!</p>',
'subtype_id': self.subtype1.id,
'res_id': self.user3.partner_id.id,
'model': 'res.partner',
'partner_ids': [(4, self.user1.partner_id.id)]
})
body = dig.message_body(message)
self.assertEqual(
# prevent fail on weird behavior:
# sometimes you get a space, sometimes not :(
body.replace('font-weight: bold', 'font-weight:bold'),
'<p style="font-weight:bold">Body!</p>')

62
mail_digest/tests/test_preview.py

@ -0,0 +1,62 @@
# Copyright 2017 Simone Orsi <simone.orsi@camptocamp.com>
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
from odoo.tests.common import SavepointCase
import mock
from ..controllers.digest_layout_preview import DigestPreview
REQUEST_PATH = 'odoo.addons.mail_digest.controllers.digest_layout_preview'
class PreviewCase(SavepointCase):
"""Easy tests for preview controller to make codecov happy."""
@classmethod
def setUpClass(cls):
super(PreviewCase, cls).setUpClass()
cls.ctrl = DigestPreview()
@mock.patch(REQUEST_PATH + '.request')
def test_fake_digest(self, patched_req):
patched_req.env = self.env
digest = self.ctrl._fake_digest()
self.assertEqual(
digest.partner_id, self.env.user.partner_id,
)
self.assertEqual(
digest.digest_template_id, digest._default_digest_template_id(),
)
self.assertTrue(digest.message_ids)
self.assertTrue(digest.sanitize_msg_body)
@mock.patch(REQUEST_PATH + '.request')
def test_fake_messages(self, patched_req):
patched_req.env = self.env
all_types = self.env['mail.message.subtype'].search([])
messages = self.ctrl._fake_messages()
self.assertEqual(
len(messages), len(all_types) * 2
)
@mock.patch(REQUEST_PATH + '.request')
def test_fake_content(self, patched_req):
patched_req.env = self.env
subj, body = self.ctrl._fake_content(None, 1, 2)
body = 'Random text here lorem ipsum 1 / 2'
self.assertEqual(subj, 'Lorem ipsum 1 / 2')
self.assertEqual(body, 'Random text here lorem ipsum 1 / 2')
subj, body = self.ctrl._fake_content(None, 2, 2)
self.assertEqual(subj, 'Lorem ipsum 2 / 2')
self.assertTrue(body.startswith('<p style="font-size: 13px;'))
@mock.patch(REQUEST_PATH + '.request')
def test_fake_tracking_vals(self, patched_req):
patched_req.env = self.env
vals = self.ctrl._fake_tracking_vals()
self.assertEqual(len(vals), 2)
@mock.patch(REQUEST_PATH + '.request')
def test_render(self, patched_req):
patched_req.env = self.env
html = self.ctrl.digest_test()
self.assertTrue(html)
Loading…
Cancel
Save