Projet

Général

Profil

0001-admin-add-live-validation-hints-to-computed-expressi.patch

Frédéric Péters, 18 octobre 2016 14:31

Télécharger (8,47 ko)

Voir les différences:

Subject: [PATCH] admin: add live validation (+ hints) to computed expression
 widgets (#13650)

 tests/test_api.py                    | 12 +++++++++++
 tests/test_widgets.py                |  2 +-
 wcs/api.py                           | 19 +++++++++++++++++-
 wcs/qommon/form.py                   | 39 ++++++++++++++++++++++++------------
 wcs/qommon/static/css/dc2/admin.css  | 13 ++++++++++++
 wcs/qommon/static/js/qommon.admin.js | 24 ++++++++++++++++++++++
 6 files changed, 94 insertions(+), 15 deletions(-)
tests/test_api.py
1485 1485
    formdata.remove_self()
1486 1486
    resp = get_app(pub).get('/api/code/%s' % code.id, status=404)
1487 1487

  
1488
def test_validate_expression(pub):
1489
    resp = get_app(pub).get('/api/validate-expression?expression=hello')
1490
    assert resp.json == {'klass': None, 'msg': ''}
1491
    resp = get_app(pub).get('/api/validate-expression?expression=[hello]')
1492
    assert resp.json == {'klass': None, 'msg': ''}
1493
    resp = get_app(pub).get('/api/validate-expression?expression==[hello')
1494
    assert resp.json['klass'] == 'error'
1495
    assert resp.json['msg'].startswith('syntax error')
1496
    resp = get_app(pub).get('/api/validate-expression?expression==[hello]')
1497
    assert resp.json['klass'] == 'warning'
1498
    assert resp.json['msg'].startswith('Make sure you want a Python expression,')
1499

  
1488 1500
@pytest.fixture(params=['sql', 'pickle'])
1489 1501
def no_request_pub(request):
1490 1502
    pub = create_temporary_pub(sql_mode=bool(request.param == 'sql'))
tests/test_widgets.py
21 21
    global pub, req
22 22
    pub = create_temporary_pub()
23 23

  
24
    req = HTTPRequest(None, {})
24
    req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'})
25 25
    req.language = None
26 26
    pub._set_request(req)
27 27

  
wcs/api.py
16 16

  
17 17
import hashlib
18 18
import json
19
import re
19 20
import time
20 21
import urllib2
21 22
import sys
......
25 26
from qommon import misc
26 27
from qommon.errors import (AccessForbiddenError, QueryError, TraversalError,
27 28
    UnknownNameIdAccessForbiddenError)
29
from qommon.form import ValidationError, ComputedExpressionWidget
28 30

  
29 31
from wcs.categories import Category
30 32
from wcs.data_sources import NamedDataSource
......
611 613
class ApiDirectory(Directory):
612 614
    _q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'),
613 615
            'formdefs', 'categories', 'user', 'users', 'code',
614
            'datasources']
616
            'datasources', ('validate-expression', 'validate_expression'),]
615 617

  
616 618
    forms = ApiFormsDirectory()
617 619
    formdefs = ApiFormdefsDirectory()
......
648 650
        get_response().set_content_type('application/json')
649 651
        return json.dumps({'data': list_roles})
650 652

  
653
    def validate_expression(self):
654
        get_response().set_content_type('application/json')
655
        expression = get_request().form.get('expression')
656
        hint = {'klass': None, 'msg': ''}
657
        try:
658
            ComputedExpressionWidget.validate(expression)
659
        except ValidationError as e:
660
            hint['klass'] = 'error'
661
            hint['msg'] = str(e)
662
        else:
663
            if expression and re.match(r'^=.*\[\w+\]', expression):
664
                hint['klass'] = 'warning'
665
                hint['msg'] = _('Make sure you want a Python expression, not a simple template string.')
666
        return json.dumps(hint)
667

  
651 668
    def _q_traverse(self, path):
652 669
        get_request().is_json_marker = True
653 670
        return super(ApiDirectory, self)._q_traverse(path)
wcs/qommon/form.py
200 200
SubmitWidget.render_content = submit_render_content
201 201

  
202 202

  
203
class ValidationError(ValueError):
204
    pass
205

  
206

  
203 207
class RadiobuttonsWidget(quixote.form.RadiobuttonsWidget):
204 208
    def __init__(self, name, value=None, **kwargs):
205 209
        self.options_with_attributes = kwargs.pop('options_with_attributes', None)
......
2303 2307
    '''StringWidget that checks the entered value is a correct workflow
2304 2308
       expression.'''
2305 2309

  
2310
    def render_content(self):
2311
        validation_url = get_publisher().get_root_url() + 'api/validate-expression'
2312
        self.attrs['data-validation-url'] = validation_url
2313
        return StringWidget.render_content(self)
2314

  
2306 2315
    @classmethod
2307 2316
    def validate_ezt(cls, template):
2308 2317
        processor = ezt.Template(compress_whitespace=False)
......
2325 2334
                parts.append(_('at line %(line)d and column %(column)d') % {
2326 2335
                    'line': e.line+1,
2327 2336
                    'column': e.column+1})
2328
            raise ValueError(_('error in template (%s)') % ' '.join(parts))
2337
            raise ValidationError(_('error in template (%s)') % ' '.join(parts))
2338

  
2339
    @classmethod
2340
    def validate(cls, expression):
2341
        if not expression:
2342
            return
2343
        if expression.startswith('=') and len(expression) > 1:
2344
            try:
2345
                compile(expression[1:], '<string>', 'eval')
2346
            except SyntaxError as e:
2347
                raise ValidationError(_('syntax error (%s)') % e)
2348
        else:
2349
            cls.validate_ezt(expression)
2329 2350

  
2330 2351
    def _parse(self, request):
2331 2352
        StringWidget._parse(self, request)
2332 2353
        if self.value:
2333
            if self.value.startswith('='):
2334
                # python expression
2335
                try:
2336
                    compile(self.value[1:], '<string>', 'eval')
2337
                except SyntaxError as e:
2338
                    self.set_error(_('syntax error (%s)') % e)
2339
            else:
2340
                # ezt expression
2341
                try:
2342
                    self.validate_ezt(self.value)
2343
                except ValueError as e:
2344
                    self.set_error(str(e))
2354
            try:
2355
                self.validate(self.value)
2356
            except ValidationError as e:
2357
                self.set_error(str(e))
wcs/qommon/static/css/dc2/admin.css
1253 1253
	border-left-width: 3ex;
1254 1254
}
1255 1255

  
1256
div.ComputedExpressionWidget.hint-error div.content input {
1257
	border-color: red;
1258
}
1259

  
1260
div.ComputedExpressionWidget.hint-warning div.content input {
1261
	border-color: orange;
1262
}
1263

  
1264
div.ComputedExpressionWidget.hint-error div.content span.hint-text,
1265
div.ComputedExpressionWidget.hint-warning div.content span.hint-text {
1266
	display: block;
1267
}
1268

  
1256 1269
div.ComputedExpressionWidget div.content::before {
1257 1270
	font-family: FontAwesome;
1258 1271
	content: "\f1b2";
wcs/qommon/static/js/qommon.admin.js
37 37
        });
38 38
    });
39 39

  
40
    /* hints on the computed expression widget */
41
    var validation_timeout_id = 0;
42
    $('input[data-validation-url]').on('change focus keyup', function() {
43
      var val = $(this).val();
44
      var $widget = $(this).parents('.ComputedExpressionWidget');
45
      var validation_url = $(this).data('validation-url');
46
      clearTimeout(validation_timeout_id);
47
      validation_timeout_id = setTimeout(function() {
48
        $.ajax({
49
          url: validation_url,
50
          data: {expression: val},
51
          dataType: 'json',
52
          success: function(data) {
53
            $widget.removeClass('hint-warning');
54
            $widget.removeClass('hint-error');
55
            if (data.klass) {
56
              $widget.addClass('hint-' + data.klass);
57
            }
58
            $widget.prop('title', data.msg);
59
          }
60
        })}, 250);
61
      return false;
62
    });
63

  
40 64
    /* keep sidebar sticky */
41 65
    if ($('#sidebar').length) {
42 66
      var $window = $(window);
43
-