Merge notification email logs into email archives

This commit is contained in:
Cédric Krier
2023-02-09 18:50:05 +01:00
parent ffc6f35442
commit 0440145051
11 changed files with 133 additions and 201 deletions
+1
View File
@@ -1,3 +1,4 @@
* Merge email logs into email archives
* Remove support for Python 3.7
* Add support for Python 3.11
+1 -1
View File
@@ -8,7 +8,7 @@ from . import ir, notification
def register():
Pool.register(
ir.Trigger,
ir.Email,
notification.Email,
notification.EmailAttachment,
notification.EmailLog,
module='notification_email', type_='model')
+53
View File
@@ -1,8 +1,13 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from sql import Table
from trytond import backend
from trytond.model import fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
from trytond.transaction import Transaction
class Trigger(metaclass=PoolMeta):
@@ -43,3 +48,51 @@ class Trigger(metaclass=PoolMeta):
else:
self.model = trigger_model
self.action = 'notification.email|trigger'
class Email(metaclass=PoolMeta):
__name__ = 'ir.email'
notification_email = fields.Many2One(
'notification.email', "Notification Email", readonly=True,
states={
'invisible': ~Eval('notification_email'),
})
notification_trigger = fields.Many2One(
'ir.trigger', "Notification Trigger", readonly=True,
states={
'invisible': ~Eval('notification_trigger'),
})
@classmethod
def __register__(cls, module):
table = cls.__table__()
log = Table('notification_email_log')
cursor = Transaction().connection.cursor()
super().__register__(module)
# Migration from 6.6: merge notification email log with email
if backend.TableHandler.table_exist('notification_email_log'):
query = table.insert(
[table.create_uid, table.create_date,
table.write_uid, table.write_date,
table.recipients, table.recipients_secondary,
table.recipients_hidden,
table.resource,
table.notification_email, table.notification_trigger],
log.select(
log.create_uid, log.create_date,
log.write_uid, log.write_date,
log.recipients, log.recipients_secondary,
log.recipients_hidden,
log.resource,
log.notification, log.trigger))
cursor.execute(*query)
def get_user(self, name):
user = super().get_user(name)
if self.notification_email or self.notification_trigger:
user = None
return user
+29
View File
@@ -8,5 +8,34 @@ this repository contains the full copyright notices and license terms. -->
<field name="inherit" ref="ir.trigger_view_form"/>
<field name="name">trigger_form</field>
</record>
<record model="ir.ui.view" id="ir_email_view_form">
<field name="model">ir.email</field>
<field name="inherit" ref="ir.email_view_form"/>
<field name="name">ir_email_form</field>
</record>
<record model="ir.action.act_window" id="act_ir_email_form_relate_notification">
<field name="name">E-mails</field>
<field name="res_model">ir.email</field>
<field name="domain"
eval="[('notification_email', 'in', Eval('active_ids'))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_ir_email_form_relate_notification_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="ir.email_view_list"/>
<field name="act_window" ref="act_ir_email_form_relate_notification"/>
</record>
<record model="ir.action.act_window.view" id="act_ir_email_form_relate_notification_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="ir.email_view_form"/>
<field name="act_window" ref="act_ir_email_form_relate_notification"/>
</record>
<record model="ir.action.keyword" id="act_ir_email_form_relate_notification_keyword1">
<field name="keyword">form_relate</field>
<field name="model">notification.email,-1</field>
<field name="action" ref="act_ir_email_form_relate_notification"/>
</record>
</data>
</tryton>
+15 -84
View File
@@ -9,12 +9,9 @@ from email.mime.nonmultipart import MIMENonMultipart
from email.utils import formataddr, getaddresses, parseaddr
from genshi.template import TextTemplate
from sql import Null
from sql.operators import Concat
from trytond.config import config
from trytond.i18n import gettext
from trytond.ir.resource import ResourceAccessMixin
from trytond.model import ModelSQL, ModelView, fields
from trytond.pool import Pool
from trytond.pyson import Eval, TimeDelta
@@ -202,17 +199,7 @@ class Email(ModelSQL, ModelView):
msg['Cc'] = ', '.join(formataddr(parseaddr(a)) for a in cc)
msg['Subject'] = Header(title, 'utf-8')
msg['Auto-Submitted'] = 'auto-generated'
return msg
def get_log(self, record, trigger, msg, bcc=None):
return {
'recipients': msg['To'],
'recipients_secondary': msg['Cc'],
'recipients_hidden': bcc,
'resource': str(record),
'notification': trigger.notification_email.id,
'trigger': trigger.id,
}
return msg, title
@classmethod
def trigger(cls, records, trigger):
@@ -240,12 +227,12 @@ class Email(ModelSQL, ModelView):
def send_email(self, records, trigger):
pool = Pool()
Log = pool.get('notification.email.log')
Email = pool.get('ir.email')
datamanager = SMTPDataManager()
Transaction().join(datamanager)
from_ = (config.get('notification_email', 'from')
or config.get('email', 'from'))
logs = []
emails = []
for record in records:
to, to_languages = self._get_to(record)
cc, cc_languages = self._get_cc(record)
@@ -253,13 +240,20 @@ class Email(ModelSQL, ModelView):
languagues = to_languages | cc_languages | bcc_languages
to_addrs = [e for _, e in getaddresses(to + cc + bcc)]
if to_addrs:
msg = self.get_email(record, from_, to, cc, bcc, languagues)
msg, title = self.get_email(
record, from_, to, cc, bcc, languagues)
sendmail_transactional(
from_, to_addrs, msg, datamanager=datamanager)
logs.append(self.get_log(
record, trigger, msg, bcc=', '.join(bcc)))
if logs:
Log.create(logs)
emails.append(Email(
recipients=', '.join(to),
recipients_secondary=', '.join(cc),
recipients_hidden=', '.join(bcc),
addresses=[{'address': a} for a in to_addrs],
subject=title,
resource=record,
notification_email=trigger.notification_email,
notification_trigger=trigger))
Email.save(emails)
def _get_recipients(self, record, name):
if name == 'id':
@@ -376,66 +370,3 @@ class EmailAttachment(ModelSQL):
'Content-Disposition', 'attachment',
filename=('utf-8', language, name))
return msg
class EmailLog(ResourceAccessMixin, ModelSQL, ModelView):
"Notification Email Log"
__name__ = 'notification.email.log'
date = fields.Function(fields.DateTime('Date'), 'get_date')
recipients = fields.Char("Recipients")
recipients_secondary = fields.Char("Secondary Recipients")
recipients_hidden = fields.Char("Hidden Recipients")
notification = fields.Many2One(
'notification.email', "Notification",
required=True, ondelete='RESTRICT')
trigger = fields.Many2One('ir.trigger', "Trigger")
@classmethod
def __setup__(cls):
super().__setup__()
cls._order = [
('create_date', 'DESC'),
('id', 'DESC'),
]
@classmethod
def __register__(cls, module_name):
pool = Pool()
Model = pool.get('ir.model')
Trigger = pool.get('ir.trigger')
model = Model.__table__()
trigger = Trigger.__table__()
table = cls.__table__()
super().__register__(module_name)
table_h = cls.__table_handler__(module_name)
cursor = Transaction().connection.cursor()
# Migration from 5.6:
# fill notification and resource
# remove required on trigger
notification = trigger.select(
trigger.notification_email,
where=trigger.id == table.trigger)
resource = (trigger
.join(model, condition=trigger.model == model.id)
.select(
Concat(model.model, ',-1'),
where=trigger.id == table.trigger))
cursor.execute(*table.update(
[table.notification, table.resource],
[notification, resource],
where=(table.trigger != Null) & (table.resource == Null)))
table_h.not_null_action('trigger', 'remove')
def get_date(self, name):
return self.create_date.replace(microsecond=0)
@classmethod
def search_date(cls, name, clause):
return [('create_date',) + tuple(clause[1:])]
@staticmethod
def order_date(tables):
table, _ = tables[None]
return [table.create_date]
@@ -55,72 +55,5 @@ this repository contains the full copyright notices and license terms. -->
<field name="perm_create" eval="True"/>
<field name="perm_delete" eval="True"/>
</record>
<record model="ir.ui.view" id="email_log_view_form">
<field name="model">notification.email.log</field>
<field name="type">form</field>
<field name="name">log_form</field>
</record>
<record model="ir.ui.view" id="email_log_view_list">
<field name="model">notification.email.log</field>
<field name="type">tree</field>
<field name="name">log_list</field>
</record>
<record model="ir.model.access" id="access_email_log">
<field name="model" search="[('model', '=', 'notification.email.log')]"/>
<field name="perm_read" eval="True"/>
<field name="perm_write" eval="False"/>
<field name="perm_create" eval="False"/>
<field name="perm_delete" eval="False"/>
</record>
<record model="ir.action.act_window" id="act_notification_email_log">
<field name="name">Logs</field>
<field name="res_model">notification.email.log</field>
<field name="domain"
eval="[('notification', 'in', Eval('active_ids'))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_notification_email_log_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="email_log_view_list"/>
<field name="act_window" ref="act_notification_email_log"/>
</record>
<record model="ir.action.act_window.view" id="act_notification_email_log_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="email_log_view_form"/>
<field name="act_window" ref="act_notification_email_log"/>
</record>
<record model="ir.action.keyword"
id="act_notification_email_log_keyword1">
<field name="keyword">form_relate</field>
<field name="model">notification.email,-1</field>
<field name="action" ref="act_notification_email_log"/>
</record>
<record model="ir.action.act_window" id="act_notification_email_log_relate">
<field name="name">Notification E-Mails</field>
<field name="res_model">notification.email.log</field>
<field
name="domain"
eval="[If(Eval('active_ids', []) == [Eval('active_id')], ('resource', '=', [Eval('active_model'), Eval('active_id')]), ('resource.id', 'in', Eval('active_ids'), Eval('active_model')))]"
pyson="1"/>
</record>
<record model="ir.action.act_window.view" id="act_notification_email_log_relate_view1">
<field name="sequence" eval="10"/>
<field name="view" ref="email_log_view_list"/>
<field name="act_window" ref="act_notification_email_log_relate"/>
</record>
<record model="ir.action.act_window.view" id="act_notification_email_log_relate_view2">
<field name="sequence" eval="20"/>
<field name="view" ref="email_log_view_form"/>
<field name="act_window" ref="act_notification_email_log_relate"/>
</record>
<record model="ir.action.keyword" id="act_notification_email_log_relate_keyword1">
<field name="keyword">form_relate</field>
<field name="action" ref="act_notification_email_log_relate"/>
</record>
</data>
</tryton>
+1 -1
View File
@@ -41,7 +41,7 @@ else:
download_url = 'http://downloads.tryton.org/%s.%s/' % (
major_version, minor_version)
requires = ['Genshi']
requires = ['Genshi', 'python-sql']
for dep in info.get('depends', []):
if not re.match(r'(ir|res)(\W|$)', dep):
requires.append(get_require_version('trytond_%s' % dep))
+22 -17
View File
@@ -91,7 +91,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
Trigger = pool.get('ir.trigger')
Model = pool.get('ir.model')
NotificationEmail = pool.get('notification.email')
Log = pool.get('notification.email.log')
Email = pool.get('ir.email')
self._setup_notification()
notification_email, = NotificationEmail.search([])
@@ -99,7 +99,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
model, = Model.search([
('model', '=', User.__name__),
])
Trigger.create([{
trigger, = Trigger.create([{
'name': 'Test creation',
'model': model.id,
'on_create': True,
@@ -111,7 +111,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
with patch.object(
notification_module, 'sendmail_transactional') as sendmail, \
patch.object(notification_module, 'SMTPDataManager'):
User.create([{'name': "Michael Scott", 'login': "msc"}])
user, = User.create([{'name': "Michael Scott", 'login': "msc"}])
self.run_tasks()
sendmail.assert_called_once_with(
FROM, ['user@example.com'], ANY,
@@ -125,11 +125,14 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
self.assertEqual(
msg.get_payload(0).get_payload(), 'Hello Michael Scott')
log, = Log.search([])
self.assertEqual(log.trigger.notification_email, notification_email)
self.assertEqual(log.recipients, 'Administrator <user@example.com>')
self.assertEqual(log.recipients_secondary, '')
self.assertEqual(log.recipients_hidden, '')
email, = Email.search([])
self.assertEqual(email.recipients, 'Administrator <user@example.com>')
self.assertEqual(email.recipients_secondary, '')
self.assertEqual(email.recipients_hidden, '')
self.assertEqual(email.subject, 'Notification Email')
self.assertEqual(email.resource, user)
self.assertEqual(email.notification_email, notification_email)
self.assertEqual(email.notification_trigger, trigger)
@unittest.skipIf(
(3, 5, 0) <= sys.version_info < (3, 5, 2), "python bug #25195")
@@ -141,7 +144,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
Trigger = pool.get('ir.trigger')
Model = pool.get('ir.model')
NotificationEmail = pool.get('notification.email')
Log = pool.get('notification.email.log')
Email = pool.get('ir.email')
self._setup_notification(recipient_field='id')
notification_email, = NotificationEmail.search([])
@@ -149,7 +152,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
model, = Model.search([
('model', '=', User.__name__),
])
Trigger.create([{
trigger, = Trigger.create([{
'name': 'Test creation',
'model': model.id,
'on_create': True,
@@ -161,7 +164,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
with patch.object(
notification_module, 'sendmail_transactional') as sendmail, \
patch.object(notification_module, 'SMTPDataManager'):
User.create([{
user, = User.create([{
'name': "Michael Scott",
'login': "msc",
'email': 'msc@example.com'}])
@@ -170,9 +173,11 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
FROM, ['msc@example.com'], ANY,
datamanager=ANY)
log, = Log.search([])
self.assertEqual(log.trigger.notification_email, notification_email)
self.assertEqual(log.recipients, 'Michael Scott <msc@example.com>')
email, = Email.search([])
self.assertEqual(email.recipients, 'Michael Scott <msc@example.com>')
self.assertEqual(email.resource, user)
self.assertEqual(email.notification_email, notification_email)
self.assertEqual(email.notification_trigger, trigger)
@with_transaction()
def test_notification_email_delay(self):
@@ -240,7 +245,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
user, = User.create([{'name': "Michael Scott", 'login': "msc"}])
msg = notification_email.get_email(
msg, title = notification_email.get_email(
user, FROM, ['Administrator <user@example.com>'], [], [], [en])
self.assertEqual(msg['From'], FROM)
@@ -280,7 +285,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
user, = User.create([{'name': "Michael Scott", 'login': "msc"}])
msg = notification_email.get_email(
msg, title = notification_email.get_email(
user, FROM, ['Administrator <user@example.com>'], [], [], [en])
self.assertEqual(msg['Subject'], 'Notification for Michael Scott')
@@ -316,7 +321,7 @@ class NotificationEmailTestCase(CompanyTestMixin, ModuleTestCase):
'language': es.id,
}])
msg = notification_email.get_email(
msg, title = notification_email.get_email(
user, FROM, ['Administrator <user@example.com>'], [], [], [es])
self.assertEqual(msg['Subject'], 'Notificación para Michael Scott')
@@ -0,0 +1,11 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<data>
<xpath expr="//field[@name='subject']" position="after">
<label name="notification_email"/>
<field name="notification_email"/>
<label name="notification_trigger"/>
<field name="notification_trigger"/>
</xpath>
</data>
@@ -1,19 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<form>
<label name="date"/>
<field name="date"/>
<label name="resource"/>
<field name="resource"/>
<label name="notification"/>
<field name="notification"/>
<label name="trigger"/>
<field name="trigger"/>
<label name="recipients"/>
<field name="recipients" colspan="3"/>
<label name="recipients_secondary"/>
<field name="recipients_secondary" colspan="3"/>
<label name="recipients_hidden"/>
<field name="recipients_hidden" colspan="3"/>
</form>
@@ -1,12 +0,0 @@
<?xml version="1.0"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<tree>
<field name="date" widget="date"/>
<field name="date" widget="time" string="Time"/>
<field name="notification" expand="2"/>
<field name="recipients" expand="1"/>
<field name="recipients_secondary" expand="1"/>
<field name="recipients_hidden" expand="1"/>
<field name="resource" expand="1"/>
</tree>