0001-admin-add-a-python-expression-widged-use-it-in-page-.patch
tests/test_api.py | ||
---|---|---|
1806 | 1806 |
resp = get_app(pub).get('/api/validate-expression?expression==hello[\'plop\']') |
1807 | 1807 |
assert resp.json == {'klass': None, 'msg': ''} |
1808 | 1808 | |
1809 |
def test_validate_python_expression(pub): |
|
1810 |
resp = get_app(pub).get('/api/validate-python-expression?expression=hello') |
|
1811 |
assert resp.json == {'klass': None, 'msg': ''} |
|
1812 |
resp = get_app(pub).get('/api/validate-python-expression?expression=form_var_x == "2"') |
|
1813 |
assert resp.json == {'klass': None, 'msg': ''} |
|
1814 |
resp = get_app(pub).get('/api/validate-python-expression?expression=hello[\'plop\']') |
|
1815 |
assert resp.json == {'klass': None, 'msg': ''} |
|
1816 |
resp = get_app(pub).get('/api/validate-python-expression?expression=[hello]') |
|
1817 |
assert resp.json['klass'] == 'warning' |
|
1818 |
assert resp.json['msg'].startswith('Make sure you use a Python expression') |
|
1819 |
resp = get_app(pub).get('/api/validate-python-expression?expression==hello') |
|
1820 |
assert resp.json['klass'] == 'error' |
|
1821 |
assert resp.json['msg'] == 'Python expression can not start with a = sign.' |
|
1822 |
resp = get_app(pub).get('/api/validate-python-expression?expression=[if-any]coucou[end]') |
|
1823 |
assert resp.json['klass'] == 'error' |
|
1824 |
assert resp.json['msg'].startswith('Python syntax error') |
|
1825 | ||
1809 | 1826 |
@pytest.fixture(params=['sql', 'pickle']) |
1810 | 1827 |
def no_request_pub(request): |
1811 | 1828 |
pub = create_temporary_pub(sql_mode=bool(request.param == 'sql')) |
tests/test_widgets.py | ||
---|---|---|
424 | 424 |
assert widget.has_error() |
425 | 425 |
assert widget.get_error().startswith('error in template') |
426 | 426 | |
427 |
def test_python_expression_widget(): |
|
428 |
widget = PythonExpressionWidget('test') |
|
429 |
form = MockHtmlForm(widget) |
|
430 |
mock_form_submission(req, widget, {'test': '"hello world"'}) |
|
431 |
assert widget.parse() == '"hello world"' |
|
432 |
assert not widget.has_error() |
|
433 | ||
434 |
widget = PythonExpressionWidget('test') |
|
435 |
mock_form_submission(req, widget, {'test': 'hello world'}) |
|
436 |
assert widget.has_error() |
|
437 |
assert widget.get_error().startswith('Python syntax error') |
|
438 | ||
439 |
widget = PythonExpressionWidget('test') |
|
440 |
mock_form_submission(req, widget, {'test': 'form_var_xxx'}) |
|
441 |
assert not widget.has_error() |
|
442 | ||
443 |
widget = PythonExpressionWidget('test') |
|
444 |
mock_form_submission(req, widget, {'test': '=form_var_xxx'}) |
|
445 |
assert widget.has_error() |
|
446 |
assert widget.get_error().startswith('Python syntax error') |
|
447 | ||
448 |
widget = PythonExpressionWidget('test') |
|
449 |
mock_form_submission(req, widget, {'test': 'blouf=3'}) |
|
450 |
assert widget.has_error() |
|
451 |
assert widget.get_error().startswith('Python syntax error') |
|
452 | ||
427 | 453 |
def test_wcsextrastringwidget(): |
428 | 454 |
widget = WcsExtraStringWidget('test', value='foo', required=True) |
429 | 455 |
mock_form_submission(req, widget, {'test': ''}) |
wcs/api.py | ||
---|---|---|
27 | 27 |
from qommon import misc |
28 | 28 |
from qommon.errors import (AccessForbiddenError, QueryError, TraversalError, |
29 | 29 |
UnknownNameIdAccessForbiddenError) |
30 |
from qommon.form import ValidationError, ComputedExpressionWidget |
|
30 |
from qommon.form import ValidationError, ComputedExpressionWidget, PythonExpressionWidget
|
|
31 | 31 | |
32 | 32 |
from wcs.categories import Category |
33 | 33 |
from wcs.formdef import FormDef |
... | ... | |
644 | 644 |
class ApiDirectory(Directory): |
645 | 645 |
_q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'), |
646 | 646 |
'formdefs', 'categories', 'user', 'users', 'code', |
647 |
('validate-expression', 'validate_expression'),] |
|
647 |
('validate-expression', 'validate_expression'), |
|
648 |
('validate-python-expression', 'validate_python_expression'),] |
|
648 | 649 | |
649 | 650 |
forms = ApiFormsDirectory() |
650 | 651 |
formdefs = ApiFormdefsDirectory() |
... | ... | |
699 | 700 |
hint['msg'] = _('Make sure you want a Python expression, not a simple template string.') |
700 | 701 |
return json.dumps(hint) |
701 | 702 | |
703 |
def validate_python_expression(self): |
|
704 |
get_response().set_content_type('application/json') |
|
705 |
expression = get_request().form.get('expression') |
|
706 |
hint = {'klass': None, 'msg': ''} |
|
707 |
if expression and expression.startswith('='): # usual error |
|
708 |
hint['klass'] = 'error' |
|
709 |
hint['msg'] = _('Python expression can not start with a = sign.') |
|
710 |
else: |
|
711 |
try: |
|
712 |
PythonExpressionWidget.validate(expression) |
|
713 |
except ValidationError as e: |
|
714 |
hint['klass'] = 'error' |
|
715 |
hint['msg'] = str(e) |
|
716 |
else: |
|
717 |
if expression and re.match(r'^.*\[[a-zA-Z_]\w*\]', expression): |
|
718 |
hint['klass'] = 'warning' |
|
719 |
hint['msg'] = _('Make sure you use a Python expression and not a template string.') |
|
720 |
return json.dumps(hint) |
|
721 | ||
702 | 722 |
def _q_traverse(self, path): |
703 | 723 |
get_request().is_json_marker = True |
704 | 724 |
return super(ApiDirectory, self)._q_traverse(path) |
wcs/fields.py | ||
---|---|---|
1405 | 1405 |
CompositeWidget.__init__(self, name, value, **kwargs) |
1406 | 1406 |
if not value: |
1407 | 1407 |
value = {} |
1408 |
self.add(StringWidget, name='condition', title=_('Condition'),
|
|
1408 |
self.add(PythonExpressionWidget, name='condition', title=_('Condition'),
|
|
1409 | 1409 |
value=value.get('condition'), size=50) |
1410 | 1410 |
self.add(StringWidget, name='error_message', title=_('Error message if condition is not met'), |
1411 | 1411 |
value=value.get('error_message'), size=50) |
... | ... | |
1468 | 1468 |
def fill_admin_form(self, form): |
1469 | 1469 |
form.add(StringWidget, 'label', title = _('Label'), value = self.label, |
1470 | 1470 |
required = True, size = 50) |
1471 |
form.add(StringWidget, 'condition', title = _('Condition'), value = self.condition,
|
|
1471 |
form.add(PythonExpressionWidget, 'condition', title = _('Condition'), value = self.condition,
|
|
1472 | 1472 |
required = False, size = 50) |
1473 | 1473 |
form.add(PostConditionsTableWidget, 'post_conditions', |
1474 | 1474 |
title=_('Post Conditions'), |
wcs/qommon/form.py | ||
---|---|---|
2222 | 2222 |
'''StringWidget that checks the entered value is a correct workflow |
2223 | 2223 |
expression.''' |
2224 | 2224 | |
2225 |
extra_css_class = 'validate-expression-widget' |
|
2226 |
validation_url = 'api/validate-expression' |
|
2227 | ||
2225 | 2228 |
def render_content(self): |
2226 |
validation_url = get_publisher().get_root_url() + 'api/validate-expression'
|
|
2229 |
validation_url = get_publisher().get_root_url() + self.validation_url
|
|
2227 | 2230 |
self.attrs['data-validation-url'] = validation_url |
2228 | 2231 |
return StringWidget.render_content(self) |
2229 | 2232 | |
... | ... | |
2270 | 2273 |
self.validate(self.value) |
2271 | 2274 |
except ValidationError as e: |
2272 | 2275 |
self.set_error(str(e)) |
2276 | ||
2277 | ||
2278 |
class PythonExpressionWidget(ComputedExpressionWidget): |
|
2279 |
'''StringWidget that checks the entered value is a correct Python expression.''' |
|
2280 | ||
2281 |
validation_url = 'api/validate-python-expression' |
|
2282 | ||
2283 |
@classmethod |
|
2284 |
def validate(cls, expression): |
|
2285 |
if not expression: |
|
2286 |
return |
|
2287 |
try: |
|
2288 |
compile(expression, '<string>', 'eval') |
|
2289 |
except SyntaxError as e: |
|
2290 |
raise ValidationError(_('Python syntax error (%s)') % e) |
wcs/qommon/static/css/dc2/admin.css | ||
---|---|---|
1291 | 1291 |
margin-bottom: 1ex; |
1292 | 1292 |
} |
1293 | 1293 | |
1294 |
div.ComputedExpressionWidget div.content {
|
|
1294 |
div.validate-expression-widget div.content {
|
|
1295 | 1295 |
position: relative; |
1296 | 1296 |
} |
1297 | 1297 | |
1298 |
div.ComputedExpressionWidget div.content input, |
|
1299 |
div.ComputedExpressionWidget div.content input:focus { |
|
1300 |
border-left-width: 3ex; |
|
1301 |
} |
|
1302 | ||
1303 |
div.ComputedExpressionWidget.hint-error div.content input { |
|
1298 |
div.validate-expression-widget.hint-error div.content input { |
|
1304 | 1299 |
border-color: red; |
1305 | 1300 |
} |
1306 | 1301 | |
1307 |
div.ComputedExpressionWidget.hint-warning div.content input {
|
|
1302 |
div.validate-expression-widget.hint-warning div.content input {
|
|
1308 | 1303 |
border-color: orange; |
1309 | 1304 |
} |
1310 | 1305 | |
1311 |
div.ComputedExpressionWidget.hint-error div.content span.hint-text,
|
|
1312 |
div.ComputedExpressionWidget.hint-warning div.content span.hint-text {
|
|
1306 |
div.validate-expression-widget.hint-error div.content span.hint-text,
|
|
1307 |
div.validate-expression-widget.hint-warning div.content span.hint-text {
|
|
1313 | 1308 |
display: block; |
1314 | 1309 |
} |
1315 | 1310 | |
1316 |
div.ComputedExpressionWidget div.content::before { |
|
1317 |
font-family: FontAwesome; |
|
1318 |
content: "\f1b2"; |
|
1311 |
div.validate-expression-widget div.content::before { |
|
1319 | 1312 |
position: absolute; |
1320 | 1313 |
top: 8px; |
1321 | 1314 |
left: 6px; |
... | ... | |
1323 | 1316 |
border: 1px solid transparent; |
1324 | 1317 |
} |
1325 | 1318 | |
1319 |
div.ComputedExpressionWidget div.content input, |
|
1320 |
div.ComputedExpressionWidget div.content input:focus { |
|
1321 |
border-left-width: 3ex; |
|
1322 |
} |
|
1323 | ||
1324 |
div.ComputedExpressionWidget div.content::before { |
|
1325 |
font-family: FontAwesome; |
|
1326 |
content: "\f1b2"; |
|
1327 |
} |
|
1328 | ||
1329 |
div.PythonExpressionWidget div.content input, |
|
1330 |
div.PythonExpressionWidget div.content input:focus { |
|
1331 |
border-left-width: 7ex; |
|
1332 |
} |
|
1333 | ||
1334 |
div.PythonExpressionWidget div.content::before { |
|
1335 |
content: "Python"; |
|
1336 |
} |
|
1337 | ||
1326 | 1338 |
div.admin-permissions thead th { |
1327 | 1339 |
transform: rotate(-45deg); |
1328 | 1340 |
transform-origin: 10% 0; |
wcs/qommon/static/js/qommon.admin.js | ||
---|---|---|
41 | 41 |
var validation_timeout_id = 0; |
42 | 42 |
$('input[data-validation-url]').on('change focus keyup', function() { |
43 | 43 |
var val = $(this).val(); |
44 |
var $widget = $(this).parents('.ComputedExpressionWidget');
|
|
44 |
var $widget = $(this).parents('.validate-expression-widget');
|
|
45 | 45 |
var validation_url = $(this).data('validation-url'); |
46 | 46 |
clearTimeout(validation_timeout_id); |
47 | 47 |
validation_timeout_id = setTimeout(function() { |
48 |
- |