use Django/ezt templates on computed expressions (#19442)

This commit is contained in:
Thomas NOËL 2017-10-15 02:48:32 +02:00
parent f626c45cbb
commit 7d92cf296e
6 changed files with 135 additions and 52 deletions

View File

@ -1952,15 +1952,25 @@ def test_workflows_edit_email_action(pub):
resp = resp.form.submit('submit')
assert resp.location
resp = app.get(item_url)
resp.form[field] = 'Hello {% if world %}{{ world }}{% else %}.'
resp = resp.form.submit('submit')
assert 'syntax error in Django template' in resp.body and 'Unclosed tag' in resp.body
resp = app.get(item_url)
resp.form[field] = 'Hello {% if world %}{{ world }}{% else %}.{% endif %}{% endif %}'
resp = resp.form.submit('submit')
assert 'syntax error in Django template' in resp.body and 'Invalid block tag' in resp.body
resp = app.get(item_url)
resp.form[field] = 'Hello [if-any world][world][else].'
resp = resp.form.submit('submit')
assert 'error in template' in resp.body and 'unclosed block' in resp.body
assert 'syntax error in ezt template' in resp.body and 'unclosed block' in resp.body
resp = app.get(item_url)
resp.form[field] = 'Hello [if-any world][world][else].[end] [end]'
resp = resp.form.submit('submit')
assert 'error in template' in resp.body and 'unmatched [end]' in resp.body
assert 'syntax error in ezt template' in resp.body and 'unmatched [end]' in resp.body
# attachments without backoffice fields: python expressions
resp = app.get(item_url)
@ -2203,9 +2213,27 @@ def test_workflows_display_action_ezt_validation(pub):
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
workflow.id, baz_status.id))
resp.form['message'] = '[is test][end]' # invalid ezt
resp.form['message'] = '{% if test %}test{% endif %}' # valid Django
resp = resp.form.submit('submit')
assert 'error in template' in resp.body
assert Workflow.get(workflow.id).possible_status[0].items[0].message == '{% if test %}test{% endif %}'
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
workflow.id, baz_status.id))
resp.form['message'] = '{% if test %}test{% end %}' # invalid Django
resp = resp.form.submit('submit')
assert 'syntax error in Django template' in resp.body
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
workflow.id, baz_status.id))
resp.form['message'] = '[if-any test]test[end]' # valid ezt
resp = resp.form.submit('submit')
assert Workflow.get(workflow.id).possible_status[0].items[0].message == '[if-any test]test[end]'
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (
workflow.id, baz_status.id))
resp.form['message'] = '[is test][end]' # invalid ezt
resp = resp.form.submit('submit')
assert 'syntax error in ezt template' in resp.body
def test_workflows_delete_action(pub):
create_superuser(pub)

View File

@ -454,6 +454,15 @@ def test_computed_expression_widget():
assert widget.has_error()
assert widget.get_error().startswith('syntax error')
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test': '{{ form_var_xxx }}'})
assert not widget.has_error()
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test': '{% if True %}'})
assert widget.has_error()
assert widget.get_error().startswith('syntax error in Django template')
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test': '[form_var_xxx]'})
assert not widget.has_error()
@ -461,7 +470,7 @@ def test_computed_expression_widget():
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test': '[end]'})
assert widget.has_error()
assert widget.get_error().startswith('error in template')
assert widget.get_error().startswith('syntax error in ezt template')
def test_wcsextrastringwidget():
widget = WcsExtraStringWidget('test', value='foo', required=True)

View File

@ -130,10 +130,18 @@ def test_variable_compute(pub):
# straight string
assert item.compute('blah') == 'blah'
# django template
assert item.compute('{{ form_var_foo }}') == 'hello'
assert item.compute('{{ form_var_foo }}', render=False) == '{{ form_var_foo }}'
assert item.compute('{% if form_var_foo %}its here{% endif %}') == 'its here'
assert item.compute('{% if form_var_foo %}') == '{% if form_var_foo %}'
with pytest.raises(Exception):
item.compute('{% if form_var_foo %}', raises=True)
# ezt string
assert item.compute('[form_var_foo]') == 'hello'
# ezt string, but not ezt asked
assert item.compute('[form_var_foo]', do_ezt=False) == '[form_var_foo]'
assert item.compute('[form_var_foo]', render=False) == '[form_var_foo]'
# ezt string, with an error
assert item.compute('[end]', raises=False) == '[end]'
with pytest.raises(Exception):
@ -147,9 +155,13 @@ def test_variable_compute(pub):
item.compute('=1/0', raises=True)
# with context
assert item.compute('{{ form_var_foo }} {{ bar }}', context={'bar': 'world'}) == 'hello world'
assert item.compute('[form_var_foo] [bar]', context={'bar': 'world'}) == 'hello world'
assert item.compute('=form_var_foo + " " + bar', context={'bar': 'world'}) == 'hello world'
# django wins
assert item.compute('{{ form_var_foo }} [bar]', context={'bar': 'world'}) == 'hello [bar]'
def test_variable_compute_dates(pub):
FormDef.wipe()
formdef = FormDef()
@ -908,14 +920,16 @@ def test_webservice_call(pub):
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = False
item.post_data = {'str': 'abcd', 'one': '=1',
item.post_data = {'str': 'abcd', 'one': '=1', 'django': '{{ form_number }}',
'evalme': '=form_number', 'error':'=1=3'}
pub.substitutions.feed(formdata)
item.perform(formdata)
assert http_requests.get_last('url') == 'http://remote.example.net'
assert http_requests.get_last('method') == 'POST'
payload = json.loads(http_requests.get_last('body'))
assert payload == {'one': 1, 'str': 'abcd', 'evalme': formdata.get_display_id()}
assert payload == {'one': 1, 'str': 'abcd',
'evalme': formdata.get_display_id(),
'django': formdata.get_display_id()}
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
@ -949,6 +963,20 @@ def test_webservice_call(pub):
assert 'signature=' in http_requests.get_last('url')
assert http_requests.get_last('method') == 'GET'
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = False
item.request_signature_key = '{{ doesntexist }}'
item.perform(formdata)
assert not 'signature=' in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = False
item.request_signature_key = '{{ empty }}'
item.perform(formdata)
assert not 'signature=' in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = False
@ -956,6 +984,13 @@ def test_webservice_call(pub):
item.perform(formdata)
assert not 'signature=' in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = False
item.request_signature_key = '{{ bar }}'
item.perform(formdata)
assert 'signature=' in http_requests.get_last('url')
item = WebserviceCallStatusItem()
item.url = 'http://remote.example.net'
item.post = False
@ -1111,6 +1146,8 @@ def test_webservice_call(pub):
'str': 'abcd',
'one': '=1',
'evalme': '=form_number',
'django': '{{ form_number }}',
'ezt': '[form_number]',
'error': '=1=3',
'in_url': '2',
}
@ -1118,10 +1155,12 @@ def test_webservice_call(pub):
item.perform(formdata)
assert http_requests.get_last('method') == 'GET'
qs = urlparse.parse_qs(http_requests.get_last('url').split('?')[1])
assert set(qs.keys()) == set(['in_url', 'str', 'one', 'evalme'])
assert set(qs.keys()) == set(['in_url', 'str', 'one', 'evalme', 'django', 'ezt'])
assert qs['in_url'] == ['1', '2']
assert qs['one'] == ['1']
assert qs['evalme'] == [formdata.get_display_id()]
assert qs['django'] == [formdata.get_display_id()]
assert qs['ezt'] == [formdata.get_display_id()]
assert qs['str'] == ['abcd']
def test_webservice_waitpoint(pub):
@ -2435,6 +2474,13 @@ def test_profile(pub):
item.perform(formdata)
assert pub.user_class.get(user.id).name == 'Plop'
item.fields = [{'field_id': '__name', 'value': 'dj{{form_var_foo}}'}]
item.perform(formdata)
assert pub.user_class.get(user.id).name == 'djPlop'
item.fields = [{'field_id': '__name', 'value': 'ezt[form_var_foo]'}]
item.perform(formdata)
assert pub.user_class.get(user.id).name == 'eztPlop'
from wcs.admin.settings import UserFieldsFormDef
formdef = UserFieldsFormDef(pub)
formdef.fields = [StringField(id='3', label='test', type='string', varname='plop')]
@ -2472,16 +2518,26 @@ def test_set_backoffice_field(two_pubs):
item = SetBackofficeFieldsWorkflowStatusItem()
item.parent = st1
item.perform(formdata)
item = SetBackofficeFieldsWorkflowStatusItem()
item.parent = st1
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert formdata.data.get('bo1') == None
item.fields = [{'field_id': 'bo1', 'value': '=form_var_string'}]
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert formdata.data['bo1'] == 'HELLO'
item.fields = [{'field_id': 'bo1', 'value': '{{ form_var_string }} WORLD'}]
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert formdata.data['bo1'] == 'HELLO WORLD'
item.fields = [{'field_id': 'bo1', 'value': '[form_var_string] GOODBYE'}]
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert formdata.data['bo1'] == 'HELLO GOODBYE'
def test_set_backoffice_field_file(two_pubs):
Workflow.wipe()
FormDef.wipe()
@ -2753,6 +2809,16 @@ def test_set_backoffice_field_date(two_pubs):
formdata = formdef.data_class().get(formdata.id)
assert datetime.date(*formdata.data['bo1'][:3]) == datetime.date.today()
formdata.data['bo1'] = None
formdata.store()
item = SetBackofficeFieldsWorkflowStatusItem()
item.parent = st1
item.fields = [{'field_id': 'bo1', 'value': '{% now "j/n/Y" %}'}]
item.perform(formdata)
formdata = formdef.data_class().get(formdata.id)
assert datetime.date(*formdata.data['bo1'][:3]) == datetime.date.today()
item = SetBackofficeFieldsWorkflowStatusItem()
item.parent = st1
item.fields = [{'field_id': 'bo1', 'value': "23/3/2017"}]

View File

@ -65,7 +65,7 @@ from quixote.util import randbytes
from django.template import RequestContext
from django.utils.safestring import mark_safe
from .template import render as render_template
from .template import render as render_template, Template, TemplateError
from wcs.portfolio import has_portfolio
from qommon import _, ngettext
@ -2228,28 +2228,11 @@ class ComputedExpressionWidget(StringWidget):
return StringWidget.render_content(self)
@classmethod
def validate_ezt(cls, template):
processor = ezt.Template(compress_whitespace=False)
def validate_template(cls, template):
try:
processor.parse(template or '')
except ezt.EZTException as e:
parts = []
parts.append({
ezt.ArgCountSyntaxError: _('wrong number of arguments'),
ezt.UnknownReference: _('unknown reference'),
ezt.NeedSequenceError: _('sequence required'),
ezt.UnclosedBlocksError: _('unclosed block'),
ezt.UnmatchedEndError: _('unmatched [end]'),
ezt.UnmatchedElseError: _('unmatched [else]'),
ezt.BaseUnavailableError: _('unavailable base location'),
ezt.BadFormatConstantError: _('bad format constant'),
ezt.UnknownFormatConstantError: _('unknown format constant'),
}.get(e.__class__))
if e.line is not None:
parts.append(_('at line %(line)d and column %(column)d') % {
'line': e.line+1,
'column': e.column+1})
raise ValidationError(_('error in template (%s)') % ' '.join(parts))
Template(template, raises=True)
except TemplateError as e:
raise ValidationError('%s' % e)
@classmethod
def validate(cls, expression):
@ -2259,9 +2242,9 @@ class ComputedExpressionWidget(StringWidget):
try:
compile(expression[1:], '<string>', 'eval')
except SyntaxError as e:
raise ValidationError(_('syntax error (%s)') % e)
raise ValidationError(_('syntax error in Python expression: %s') % e)
else:
cls.validate_ezt(expression)
cls.validate_template(expression)
def _parse(self, request):
StringWidget._parse(self, request)

View File

@ -36,6 +36,7 @@ from qommon.humantime import seconds2humanduration
from qommon import emails, get_cfg, get_logger
from quixote.html import htmltext
import qommon.errors
from qommon.template import Template, TemplateError
from wcs.roles import Role, logged_users_role, get_user_roles
from wcs.fields import FileField
@ -1609,11 +1610,11 @@ class WorkflowStatusItem(XmlSerialisable):
setattr(self, f, value)
@classmethod
def compute(cls, var, do_ezt=True, raises=False, context=None):
def compute(cls, var, render=True, raises=False, context=None):
if not isinstance(var, basestring):
return var
if not var.startswith('=') and not do_ezt:
if not var.startswith('=') and not render:
return var
vars = get_publisher().substitutions.get_context_variables()
@ -1621,12 +1622,8 @@ class WorkflowStatusItem(XmlSerialisable):
if not var.startswith('='):
try:
processor = ezt.Template(compress_whitespace=False)
processor.parse(var)
fd = StringIO()
processor.generate(fd, vars)
return fd.getvalue()
except ezt.EZTException:
return Template(var, raises=raises).render(vars)
except TemplateError:
if raises:
raise
return var
@ -2117,12 +2114,12 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
self.get_list_of_roles(include_logged_in_users=False)})
if 'subject' in parameters:
form.add(StringWidget, '%ssubject' % prefix, title=_('Subject'),
validation_function=ComputedExpressionWidget.validate_ezt,
validation_function=ComputedExpressionWidget.validate_template,
value=self.subject, size=40)
if 'body' in parameters:
form.add(TextWidget, '%sbody' % prefix, title=_('Body'),
value=self.body, cols=80, rows=10,
validation_function=ComputedExpressionWidget.validate_ezt,
validation_function=ComputedExpressionWidget.validate_template,
hint=_('Available variables: url, url_status, details, name, number, comment, field_NAME'))
if 'attachments' in parameters:
@ -2160,13 +2157,13 @@ class SendmailWorkflowStatusItem(WorkflowStatusItem):
url = formdata.get_url()
try:
mail_body = template_on_formdata(formdata, self.compute(self.body, do_ezt=False))
mail_body = template_on_formdata(formdata, self.compute(self.body, render=False))
except ezt.EZTException:
get_logger().error('error in template for email body [%s], mail could not be generated' % url)
return
try:
mail_subject = template_on_formdata(formdata, self.compute(self.subject, do_ezt=False))
mail_subject = template_on_formdata(formdata, self.compute(self.subject, render=False))
except ezt.EZTException:
get_logger().error('error in template for email subject [%s], mail could not be generated' % url)
return
@ -2361,7 +2358,7 @@ class SendSMSWorkflowStatusItem(WorkflowStatusItem):
return
try:
sms_body = template_on_formdata(formdata, self.compute(self.body, do_ezt=False))
sms_body = template_on_formdata(formdata, self.compute(self.body, render=False))
except ezt.EZTException:
url = formdata.get_url()
get_logger().error('error in template for sms [%s], sms could not be generated' % url)
@ -2436,7 +2433,7 @@ class DisplayMessageWorkflowStatusItem(WorkflowStatusItem):
if 'message' in parameters:
form.add(TextWidget, '%smessage' % prefix, title = _('Message'),
value=self.message, cols=80, rows=10,
validation_function=ComputedExpressionWidget.validate_ezt)
validation_function=ComputedExpressionWidget.validate_template)
if 'to' in parameters:
form.add(WidgetList, '%sto' % prefix, title=_('To'),
element_type=SingleSelectWidget,

View File

@ -72,7 +72,7 @@ def call_webservice(url, qs_data=None, request_signature_key=None,
url = urlparse.urlunparse(parsed[:4] + (qs,) + parsed[5:6])
if request_signature_key:
signature_key = WorkflowStatusItem.compute(request_signature_key)
signature_key = str(WorkflowStatusItem.compute(request_signature_key))
if signature_key:
url = sign_url(url, signature_key)