Projet

Général

Profil

0001-misc-add-a-jsonp-endpoint-for-datasources-use-it-for.patch

Frédéric Péters, 09 octobre 2016 11:00

Télécharger (8,53 ko)

Voir les différences:

Subject: [PATCH] misc: add a jsonp endpoint for datasources, use it for
 autocomplete (#10990)

 tests/test_api.py        | 25 +++++++++++++++++++++++++
 tests/test_form_pages.py | 12 +++++++++++-
 wcs/api.py               | 35 +++++++++++++++++++++++++++++++++--
 wcs/data_sources.py      | 12 ++++++++----
 wcs/fields.py            |  5 +++++
 wcs/qommon/form.py       |  3 +--
 6 files changed, 83 insertions(+), 9 deletions(-)
tests/test_api.py
1438 1438
    secret, orig = get_secret_and_orig('https://api.example.com/endpoint/')
1439 1439
    assert secret == '1234'
1440 1440
    assert orig == 'example.net'
1441

  
1442
def test_datasources_jsonp(pub):
1443
    NamedDataSource.wipe()
1444
    data_source = NamedDataSource(name='foobar')
1445
    source = [{'id': '1', 'text': 'foo', 'more': 'XXX'},
1446
              {'id': '2', 'text': 'bar', 'more': 'YYY'}]
1447
    data_source.data_source = {'type': 'formula', 'value': repr(source)}
1448
    data_source.store()
1449

  
1450
    get_app(pub).get('/api/datasources/xxx', status=404)
1451
    get_app(pub).get('/api/datasources/xxx/', status=404)
1452
    resp = get_app(pub).get('/api/datasources/foobar/')
1453
    assert len(resp.json['data']) == 2
1454
    resp = get_app(pub).get('/api/datasources/foobar/?q=fo')
1455
    resp_data = resp.body
1456
    assert len(resp.json['data']) == 1
1457
    resp = get_app(pub).get('/api/datasources/foobar/?q=fo&callback=cb123')
1458
    assert resp_data in resp.body
1459
    assert resp.body.startswith('cb123(')
1460

  
1461
    # test custom handling of jsonp sources (redirect)
1462
    data_source.data_source = {'type': 'jsonp', 'value': 'http://remote.example.net/json'}
1463
    data_source.store()
1464
    resp = get_app(pub).get('/api/datasources/foobar/?q=fo&callback=cb123')
1465
    assert resp.location == 'http://remote.example.net/json?q=fo&callback=cb123'
tests/test_form_pages.py
3080 3080
    assert not hasattr(formdata.data['0'], 'metadata')
3081 3081
    assert not '0_structured' in formdata.data
3082 3082

  
3083

  
3084 3083
def test_form_string_field_autocomplete(pub):
3085 3084
    formdef = create_formdef()
3086 3085
    formdef.fields = [fields.StringField(id='0', label='string', type='string', required=False)]
......
3105 3104
    assert ').autocomplete({' in resp.body
3106 3105
    assert 'http://example.net' in resp.body
3107 3106

  
3107
    # named data source
3108
    NamedDataSource.wipe()
3109
    data_source = NamedDataSource(name='foobar')
3110
    data_source.data_source = {'type': 'formula', 'value': repr([])}
3111
    data_source.store()
3112
    formdef.fields[0].data_source = {'type': 'foobar', 'value': ''}
3113
    formdef.store()
3114
    resp = get_app(pub).get('/test/')
3115
    assert ').autocomplete({' in resp.body
3116
    assert '/api/datasources/foobar/' in resp.body
3117

  
3108 3118
def test_form_workflow_trigger(pub):
3109 3119
    user = create_user(pub)
3110 3120

  
wcs/api.py
19 19
import urllib2
20 20
import sys
21 21

  
22
from quixote import get_request, get_publisher, get_response
22
from quixote import get_request, get_publisher, get_response, redirect
23 23
from quixote.directory import Directory
24 24
from qommon import misc
25 25
from qommon.errors import (AccessForbiddenError, QueryError, TraversalError,
26 26
    UnknownNameIdAccessForbiddenError)
27 27

  
28 28
from wcs.categories import Category
29
from wcs.data_sources import NamedDataSource
29 30
from wcs.formdef import FormDef
30 31
from wcs.roles import Role, logged_users_role
31 32
from wcs.forms.common import FormStatusPage
......
569 570
        return json.dumps(data)
570 571

  
571 572

  
573
class ApiDataSourceDirectory(Directory):
574
    _q_exports = ['']
575

  
576
    def __init__(self, datasource):
577
        self.datasource = datasource
578

  
579
    def _q_index(self):
580
        dtype = self.datasource.data_source.get('type')
581
        if dtype == 'jsonp':
582
            # redirect to the source
583
            url = self.datasource.data_source.get('value')
584
            if not '?' in url:
585
                url += '?'
586
            url += get_request().get_query()
587
            return redirect(url)
588
        query = get_request().form.get('q', '').lower()
589
        items = [x[-1] for x in self.datasource.get_items() if query in x[1].lower()]
590
        return misc.json_response({'data': items})
591

  
592

  
593
class ApiDataSourcesDirectory(Directory):
594
    def _q_lookup(self, component):
595
        try:
596
            return ApiDataSourceDirectory(NamedDataSource.get_by_slug(component))
597
        except KeyError:
598
            raise TraversalError()
599

  
600

  
572 601
class ApiDirectory(Directory):
573 602
    _q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'),
574
            'formdefs', 'categories', 'user', 'users', 'code']
603
            'formdefs', 'categories', 'user', 'users', 'code',
604
            'datasources']
575 605

  
576 606
    forms = ApiFormsDirectory()
577 607
    formdefs = ApiFormdefsDirectory()
......
579 609
    user = ApiUserDirectory()
580 610
    users = ApiUsersDirectory()
581 611
    code = ApiTrackingCodeDirectory()
612
    datasources = ApiDataSourcesDirectory()
582 613

  
583 614
    def reverse_geocoding(self):
584 615
        try:
wcs/data_sources.py
39 39

  
40 40
class DataSourceSelectionWidget(CompositeWidget):
41 41
    def __init__(self, name, value=None, allow_jsonp=True,
42
            allow_named_sources=True, **kwargs):
42
            allow_named_sources=True, require_jsonp=False, **kwargs):
43 43
        CompositeWidget.__init__(self, name, value, **kwargs)
44 44

  
45 45
        if not value:
46 46
            value = {}
47 47

  
48
        options = [('none', _('None')),
49
                   ('formula', _('Python Expression')),
50
                   ('json', _('JSON URL'))]
48
        options = [('none', _('None'))]
49
        if not require_jsonp:
50
            options.append(('formula', _('Python Expression')))
51
            options.append(('json', _('JSON URL')))
51 52
        if allow_jsonp:
52 53
            options.append(('jsonp', _('JSONP URL')))
53 54
        if allow_named_sources:
......
245 246
    def get_substitution_variables(cls):
246 247
        return {'data_source': DataSourcesSubstitutionProxy()}
247 248

  
249
    def get_items(self):
250
        return get_items(self.data_source)
251

  
248 252

  
249 253
class DataSourcesSubstitutionProxy(object):
250 254
    def __getattr__(self, attr):
wcs/fields.py
564 564
            if real_data_source.get('type') == 'jsonp':
565 565
                kwargs['url'] = real_data_source.get('value')
566 566
                self.widget_class = AutocompleteStringWidget
567
            elif self.data_source.get('type') not in ('none', 'formula', 'json'):
568
                # named data source
569
                kwargs['url'] = root_url = get_publisher().get_root_url() + 'api/datasources/%s/' % self.data_source.get('type')
570
                self.widget_class = AutocompleteStringWidget
567 571

  
568 572
    def fill_admin_form(self, form):
569 573
        WidgetField.fill_admin_form(self, form)
......
573 577
                value=self.validation, advanced=(not self.validation))
574 578
        form.add(data_sources.DataSourceSelectionWidget, 'data_source',
575 579
                 value=self.data_source,
580
                 require_jsonp=True,
576 581
                 title=_('Data Source'),
577 582
                 hint=_('This will allow autocompletion from an external source.'),
578 583
                 advanced=is_datasource_advanced(self.data_source),
wcs/qommon/form.py
1972 1972
    url = None
1973 1973

  
1974 1974
    def __init__(self, *args, **kwargs):
1975
        self.url = kwargs.pop('url', None)
1975 1976
        WcsExtraStringWidget.__init__(self, *args, **kwargs)
1976
        if kwargs.get('url'):
1977
            self.url = kwargs.get('url')
1978 1977

  
1979 1978
    def render_content(self):
1980 1979
        get_response().add_javascript(['jquery.js', 'jquery-ui.js'])
1981
-