diff --git a/trytond/ir/email_.py b/trytond/ir/email_.py
index b4a9b96449..6534d912ad 100644
--- a/trytond/ir/email_.py
+++ b/trytond/ir/email_.py
@@ -19,10 +19,10 @@ from genshi.template import TextTemplate
from trytond.i18n import gettext
from trytond.config import config
-from trytond.model import ModelSQL, ModelView, fields
+from trytond.model import ModelSQL, ModelView, fields, EvalEnvironment
from trytond.model.exceptions import ValidationError, AccessError
from trytond.pool import Pool
-from trytond.pyson import Eval
+from trytond.pyson import Eval, Bool, PYSONDecoder
from trytond.rpc import RPC
from trytond.sendmail import sendmail_transactional, SMTPDataManager
from trytond.tools import escape_wildcard
@@ -255,22 +255,55 @@ class EmailTemplate(ModelSQL, ModelView):
domain=[
('model', '=', Eval('model')),
],
- depends=['model'],
+ states={
+ 'invisible': Bool(Eval('recipients_pyson')),
+ },
+ depends=['model', 'recipients_pyson'],
help="The field that contains the recipient(s).")
+ recipients_pyson = fields.Char(
+ "Recipients",
+ states={
+ 'invisible': Bool(Eval('recipients')),
+ },
+ depends=['recipients'],
+ help="A PYSON expression that generates a list of recipients"
+ 'with the record represented by "self".')
recipients_secondary = fields.Many2One(
'ir.model.field', "Secondary Recipients",
domain=[
('model', '=', Eval('model')),
],
- depends=['model'],
+ states={
+ 'invisible': Bool(Eval('recipients_secondary_pyson')),
+ },
+ depends=['model', 'recipients_secondary_pyson'],
help="The field that contains the secondary recipient(s).")
+ recipients_secondary_pyson = fields.Char(
+ "Secondary Recipients",
+ states={
+ 'invisible': Bool(Eval('recipients_secondary')),
+ },
+ depends=['recipients_secondary'],
+ help="A PYSON expression that generates a list of secondary recipients"
+ 'with the record represented by "self".')
recipients_hidden = fields.Many2One(
'ir.model.field', "Hidden Recipients",
domain=[
('model', '=', Eval('model')),
],
- depends=['model'],
+ states={
+ 'invisible': Bool(Eval('recipients_hidden_pyson')),
+ },
+ depends=['model', 'recipients_hidden_pyson'],
help="The field that contains the secondary recipient(s).")
+ recipients_hidden_pyson = fields.Char(
+ "Hidden Recipients",
+ states={
+ 'invisible': Bool(Eval('recipients_hidden')),
+ },
+ depends=['recipients_hidden'],
+ help="A PYSON expression that generates a list of hidden recipients"
+ 'with the record represented by "self".')
subject = fields.Char("Subject", translate=True)
body = fields.Text("Body", translate=True)
reports = fields.Many2Many(
@@ -316,6 +349,7 @@ class EmailTemplate(ModelSQL, ModelView):
for template in templates:
template.check_subject()
template.check_body()
+ template.check_fields_pyson()
def check_subject(self):
if not self.subject:
@@ -339,27 +373,57 @@ class EmailTemplate(ModelSQL, ModelView):
template=self.rec_name,
exception=exception)) from exception
+ def check_fields_pyson(self):
+ encoder = PYSONDecoder(noeval=True)
+ for field in [
+ 'recipients_pyson',
+ 'recipients_secondary_pyson',
+ 'recipients_hidden_pyson',
+ ]:
+ value = getattr(self, field)
+ if not value:
+ continue
+ try:
+ pyson = encoder.decode(value)
+ except Exception as exception:
+ raise EmailTemplateError(
+ gettext('ir.msg_email_template_invalid_field_pyson',
+ template=self.rec_name,
+ field=self.__names__(field)['field'],
+ exception=exception)) from exception
+ if not isinstance(pyson, list) and pyson.types() != {list}:
+ raise EmailTemplateError(
+ gettext('ir.msg_email_template_invalid_field_pyson_type',
+ template=self.rec_name,
+ field=self.__names__(field)['field'],
+ ))
+
def get(self, record):
pool = Pool()
Model = pool.get(self.model.model)
record = Model(int(record))
values = {}
- for field, key in [
+ for attr, key in [
('recipients', 'to'),
('recipients_secondary', 'cc'),
('recipients_hidden', 'bcc'),
]:
- field = getattr(self, field)
- if not field:
- continue
+ field = getattr(self, attr)
try:
- if field.name == 'id':
- value = record
+ if field:
+ if field.name == 'id':
+ value = record
+ else:
+ value = getattr(record, field.name, None)
+ if value:
+ values[key] = self.get_addresses(value)
else:
- value = getattr(record, field.name, None)
- if value:
- values[key] = self.get_addresses(value)
+ value = getattr(self, attr + '_pyson')
+ if value:
+ value = self.eval(record, value)
+ if value:
+ values[key] = self.get_addresses(value)
except AccessError:
continue
@@ -381,6 +445,13 @@ class EmailTemplate(ModelSQL, ModelView):
values['reports'] = [r.id for r in self.reports]
return values
+ def eval(self, record, pyson):
+ 'Evaluate the pyson with the record'
+ env = {}
+ env['context'] = Transaction().context
+ env['self'] = EvalEnvironment(record, record.__class__)
+ return PYSONDecoder(env).decode(pyson)
+
@classmethod
def _get_default_exclude(cls, record):
return ['create_uid', 'write_uid']
@@ -441,7 +512,9 @@ class EmailTemplate(ModelSQL, ModelView):
def _get_address(cls, record):
pool = Pool()
User = pool.get('res.user')
- if isinstance(record, User) and record.email:
+ if isinstance(record, str):
+ return (None, record)
+ elif isinstance(record, User) and record.email:
return (record.name, record.email)
@classmethod
diff --git a/trytond/ir/message.xml b/trytond/ir/message.xml
index 3e7899c51f..1a6d873656 100644
--- a/trytond/ir/message.xml
+++ b/trytond/ir/message.xml
@@ -323,5 +323,11 @@ this repository contains the full copyright notices and license terms. -->
Invalid body in e-mail template "%(template)s" with exception "%(exception)s"
+
+ Invalid PYSON %(field)s in e-mail template "%(template)s" with exception "%(exception)s".
+
+
+ The PYSON %(field)s in e-mail template "%(template)s" must generate a list.
+
diff --git a/trytond/ir/view/email_template_form.xml b/trytond/ir/view/email_template_form.xml
index 5514f81cca..733a126ddf 100644
--- a/trytond/ir/view/email_template_form.xml
+++ b/trytond/ir/view/email_template_form.xml
@@ -6,12 +6,21 @@ this repository contains the full copyright notices and license terms. -->
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/trytond/tests/test_ir.py b/trytond/tests/test_ir.py
index be1bbebf03..eb7486378a 100644
--- a/trytond/tests/test_ir.py
+++ b/trytond/tests/test_ir.py
@@ -9,6 +9,7 @@ from dateutil.relativedelta import relativedelta
from trytond.config import config
from trytond.pool import Pool
+from trytond.pyson import Eval, If, PYSONEncoder
from trytond.transaction import Transaction
from .test_tryton import ModuleTestCase, with_transaction
@@ -246,6 +247,44 @@ class IrTestCase(ModuleTestCase):
'subject': "User: Administrator",
})
+ @with_transaction()
+ def test_email_template_get_pyson(self):
+ "Test email template get with pyson"
+ pool = Pool()
+ Template = pool.get('ir.email.template')
+ IrModel = pool.get('ir.model')
+ IrModelField = pool.get('ir.model.field')
+ User = pool.get('res.user')
+
+ admin = User(1)
+ admin.email = 'admin@example.com'
+ admin.save()
+ model, = IrModel.search([('model', '=', 'res.user')])
+ field, = IrModelField.search([
+ ('model', '=', model.id),
+ ('name', '=', 'id'),
+ ])
+
+ template = Template(
+ model=model,
+ name="Test",
+ recipients_pyson=PYSONEncoder().encode(
+ [Eval('self.email')]),
+ recipients_secondary_pyson=PYSONEncoder().encode(
+ If(Eval('self.email'),
+ ['fallback@example.com'],
+ [])),
+ )
+ template.save()
+
+ values = template.get(admin)
+
+ self.assertEqual(
+ values, {
+ 'to': ['admin@example.com'],
+ 'cc': ['fallback@example.com'],
+ })
+
def suite():
return unittest.TestLoader().loadTestsFromTestCase(IrTestCase)