0001-misc-add-a-jsonp-endpoint-for-datasources-use-it-for.patch
tests/test_admin_pages.py | ||
---|---|---|
942 | 942 |
resp.body.index('<label for="form_data_source">Data Source</label>') |
943 | 943 | |
944 | 944 |
# start filling the "data source" field |
945 |
resp.forms[0]['data_source$type'] = 'JSON URL' |
|
945 |
resp.forms[0]['data_source$type'] = 'JSONP URL'
|
|
946 | 946 |
resp = resp.forms[0].submit('data_source$apply') |
947 | 947 | |
948 | 948 |
# it should now appear before the additional parameters section |
tests/test_api.py | ||
---|---|---|
5 | 5 |
import hmac |
6 | 6 |
import base64 |
7 | 7 |
import hashlib |
8 |
import re |
|
8 | 9 |
import urllib |
9 | 10 |
import urlparse |
10 | 11 |
import datetime |
... | ... | |
1443 | 1444 |
secret, orig = get_secret_and_orig('https://api.example.com/endpoint/') |
1444 | 1445 |
assert secret == '1234' |
1445 | 1446 |
assert orig == 'example.net' |
1447 | ||
1448 |
def test_datasources_jsonp(pub): |
|
1449 |
NamedDataSource.wipe() |
|
1450 |
data_source = NamedDataSource(name='foobar') |
|
1451 |
source = [{'id': '1', 'text': 'foo', 'more': 'XXX'}, |
|
1452 |
{'id': '2', 'text': 'bar', 'more': 'YYY'}] |
|
1453 |
data_source.data_source = {'type': 'formula', 'value': repr(source)} |
|
1454 |
data_source.store() |
|
1455 | ||
1456 |
get_app(pub).get('/api/datasources/xxx', status=404) |
|
1457 |
get_app(pub).get('/api/datasources/xxx/', status=404) |
|
1458 |
get_app(pub).get('/api/datasources/foobar/', status=403) |
|
1459 | ||
1460 |
FormDef.wipe() |
|
1461 |
formdef = FormDef() |
|
1462 |
formdef.name = 'test' |
|
1463 |
formdef.fields = [ |
|
1464 |
fields.StringField(id='0', label='foobar0', varname='foobar0', |
|
1465 |
data_source={'type': 'foobar'}), |
|
1466 |
] |
|
1467 |
formdef.store() |
|
1468 | ||
1469 |
get_app(pub).get('/api/datasources/foobar/12122', status=403) |
|
1470 | ||
1471 |
app = get_app(pub) |
|
1472 |
resp = app.get('/test/') |
|
1473 |
url = re.findall(r"'(/api/datasou.*)'", resp.body)[0] |
|
1474 | ||
1475 |
resp = app.get(url) |
|
1476 |
assert len(resp.json['data']) == 2 |
|
1477 |
resp = app.get(url + '?q=fo') |
|
1478 |
resp_data = resp.body |
|
1479 |
assert len(resp.json['data']) == 1 |
|
1480 |
resp = app.get(url + '?q=fo&callback=cb123') |
|
1481 |
assert resp_data in resp.body |
|
1482 |
assert resp.body.startswith('cb123(') |
|
1483 | ||
1484 |
# check accessing the URL from another session |
|
1485 |
get_app(pub).get(url, status=403) |
|
1486 |
app2 = get_app(pub) |
|
1487 |
resp2 = app2.get('/test/') |
|
1488 |
app2.get(url, status=403) |
|
1489 | ||
1490 |
# test custom handling of jsonp sources (redirect) |
|
1491 |
data_source.data_source = {'type': 'jsonp', 'value': 'http://remote.example.net/json'} |
|
1492 |
data_source.store() |
|
1493 |
resp = app.get(url + '?q=fo&callback=cb123') |
|
1494 |
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 | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import hashlib |
|
17 | 18 |
import json |
18 | 19 |
import time |
19 | 20 |
import urllib2 |
20 | 21 |
import sys |
21 | 22 | |
22 |
from quixote import get_request, get_publisher, get_response |
|
23 |
from quixote import get_request, get_publisher, get_response, get_session, redirect
|
|
23 | 24 |
from quixote.directory import Directory |
24 | 25 |
from qommon import misc |
25 | 26 |
from qommon.errors import (AccessForbiddenError, QueryError, TraversalError, |
26 | 27 |
UnknownNameIdAccessForbiddenError) |
27 | 28 | |
28 | 29 |
from wcs.categories import Category |
30 |
from wcs.data_sources import NamedDataSource |
|
29 | 31 |
from wcs.formdef import FormDef |
30 | 32 |
from wcs.roles import Role, logged_users_role |
31 | 33 |
from wcs.forms.common import FormStatusPage |
... | ... | |
576 | 578 |
return json.dumps(data) |
577 | 579 | |
578 | 580 | |
581 |
class ApiDataSourceDirectory(Directory): |
|
582 |
def __init__(self, datasource): |
|
583 |
self.datasource = datasource |
|
584 | ||
585 |
def _q_lookup(self, component): |
|
586 |
if not get_session() or not get_session().session_id: |
|
587 |
raise AccessForbiddenError() |
|
588 |
if component != hashlib.md5('%s:%s' % (self.datasource.slug, get_session().session_id)).hexdigest(): |
|
589 |
raise AccessForbiddenError() |
|
590 |
dtype = self.datasource.data_source.get('type') |
|
591 |
if dtype == 'jsonp': |
|
592 |
# redirect to the source |
|
593 |
url = self.datasource.data_source.get('value') |
|
594 |
if not '?' in url: |
|
595 |
url += '?' |
|
596 |
url += get_request().get_query() |
|
597 |
return redirect(url) |
|
598 |
query = get_request().form.get('q', '').lower() |
|
599 |
items = [x[-1] for x in self.datasource.get_items() if query in x[1].lower()] |
|
600 |
return misc.json_response({'data': items}) |
|
601 | ||
602 | ||
603 |
class ApiDataSourcesDirectory(Directory): |
|
604 |
def _q_lookup(self, component): |
|
605 |
try: |
|
606 |
return ApiDataSourceDirectory(NamedDataSource.get_by_slug(component)) |
|
607 |
except KeyError: |
|
608 |
raise TraversalError() |
|
609 | ||
610 | ||
579 | 611 |
class ApiDirectory(Directory): |
580 | 612 |
_q_exports = ['forms', 'roles', ('reverse-geocoding', 'reverse_geocoding'), |
581 |
'formdefs', 'categories', 'user', 'users', 'code'] |
|
613 |
'formdefs', 'categories', 'user', 'users', 'code', |
|
614 |
'datasources'] |
|
582 | 615 | |
583 | 616 |
forms = ApiFormsDirectory() |
584 | 617 |
formdefs = ApiFormdefsDirectory() |
... | ... | |
586 | 619 |
user = ApiUserDirectory() |
587 | 620 |
users = ApiUsersDirectory() |
588 | 621 |
code = ApiTrackingCodeDirectory() |
622 |
datasources = ApiDataSourcesDirectory() |
|
589 | 623 | |
590 | 624 |
def reverse_geocoding(self): |
591 | 625 |
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 | ||
---|---|---|
19 | 19 |
import random |
20 | 20 |
import re |
21 | 21 |
import base64 |
22 |
import hashlib |
|
22 | 23 |
import xml.etree.ElementTree as ET |
23 | 24 |
import collections |
24 | 25 |
from HTMLParser import HTMLParser |
25 | 26 | |
26 |
from quixote import get_request, get_publisher |
|
27 |
from quixote import get_request, get_publisher, get_session_manager
|
|
27 | 28 |
from quixote.html import htmltext, TemplateIO |
28 | 29 | |
29 | 30 |
from qommon.form import * |
... | ... | |
564 | 565 |
if real_data_source.get('type') == 'jsonp': |
565 | 566 |
kwargs['url'] = real_data_source.get('value') |
566 | 567 |
self.widget_class = AutocompleteStringWidget |
568 |
elif self.data_source.get('type') not in ('none', 'formula', 'json'): |
|
569 |
# named data source, pass a token to assert the user is |
|
570 |
# allowed to access the data. |
|
571 |
session = get_session() |
|
572 |
if not session.session_id: |
|
573 |
# this require a session to exist |
|
574 |
session.get_anonymous_key(generate=True) |
|
575 |
get_session_manager().maintain_session(session) |
|
576 |
token = hashlib.md5('%s:%s' % (self.data_source.get('type'), get_session().session_id)).hexdigest() |
|
577 |
kwargs['url'] = get_publisher().get_root_url() + 'api/datasources/%s/%s' % ( |
|
578 |
self.data_source.get('type'), token) |
|
579 |
self.widget_class = AutocompleteStringWidget |
|
567 | 580 | |
568 | 581 |
def fill_admin_form(self, form): |
569 | 582 |
WidgetField.fill_admin_form(self, form) |
... | ... | |
573 | 586 |
value=self.validation, advanced=(not self.validation)) |
574 | 587 |
form.add(data_sources.DataSourceSelectionWidget, 'data_source', |
575 | 588 |
value=self.data_source, |
589 |
require_jsonp=True, |
|
576 | 590 |
title=_('Data Source'), |
577 | 591 |
hint=_('This will allow autocompletion from an external source.'), |
578 | 592 |
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 |
- |