Merge notification email logs into email archives
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
* Merge email logs into email archives
|
||||
* Remove support for Python 3.7
|
||||
* Add support for Python 3.11
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user