misc: do not include gear dropdown when python is disabled (#73161) #23

Merged
fpeters merged 1 commits from wip/73161-no-python-no-dropdown into main 2023-01-17 09:12:03 +01:00
6 changed files with 196 additions and 113 deletions

View File

@ -326,22 +326,17 @@ def test_data_sources_agenda_manual_qs_data_type_options(pub):
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
# check expression type is always forced (no python allowed).
app = login(get_app(pub))
resp = app.get('/backoffice/settings/data-sources/%s/edit' % data_source.id)
assert resp.form['qs_data$element0value$type'].options == [
('text', False, 'Text'),
('template', False, 'Template'),
]
assert 'qs_data$element0value$type' not in resp.text
pub.site_options.set('options', 'disable-python-expressions', 'true')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
resp = app.get('/backoffice/settings/data-sources/%s/edit' % data_source.id)
assert resp.form['qs_data$element0value$type'].options == [
('text', False, 'Text'),
('template', False, 'Template'),
]
assert 'qs_data$element0value$type' not in resp.text
def test_data_sources_category(pub):
@ -863,7 +858,6 @@ def test_data_sources_agenda_manual_edit(pub):
resp = app.get('/backoffice/settings/data-sources/%s/edit' % data_source.id)
resp.forms[0]['qs_data$element0key'] = 'arg1'
resp.forms[0]['qs_data$element0value$value_template'] = '{{ foobar }}'
resp.forms[0]['qs_data$element0value$type'] = 'template'
resp = resp.forms[0].submit('submit')
data_source = NamedDataSource.get(data_source.id)

View File

@ -2827,9 +2827,7 @@ def test_form_page_field_condition_types(pub):
pub.site_options.write(fd)
resp = app.get('/backoffice/forms/1/fields/0/')
assert resp.form['condition$type'].options == [
('django', False, 'Django Expression'),
]
assert 'condition$type' not in resp.text
def test_form_fields_reorder(pub):

View File

@ -1205,7 +1205,7 @@ def test_workflows_edit_email_action(pub):
resp = app.get(item_url)
assert 'custom_from' in resp.text
resp.form['custom_from$value_text'] = 'test@localhost'
resp.form['custom_from$value_template'] = 'test@localhost'
resp = resp.form.submit('submit')
sendmail = Workflow.get(workflow.id).get_status(st1.id).items[0]
assert sendmail.custom_from == 'test@localhost'
@ -1217,7 +1217,7 @@ def test_workflows_edit_email_action(pub):
resp = app.get(item_url)
assert 'custom_from' in resp.text
assert resp.form['custom_from$value_text'].value == 'test@localhost'
assert resp.form['custom_from$value_template'].value == 'test@localhost'
def test_workflows_edit_jump_previous(pub):
@ -1362,7 +1362,7 @@ def test_workflows_edit_sms_action(pub):
resp = resp.form.submit('to$add_element')
resp = resp.form.submit('to$add_element')
resp = resp.form.submit('to$add_element')
resp.form['to$element1$value_text'] = '12345'
resp.form['to$element1$value_template'] = '12345'
resp = resp.form.submit('submit')
assert Workflow.get(workflow.id).possible_status[0].items[0].to == ['12345']
@ -1471,7 +1471,7 @@ def test_workflows_edit_display_form_action(pub):
assert 'foobar' in resp.text
resp = resp.click('Edit')
assert 'display_locations' not in resp.form.fields.keys()
assert 'condition$type' in resp.form.fields.keys()
assert 'condition$value_django' in resp.form.fields.keys()
resp = resp.form.submit('cancel')
resp = resp.follow()
resp = resp.click('Remove')
@ -2115,7 +2115,7 @@ def test_workflows_backoffice_fields(pub):
assert 'foobar3' not in options
resp.form['fields$element0$field_id'] = first_field_id
resp.form['fields$element0$value$value_text'] = 'Hello'
resp.form['fields$element0$value$value_template'] = 'Hello'
resp = resp.form.submit('submit')
workflow = Workflow.get(workflow.id)
assert workflow.possible_status[0].items[0].fields == [{'field_id': first_field_id, 'value': 'Hello'}]
@ -2905,7 +2905,6 @@ def test_workflows_create_formdata_expression_types(pub):
app = login(get_app(pub))
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st1.id))
assert resp.form['mappings$element0$expression$type'].options == [
('text', False, 'Text'),
('template', False, 'Template'),
('python', False, 'Python Expression (deprecated)'),
]
@ -2915,10 +2914,7 @@ def test_workflows_create_formdata_expression_types(pub):
pub.site_options.write(fd)
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st1.id))
assert resp.form['mappings$element0$expression$type'].options == [
('text', False, 'Text'),
('template', False, 'Template'),
]
assert 'mappings$element0$expression$type' not in resp.text
def test_workflows_edit_carddata_action_config(pub):
@ -3021,17 +3017,9 @@ def test_workflows_assign_carddata_action_options(pub):
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
assert resp.form['target_id$type'].options == [
('text', False, 'Text'),
('template', False, 'Template'),
]
assert resp.form['user_association_template$type'].options == [
('text', False, 'Text'),
('template', False, 'Template'),
]
assert resp.form['condition$type'].options == [
('django', False, 'Django Expression'),
]
assert 'target_id$type' not in resp.text
assert 'user_association_template$type' not in resp.text
assert 'condition$type' not in resp.text
def test_workflows_assign_carddata_action(pub):
@ -3070,7 +3058,6 @@ def test_workflows_assign_carddata_action(pub):
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
resp.forms[0]['target_mode'] = 'manual'
resp.forms[0]['target_id$type'] = 'template'
resp.forms[0]['target_id$value_template'] = '{{ form_var_plop_id }}'
resp.forms[0]['user_association_mode'] = 'keep-user'
resp = resp.forms[0].submit('submit')
@ -3081,7 +3068,6 @@ def test_workflows_assign_carddata_action(pub):
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
resp.forms[0]['user_association_mode'] = 'custom'
resp.forms[0]['user_association_template$type'] = 'template'
resp.forms[0]['user_association_template$value_template'] = '{{ form_var_user_id }}'
resp = resp.forms[0].submit('submit')
assert Workflow.get(wf.id).possible_status[0].items[0].user_association_mode == 'custom'
@ -3125,14 +3111,14 @@ def test_workflows_user_notification_action(pub):
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
assert resp.forms[0]['to'].value == '_receiver'
resp.forms[0]['to'] = '__other'
resp.forms[0]['users_template$value_text'] = 'foobar'
resp.forms[0]['users_template$value_template'] = 'foobar'
resp = resp.forms[0].submit('submit')
assert Workflow.get(wf.id).possible_status[0].items[0].to == []
assert Workflow.get(wf.id).possible_status[0].items[0].users_template == 'foobar'
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
assert resp.forms[0]['to'].value == '__other'
assert resp.forms[0]['users_template$value_text'].value == 'foobar'
assert resp.forms[0]['users_template$value_template'].value == 'foobar'
resp = app.get('/backoffice/workflows/%s/status/%s/items/1/' % (wf.id, st.id))
resp.forms[0]['target_url'] = '{{portal_url}}'

View File

@ -12,6 +12,7 @@ from wcs.qommon.form import (
CheckboxesWidget,
CompositeWidget,
ComputedExpressionWidget,
ConditionWidget,
DateWidget,
EmailWidget,
FileWithPreviewWidget,
@ -702,12 +703,12 @@ def test_composite_widget():
def test_computed_expression_widget():
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test$value_text': 'hello world', 'test$type': ['text']})
mock_form_submission(req, widget, {'test$value_template': 'hello world', 'test$type': ['template']})
assert widget.parse() == 'hello world'
assert not widget.has_error()
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test$value_text': '', 'test$type': ['text']})
mock_form_submission(req, widget, {'test$value_template': '', 'test$type': ['template']})
assert widget.parse() is None
assert not widget.has_error()
@ -1286,3 +1287,93 @@ def test_map_widget():
def test_profile_fields_sorting():
widget = ProfileUpdateRowWidget('profile')
assert [f[1] for f in widget.get_widgets()[0].options] == ['Email', 'Name']
def test_computed_expression_widget_no_python():
pub.load_site_options()
pub.site_options.set('options', 'disable-python-expressions', 'true')
widget = ComputedExpressionWidget('test')
form = Form(method='post', use_tokens=False, enctype='application/x-www-form-urlencoded')
form.widgets.append(widget)
assert '$type' not in str(form.render())
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test$value_template': 'hello world'})
assert widget.parse() == 'hello world'
assert not widget.has_error()
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test$value_template': ''})
assert widget.parse() is None
assert not widget.has_error()
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test$value_template': '{{ form_var_xxx }}'})
assert not widget.has_error()
widget = ComputedExpressionWidget('test')
mock_form_submission(req, widget, {'test$value_template': '{% if True %}'})
assert widget.has_error()
assert widget.get_error().startswith('syntax error in Django template')
pub.site_options.set('options', 'disable-python-expressions', 'false')
def test_condition_widget():
widget = ConditionWidget('test')
form = Form(method='post', use_tokens=False, enctype='application/x-www-form-urlencoded')
form.widgets.append(widget)
assert '$type' in str(form.render())
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_django': 'hello == 1'})
assert widget.parse() == {'type': 'django', 'value': 'hello == 1'}
assert not widget.has_error()
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_django': ''})
assert widget.parse() is None
assert not widget.has_error()
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_django': '{{ form_var_xxx }}'})
assert widget.has_error()
assert widget.get_error() == "syntax error: Could not parse the remainder: '{{' from '{{'"
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_python': 'hello == 1', 'test$type': ['python']})
assert widget.parse() == {'type': 'python', 'value': 'hello == 1'}
assert not widget.has_error()
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_python': 'hello~', 'test$type': ['python']})
assert widget.has_error()
assert widget.get_error().startswith('syntax error:')
def test_condition_widget_no_python():
pub.load_site_options()
pub.site_options.set('options', 'disable-python-expressions', 'true')
widget = ConditionWidget('test')
form = Form(method='post', use_tokens=False, enctype='application/x-www-form-urlencoded')
form.widgets.append(widget)
assert '$type' not in str(form.render())
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_django': 'hello == 1'})
assert widget.parse() == {'type': 'django', 'value': 'hello == 1'}
assert not widget.has_error()
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_django': ''})
assert widget.parse() is None
assert not widget.has_error()
widget = ConditionWidget('test')
mock_form_submission(req, widget, {'test$value_django': '{{ form_var_xxx }}'})
assert widget.has_error()
assert widget.get_error() == "syntax error: Could not parse the remainder: '{{' from '{{'"
pub.site_options.set('options', 'disable-python-expressions', 'false')

View File

@ -3333,26 +3333,13 @@ class ComputedExpressionWidget(CompositeWidget):
from wcs.workflows import WorkflowStatusItem
value = WorkflowStatusItem.get_expression(value)
if value.get('type') == 'text':
value['type'] = 'template'
value_placeholder = kwargs.pop('value_placeholder', None)
self.allow_python = kwargs.pop('allow_python', True)
CompositeWidget.__init__(self, name, value, **kwargs)
options = [
('text', _('Text'), 'text'),
('template', _('Template'), 'template'),
]
if self.allow_python and not get_publisher().has_site_option('disable-python-expressions'):
options.append(('python', _('Python Expression (deprecated)'), 'python'))
self.add(
StringWidget,
'value_text',
size=80,
value=value.get('value') if value.get('type') == 'text' else None,
placeholder=value_placeholder,
)
self.add(
StringWidget,
'value_template',
@ -3361,47 +3348,58 @@ class ComputedExpressionWidget(CompositeWidget):
placeholder=value_placeholder,
)
self.add(
StringWidget,
'value_python',
size=80,
value=value.get('value') if value.get('type') == 'python' else None,
placeholder=value_placeholder,
)
self.add(
SingleSelectWidget,
'type',
options=options,
value=value.get('type'),
attrs={'data-dynamic-display-parent': 'true'},
)
self.has_python = False
if self.allow_python and not get_publisher().has_site_option('disable-python-expressions'):
self.has_python = True
self.add(
StringWidget,
'value_python',
size=80,
value=value.get('value') if value.get('type') == 'python' else None,
placeholder=value_placeholder,
)
self.add(
SingleSelectWidget,
'type',
options=[
('template', _('Template'), 'template'),
('python', _('Python Expression (deprecated)'), 'python'),
],
value=value.get('type'),
attrs={'data-dynamic-display-parent': 'true'},
)
def render_content(self):
ctx = {
'name': self.name,
'text_label': _('Text'),
'template_label': _('Template'),
'python_label': _('Python'),
'value_text': self.get_widget('value_text').render_content(),
'value_template': self.get_widget('value_template').render_content(),
'value_python': self.get_widget('value_python').render_content(),
'type': self.get_widget('type').render_content(),
}
if not self.has_python:
return (
htmltext(
'''\
<style>span[data-name="%(name)s"].template::after { content: "%(template_label)s"; }</style>
<span class="template only" data-name="%(name)s">%(value_template)s</span>'''
)
% ctx
)
ctx.update(
{
'python_label': _('Python'),
'type': self.get_widget('type').render_content(),
'value_python': self.get_widget('value_python').render_content(),
}
)
return (
htmltext(
'''
<style>
span[data-name="%(name)s"].text::after { content: "%(text_label)s"; }
span[data-name="%(name)s"].template::after { content: "%(template_label)s"; }
span[data-name="%(name)s"].python::after { content: "%(python_label)s"; }
</style>
<span class="text"
data-name="%(name)s"
data-dynamic-display-value="text"
data-dynamic-display-child-of="%(name)s$type"
>%(value_text)s</span
><span class="template"
<span class="template"
data-name="%(name)s"
data-dynamic-display-value="template"
data-dynamic-display-child-of="%(name)s$type"
@ -3442,9 +3440,12 @@ class ComputedExpressionWidget(CompositeWidget):
def _parse(self, request):
self.value = None
if not self.get('type'):
return
value_type = self.get('type')
if self.has_python:
if not self.get('type'):
return
value_type = self.get('type')
else:
value_type = 'template'
value_content = self.get('value_%s' % value_type)
if value_type == 'python' and value_content:
self.value = '=' + (value_content or '')
@ -3464,12 +3465,7 @@ class ConditionWidget(CompositeWidget):
if not value:
value = {}
options = []
options.append(('django', _('Django Expression'), 'django'))
if kwargs.get('allow_python', True) and not get_publisher().has_site_option(
'disable-python-expressions'
):
options.append(('python', _('Python Expression (deprecated)'), 'python'))
self.has_python = False
self.add(
StringWidget,
@ -3478,20 +3474,26 @@ class ConditionWidget(CompositeWidget):
value=value.get('value') if value.get('type') == 'django' else None,
)
self.add(
StringWidget,
'value_python',
size=80,
value=value.get('value') if value.get('type') == 'python' else None,
)
self.add(
SingleSelectWidget,
'type',
options=options,
value=value.get('type'),
attrs={'data-dynamic-display-parent': 'true'},
)
if kwargs.get('allow_python', True) and not get_publisher().has_site_option(
'disable-python-expressions'
):
self.has_python = True
self.add(
StringWidget,
'value_python',
size=80,
value=value.get('value') if value.get('type') == 'python' else None,
)
self.add(
SingleSelectWidget,
'type',
options=[
('django', _('Django Expression'), 'django'),
('python', _('Python Expression (deprecated)'), 'python'),
],
value=value.get('type'),
attrs={'data-dynamic-display-parent': 'true'},
)
@property
def content_extra_attributes(self):
@ -3500,14 +3502,14 @@ class ConditionWidget(CompositeWidget):
def _parse(self, request):
self.value = None
if not self.get('type') or self.get('type') == 'none':
return
if self.has_python:
if not self.get('type') or self.get('type') == 'none':
return
self.value = {'type': self.get('type')}
else:
self.value = {'type': 'django'}
self.value = {}
self.value['type'] = self.get('type')
if self.value['type']:
self.value['value'] = self.get('value_%s' % self.value['type'])
self.value['value'] = self.get('value_%s' % self.value['type'])
if self.value['value']:
try:
@ -3521,9 +3523,16 @@ class ConditionWidget(CompositeWidget):
ctx = {
'name': self.name,
'value_django': self.get_widget('value_django').render_content(),
'value_python': self.get_widget('value_python').render_content(),
'type': self.get_widget('type').render_content(),
}
if not self.has_python:
return htmltext('<span class="django only">%(value_django)s</span>') % ctx
ctx.update(
{
'value_python': self.get_widget('value_python').render_content(),
'type': self.get_widget('type').render_content(),
}
)
return (
htmltext(
'''

View File

@ -1057,6 +1057,11 @@ div.ConditionWidget div.content input[type=text] {
width: calc(100% - 2.7em);
}
div.ComputedExpressionWidget div.content span.only input[type=text],
div.ConditionWidget div.content span.only input[type=text] {
width: 100%;
}
div.ComputedExpressionWidget div.content span,
div.ConditionWidget div.content span.django,
div.ConditionWidget div.content span.python {