Projet

Général

Profil

0001-admin-add-a-python-expression-widged-use-it-in-page-.patch

Thomas Noël, 02 octobre 2017 23:02

Télécharger (11,1 ko)

Voir les différences:

Subject: [PATCH] admin: add a python expression widged, use it in page
 conditions (#19113)

 tests/test_api.py                    | 17 ++++++++++++++++
 tests/test_widgets.py                | 26 ++++++++++++++++++++++++
 wcs/api.py                           | 24 +++++++++++++++++++++--
 wcs/fields.py                        |  4 ++--
 wcs/qommon/form.py                   | 20 ++++++++++++++++++-
 wcs/qommon/static/css/dc2/admin.css  | 38 ++++++++++++++++++++++++------------
 wcs/qommon/static/js/qommon.admin.js |  2 +-
 7 files changed, 112 insertions(+), 19 deletions(-)
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
-