workflows: add mail template management (#25378)

This commit is contained in:
Frédéric Péters 2020-01-21 09:55:20 +01:00
parent b10d3fdb33
commit 0105a7bd29
11 changed files with 588 additions and 13 deletions

View File

@ -0,0 +1,217 @@
# -*- coding: utf-8 -*-
import base64
import os
import pytest
from django.utils.encoding import force_bytes
from quixote import cleanup
from wcs.formdef import FormDef
from wcs.logged_errors import LoggedError
from wcs.mail_templates import MailTemplate
from wcs.workflows import Workflow, SendmailWorkflowStatusItem
from wcs.qommon.http_request import HTTPRequest
from wcs.qommon.ident.password_accounts import PasswordAccount
from utilities import (get_app, login, create_temporary_pub, clean_temporary_pub)
def setup_module(module):
cleanup()
def teardown_module(module):
clean_temporary_pub()
@pytest.fixture
def pub(request):
pub = create_temporary_pub()
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
pub.set_app_dir(req)
pub._set_request(req)
pub.cfg['identification'] = {'methods': ['password']}
pub.write_cfg()
return pub
@pytest.fixture
def superuser(pub):
if pub.user_class.select(lambda x: x.name == 'admin'):
user1 = pub.user_class.select(lambda x: x.name == 'admin')[0]
user1.is_admin = True
user1.store()
return user1
user1 = pub.user_class(name='admin')
user1.is_admin = True
user1.store()
account1 = PasswordAccount(id='admin')
account1.set_password('admin')
account1.user_id = user1.id
account1.store()
return user1
@pytest.fixture
def mail_templates_option(pub):
if not pub.site_options.has_section('options'):
pub.site_options.add_section('options')
pub.site_options.set('options', 'mail-templates', 'true')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
return pub
def test_mail_templates_disabled(pub, superuser):
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/')
assert 'Mail Templates' not in resp
def test_mail_templates_basics(pub, superuser, mail_templates_option):
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/')
assert 'Mail Templates' in resp
resp = resp.click('Mail Templates')
assert 'There are no mail templates defined.' in resp
resp = resp.click('New')
resp.form['name'] = 'first mail template'
resp = resp.form.submit('cancel').follow()
assert 'There are no mail templates defined.' in resp
resp = resp.click('New')
resp.form['name'] = 'first mail template'
resp = resp.form.submit('submit').follow()
resp.form['subject'] = 'mail subject'
resp.form['body'] = 'mail body'
resp = resp.form.submit('submit').follow()
resp = resp.click('Edit')
resp.form['subject'] = 'edited mail subject'
resp.form['body'] = 'edited mail body'
resp = resp.form.submit('submit').follow()
resp = resp.click('Delete')
resp = resp.form.submit('cancel').follow()
assert 'first mail template' in resp
resp = resp.click('Delete')
resp = resp.form.submit('submit').follow()
assert 'first mail template' not in resp
assert 'There are no mail templates defined.' in resp
resp = resp.click('New')
resp.form['name'] = 'first mail template'
resp = resp.form.submit('submit').follow()
resp.form['subject'] = 'mail subject'
resp.form['body'] = 'mail body'
resp = resp.form.submit('submit').follow()
resp = app.get('/backoffice/workflows/')
resp = resp.click('Mail Templates')
assert 'first mail template' in resp
def test_mail_template_in_use(pub, mail_templates_option):
Workflow.wipe()
MailTemplate.wipe()
workflow = Workflow(name='test mail template')
st1 = workflow.add_status('Status1')
item = SendmailWorkflowStatusItem()
item.to = ['_receiver']
item.subject = 'Foobar'
item.body = 'Hello'
st1.items.append(item)
item.parent = st1
workflow.store()
mail_template = MailTemplate(name='test mail template')
mail_template.subject = 'test subject'
mail_template.body = 'test body'
mail_template.store()
assert mail_template.is_in_use() is False
item.mail_template = mail_template.slug
workflow.store()
assert mail_template.is_in_use() is True
def test_admin_workflow_edit(pub, superuser, mail_templates_option):
Workflow.wipe()
MailTemplate.wipe()
mail_template = MailTemplate(name='test mail template')
mail_template.subject = 'test subject'
mail_template.body = 'test body'
mail_template.store()
workflow = Workflow(name='test mail template')
st1 = workflow.add_status('Status1')
item = SendmailWorkflowStatusItem()
item.to = ['_receiver']
item.subject = 'Foobar'
item.body = 'Hello'
st1.items.append(item)
item.parent = st1
workflow.store()
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/' % workflow.id)
resp = resp.click('Status1')
resp = resp.click('Email')
resp.form['mail_template'] = 'test-mail-template'
resp = resp.form.submit('submit')
workflow = Workflow.get(workflow.id)
assert workflow.possible_status[0].items[0].mail_template == 'test-mail-template'
def test_workflow_send_mail_template(pub, superuser, mail_templates_option, emails):
Workflow.wipe()
MailTemplate.wipe()
mail_template = MailTemplate(name='test mail template')
mail_template.subject = 'test subject'
mail_template.body = 'test body'
mail_template.store()
workflow = Workflow(name='test mail template')
st1 = workflow.add_status('Status1')
item = SendmailWorkflowStatusItem()
item.to = 'xyz@localhost'
item.subject = 'Foobar'
item.body = 'Hello'
item.mail_template = mail_template.slug
st1.items.append(item)
item.parent = st1
workflow.store()
formdef = FormDef()
formdef.name = 'baz'
formdef.fields = []
formdef.store()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
item.perform(formdata)
pub.get_request().response.process_after_jobs()
assert emails.count() == 1
assert emails.get('test subject')['email_rcpt'] == ['xyz@localhost']
assert b'test body' in base64.decodestring(force_bytes(emails.get('test subject')['msg'].get_payload(0).get_payload()))
# check nothing is sent and an error is logged if the mail template is
# missing
emails.empty()
LoggedError.wipe()
MailTemplate.wipe()
item.perform(formdata)
pub.get_request().response.process_after_jobs()
assert emails.count() == 0
assert LoggedError.count() == 1
logged_error = LoggedError.select()[0]
assert logged_error.summary == 'reference to invalid mail template test-mail-template in status Status1'

170
wcs/admin/mail_templates.py Normal file
View File

@ -0,0 +1,170 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2020 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from quixote import get_response, redirect
from quixote.directory import Directory
from quixote.html import TemplateIO, htmltext
from wcs.qommon import _, errors, template
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import Form, HtmlWidget, StringWidget, TextWidget, ComputedExpressionWidget
from wcs.mail_templates import MailTemplate
class MailTemplatesDirectory(Directory):
_q_exports = ['', 'new']
do_not_call_in_templates = True
def _q_traverse(self, path):
get_response().breadcrumb.append(('workflows/', _('Workflows')))
get_response().breadcrumb.append(('mail-templates/', _('Mail Templates')))
return super(MailTemplatesDirectory, self)._q_traverse(path)
def _q_lookup(self, component):
return MailTemplatePage(component)
def _q_index(self):
html_top('mail_templates', title=_('Mail Templates'))
return template.QommonTemplateResponse(
templates=['wcs/backoffice/mail-templates.html'],
context={'view': self, 'mail_templates': MailTemplate.select(order_by='name')})
def new(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50)
form.add_submit('submit', _('Add'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
mail_template = MailTemplate(name=form.get_widget('name').parse())
mail_template.store()
return redirect('%s/edit' % mail_template.id)
get_response().breadcrumb.append(('new', _('New Mail Template')))
html_top('mail_templates', title=_('New Mail Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('New Mail Template')
r += form.render()
return r.getvalue()
class MailTemplatePage(Directory):
_q_exports = ['', 'edit', 'delete']
do_not_call_in_templates = True
def __init__(self, mail_template_id):
try:
self.mail_template = MailTemplate.get(mail_template_id)
except KeyError:
raise errors.TraversalError()
get_response().breadcrumb.append((mail_template_id + '/', self.mail_template.name))
def _q_index(self):
html_top('mail_templates', title=self.mail_template.name)
return template.QommonTemplateResponse(
templates=['wcs/backoffice/mail-template.html'],
context={'view': self, 'mail_template': self.mail_template})
def get_form(self):
form = Form(enctype='multipart/form-data', advanced_label=_('Additional options'))
form.add(StringWidget, 'name', title=_('Name'), required=True, size=30,
value=self.mail_template.name)
form.add(TextWidget, 'description', title=_('Description'),
cols=80, rows=3,
value=self.mail_template.description)
form.add(StringWidget, 'subject', title=_('Subject'), required=True, size=40,
value=self.mail_template.subject,
validation_function=ComputedExpressionWidget.validate_template)
form.add(TextWidget, 'body', title=_('Body'),
value=self.mail_template.body, cols=80, rows=15, require=True,
validation_function=ComputedExpressionWidget.validate_template)
if self.mail_template.slug and not self.mail_template.is_in_use():
form.add(StringWidget, 'slug',
value=self.mail_template.slug,
title=_('Identifier'),
required=True, advanced=True,
)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
return form
def submit_form(self, form):
name = form.get_widget('name').parse()
slug_widget = form.get_widget('slug')
if slug_widget:
slug = form.get_widget('slug').parse()
for mail_template in MailTemplate.select():
if mail_template.id == self.mail_template.id:
continue
if slug_widget and slug == mail_template.slug:
slug_widget.set_error(_('This value is already used.'))
if form.has_errors():
raise ValueError()
self.mail_template.name = name
self.mail_template.description = form.get_widget('description').parse()
self.mail_template.subject = form.get_widget('subject').parse()
self.mail_template.body = form.get_widget('body').parse()
if slug_widget:
self.mail_template.slug = slug
self.mail_template.store()
def edit(self):
form = self.get_form()
if form.get_submit() == 'cancel':
return redirect('.')
if form.get_submit() == 'submit' and not form.has_errors():
try:
self.submit_form(form)
except ValueError:
pass
else:
return redirect('.')
get_response().breadcrumb.append(('edit', _('Edit')))
html_top('mail_templates', title=_('Edit Mail Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Edit Mail Template')
r += form.render()
return r.getvalue()
def delete(self):
form = Form(enctype='multipart/form-data')
if not self.mail_template.is_in_use():
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'You are about to irrevocably delete this mail template.')))
form.add_submit('delete', _('Submit'))
else:
form.widgets.append(HtmlWidget('<p>%s</p>' % _(
'This mail template is still used, it cannot be deleted.')))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top('mail_templates', title=_('Delete Mail Template'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Mail Template:'), self.mail_template.name)
r += form.render()
return r.getvalue()
else:
self.mail_template.remove_self()
return redirect('..')

View File

@ -861,6 +861,7 @@ class SettingsDirectory(QommonSettingsDirectory):
form.add(CheckboxWidget, 'categories', title = _('Categories'), value = True)
form.add(CheckboxWidget, 'settings', title = _('Settings'), value = False)
form.add(CheckboxWidget, 'datasources', title=_('Data sources'), value=True)
form.add(CheckboxWidget, 'mail-templates', title=_('Mail templates'), value=True)
form.add(CheckboxWidget, 'wscalls', title=_('Webservice calls'), value=True)
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
@ -885,7 +886,7 @@ class SettingsDirectory(QommonSettingsDirectory):
c = BytesIO()
z = zipfile.ZipFile(c, 'w')
for d in self.dirs:
if d not in ('roles', 'categories', 'datasources', 'wscalls'):
if d not in ('roles', 'categories', 'datasources', 'wscalls', 'mail-templates'):
continue
path = os.path.join(self.app_dir, d)
if not os.path.exists(path):
@ -928,7 +929,7 @@ class SettingsDirectory(QommonSettingsDirectory):
dirs = []
for w in ('formdefs', 'carddefs', 'workflows', 'roles', 'categories',
'datasources', 'wscalls'):
'datasources', 'wscalls', 'mail-templates'):
if form.get_widget(w) and form.get_widget(w).parse():
dirs.append(w)
if not dirs and not form.get_widget('settings').parse():
@ -1017,6 +1018,8 @@ class SettingsDirectory(QommonSettingsDirectory):
r += htmltext('<li>%s</li>') % _('Settings')
if results['datasources']:
r += htmltext('<li>%d %s</li>') % (results['datasources'], _('data sources'))
if results['mail-templates']:
r += htmltext('<li>%d %s</li>') % (results['mail-templates'], _('mail templates'))
if results['wscalls']:
r += htmltext('<li>%d %s</li>') % (results['wscalls'], _('webservice calls'))
r += htmltext('</ul>')

View File

@ -46,6 +46,7 @@ from wcs.formdata import Evolution
from .fields import FieldDefPage, FieldsDirectory
from .data_sources import NamedDataSourcesDirectory
from .logged_errors import LoggedErrorsDirectory
from .mail_templates import MailTemplatesDirectory
from wcs.backoffice.studio import StudioDirectory
@ -1810,9 +1811,11 @@ class NamedDataSourcesDirectoryInWorkflows(NamedDataSourcesDirectory):
class WorkflowsDirectory(Directory):
_q_exports = ['', 'new', ('import', 'p_import'),
('data-sources', 'data_sources')]
('data-sources', 'data_sources'),
('mail-templates', 'mail_templates')]
data_sources = NamedDataSourcesDirectoryInWorkflows()
mail_templates = MailTemplatesDirectory()
def html_top(self, title):
return html_top('workflows', title)
@ -1825,6 +1828,8 @@ class WorkflowsDirectory(Directory):
r += htmltext('<div id="appbar">')
r += htmltext('<h2>%s</h2>') % _('Workflows')
r += htmltext('<span class="actions">')
if get_publisher().has_site_option('mail-templates'):
r += htmltext('<a href="mail-templates/">%s</a>') % _('Mail Templates')
r += htmltext('<a href="data-sources/">%s</a>') % _('Data sources')
r += htmltext('<a href="import" rel="popup">%s</a>') % _('Import')
r += htmltext('<a class="new-item" rel="popup" href="new">%s</a>') % _('New Workflow')

85
wcs/mail_templates.py Normal file
View File

@ -0,0 +1,85 @@
# w.c.s. - web application for online forms
# Copyright (C) 2005-2020 Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
from wcs.qommon import misc, get_logger
from wcs.qommon.xml_storage import XmlStorableObject
class MailTemplate(XmlStorableObject):
_names = 'mail-templates'
_xml_tagname = 'mail-template'
name = None
slug = None
description = None
subject = None
body = None
# declarations for serialization
XML_NODES = [
('name', 'str'),
('slug', 'str'),
('description', 'str'),
('subject', 'str'),
('body', 'str'),
]
def __init__(self, name=None):
XmlStorableObject.__init__(self)
self.name = name
def store(self):
if self.slug is None:
# set slug if it's not yet there
self.slug = self.get_new_slug()
super(MailTemplate, self).store()
def get_new_slug(self):
new_slug = misc.simplify(self.name, space='-')
base_new_slug = new_slug
suffix_no = 0
known_slugs = {x.slug: x.id for x in self.select(ignore_migration=True, ignore_errors=True)}
while True:
if not new_slug in known_slugs:
break
suffix_no += 1
new_slug = '%s-%s' % (base_new_slug, suffix_no)
return new_slug
def is_in_use(self):
from wcs.workflows import Workflow
for workflow in Workflow.select(ignore_errors=True, ignore_migration=True):
for item in workflow.get_all_items():
if item.key != 'sendmail':
continue
if item.mail_template == self.slug:
return True
return False
@classmethod
def get_as_options_list(cls):
options = []
for mail_template in cls.select(order_by='name'):
options.append((mail_template.slug, mail_template.name, mail_template.slug))
return options
@classmethod
def get_by_slug(cls, slug):
objects = [x for x in cls.select() if x.slug == slug]
if objects:
return objects[0]
get_logger().warn("mail template '%s' does not exist" % slug)
return None

View File

@ -160,7 +160,7 @@ class WcsPublisher(StubWcsPublisher):
def import_zip(self, fd):
z = zipfile.ZipFile(fd)
results = {'formdefs': 0, 'carddefs': 0, 'workflows': 0, 'categories': 0, 'roles': 0,
'settings': 0, 'datasources': 0, 'wscalls': 0}
'settings': 0, 'datasources': 0, 'wscalls': 0, 'mail-templates': 0}
def _decode_list(data):
rv = []

View File

@ -1878,3 +1878,8 @@ table#listing tr.checked td {
width: 40%;
min-width: 10em;
}
div.mail-body {
margin-top: 1em;
white-space: pre-line;
}

View File

@ -1,6 +1,7 @@
{% block appbar %}
<div id="appbar">
<h2>{% block appbar-title %}{% endblock %}</h2>
<span class="actions">{% block appbar-actions %}{% endblock %}</span>
</div>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "wcs/backoffice/base.html" %}
{% load i18n %}
{% block appbar-title %}{% trans "Mail Template" %} - {{ mail_template.name }}{% endblock %}
{% block appbar-actions %}
<a href="delete">{% trans "Delete" %}</a>
<a href="edit">{% trans "Edit" %}</a>
{% endblock %}
{% block content %}
{% if mail_template.description %}
<div class="bo-block">{{ mail_template.description }}</div>
{% endif %}
{% if mail_template.subject and mail_template.body %}
<div class="bo-block">
<div class="mail-subject"><strong>{% trans "Subject:" %} </strong>{{ mail_template.subject }}</div>
<div class="mail-body">{{ mail_template.body }}</div>
</div>
{% else %}
<div class="infonotice">{% trans "This mail template still needs to be configured." %}</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "wcs/backoffice/base.html" %}
{% load i18n %}
{% block appbar-title %}{% trans "Mail Templates" %}{% endblock %}
{% block appbar-actions %}
<a rel="popup" href="new">{% trans "New mail template" %}</a>
{% endblock %}
{% block content %}
{% if mail_templates %}
<ul class="objects-list single-links">
{% for mail_template in mail_templates %}
<li><a href="{{ mail_template.id }}/">{{ mail_template.name }}</a></li>
{% endfor %}
</ul>
{% else %}
<div class="infonotice">
{% trans "There are no mail templates defined." %}
</div>
{% endif %}
{% endblock %}

View File

@ -45,6 +45,7 @@ from .roles import Role, logged_users_role, get_user_roles
from .fields import FileField
from .formdef import FormDef
from .formdata import Evolution
from .mail_templates import MailTemplate
if not __name__.startswith('wcs.') and not __name__ == "__main__":
raise ImportError('Import of workflows module must be absolute (import wcs.workflows)')
@ -423,6 +424,14 @@ class Workflow(StorableObject):
return self.backoffice_fields_formdef.fields or []
return []
def get_all_items(self):
for status in self.possible_status or []:
for item in status.items or []:
yield item
for action in self.global_actions or []:
for item in action.items or []:
yield item
def add_global_action(self, name, id=None):
if [x for x in self.global_actions if x.name == name]:
raise DuplicateGlobalActionNameError()
@ -2449,6 +2458,7 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
to = []
subject = None
mail_template = None
body = None
custom_from = None
attachments = None
@ -2491,11 +2501,19 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
return _('not completed')
def get_parameters(self):
return ('to', 'subject', 'body', 'attachments', 'custom_from', 'condition')
return ('to', 'mail_template', 'subject', 'body', 'attachments', 'custom_from', 'condition')
def add_parameters_widgets(self, form, parameters, prefix='', formdef=None):
super(SendmailWorkflowStatusItem, self).add_parameters_widgets(
form, parameters, prefix=prefix, formdef=formdef)
subject_body_attrs = {}
if 'subject' in parameters or 'body' in parameters:
if get_publisher().has_site_option('mail-templates') and MailTemplate.count():
subject_body_attrs = {
'data-dynamic-display-value': '',
'data-dynamic-display-child-of': '%smail_template' % prefix,
}
if 'to' in parameters:
form.add(WidgetList, '%sto' % prefix, title=_('To'),
element_type=SingleSelectWidgetWithOther,
@ -2507,11 +2525,21 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
if 'subject' in parameters:
form.add(StringWidget, '%ssubject' % prefix, title=_('Subject'),
validation_function=ComputedExpressionWidget.validate_template,
value=self.subject, size=40)
value=self.subject, size=40,
attrs=subject_body_attrs)
if 'mail_template' in parameters and get_publisher().has_site_option('mail-templates') and MailTemplate.count():
form.add(SingleSelectWidget,
'%smail_template' % prefix,
title=_('Mail Template'),
value=self.mail_template,
options=[(None, '', '')] + MailTemplate.get_as_options_list(),
attrs={'data-dynamic-display-parent': 'true'}
)
if 'body' in parameters:
form.add(TextWidget, '%sbody' % prefix, title=_('Body'),
value=self.body, cols=80, rows=10,
validation_function=ComputedExpressionWidget.validate_template)
validation_function=ComputedExpressionWidget.validate_template,
attrs=subject_body_attrs)
if 'custom_from' in parameters:
form.add(ComputedExpressionWidget, '%scustom_from' % prefix,
@ -2524,16 +2552,30 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
def perform(self, formdata):
if not self.to:
return
if not self.subject:
return
if not self.body:
if not (self.subject and self.body) and not self.mail_template:
return
url = formdata.get_url()
body = self.body
subject = self.subject
if self.mail_template:
mail_template = MailTemplate.get_by_slug(self.mail_template)
if mail_template:
body = mail_template.body
subject = mail_template.subject
else:
from wcs.logged_errors import LoggedError
message = _('reference to invalid mail template %(mail_template)s in status %(status)s') % {
'status': self.parent.name,
'mail_template': self.mail_template,
}
LoggedError.record(message, formdata=formdata, status_item=self)
return
try:
mail_body = template_on_formdata(formdata,
self.compute(self.body, render=False),
autoescape=self.body.startswith('<'))
self.compute(body, render=False),
autoescape=body.startswith('<'))
except TemplateError as e:
get_logger().error('error in template for email body [%s], '
'mail could not be generated: %s' % (url, str(e)))
@ -2541,7 +2583,7 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
try:
mail_subject = template_on_formdata(formdata,
self.compute(self.subject, render=False),
self.compute(subject, render=False),
autoescape=False)
except TemplateError as e:
get_logger().error('error in template for email subject [%s], '