diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index 5d8f90a8d..2e5a5240f 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -6091,6 +6091,49 @@ def test_item_field_autocomplete_jsonp_source(http_requests, pub): assert '0_structured' not in formdef.data_class().select()[0].data +def test_item_field_autocomplete_cards_source(pub): + user = create_user(pub) + formdef = create_formdef() + formdef.data_class().wipe() + + carddef = CardDef() + carddef.name = 'items' + carddef.digest_template = '{{form_var_name}}' + carddef.fields = [ + fields.StringField(id='0', label='string', varname='name'), + fields.StringField(id='1', label='string', varname='attr'), + ] + carddef.store() + for i, value in enumerate(['foo', 'bar', 'baz']): + carddata = carddef.data_class()() + carddata.data = { + '0': value, + '1': 'attr%s' % i, + } + carddata.just_created() + carddata.store() + + ds = {'type': 'carddef:%s' % carddef.url_name} + formdef.fields = [ + fields.ItemField(id='0', label='string', type='item', + data_source=ds, + display_mode='autocomplete', + ), + ] + formdef.store() + + app = get_app(pub) + resp = app.get('/test/') + select2_url = resp.pyquery('select').attr['data-select2-url'] + resp2 = app.get(select2_url + '?q=ba') + assert [x['text'] for x in resp2.json['data']] == ['bar', 'baz'] + resp.form['f0'].force_value(str(resp2.json['data'][0]['id'])) + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == '2' + assert formdef.data_class().select()[0].data['0_display'] == 'bar' + + def test_form_data_keywords(pub): formdef = create_formdef() formdef.keywords = 'hello,world' diff --git a/wcs/api.py b/wcs/api.py index 4e018e78f..d047160be 100644 --- a/wcs/api.py +++ b/wcs/api.py @@ -829,11 +829,18 @@ class ApiTrackingCodeDirectory(Directory): class AutocompleteDirectory(Directory): def _q_lookup(self, component): - url = get_session().get_data_source_query_url_from_token(component) - if not url: + info = get_session().get_data_source_query_info_from_token(component) + if not info: raise AccessForbiddenError() + get_response().set_content_type('application/json') + if info.startswith('carddef:'): + from wcs.carddef import CardDef + values = CardDef.get_data_source_items(info, + query=get_request().form['q'], + limit=get_request().form.get('page_limit')) + return json.dumps({'data': [{'id': x['id'], 'text': x['text']} for x in values]}) + url = info url += urllib.quote(get_request().form['q']) - unsigned_url = url url = sign_url_auto_orig(url) get_response().set_content_type('application/json') return misc.urlopen(url).read() diff --git a/wcs/carddef.py b/wcs/carddef.py index a076848d4..1783290d5 100644 --- a/wcs/carddef.py +++ b/wcs/carddef.py @@ -19,7 +19,7 @@ import types from quixote import get_publisher from .qommon import _, N_, misc -from .qommon.storage import Equal, NotEqual +from .qommon.storage import Equal, NotEqual, ILike from wcs.carddata import CardData from wcs.formdef import FormDef @@ -154,7 +154,7 @@ class CardDef(FormDef): yield (data_source_id, '%s - %s' % (carddef['name'], custom_view.title), data_source_id) @classmethod - def get_data_source_items(cls, data_source_id): + def get_data_source_items(cls, data_source_id, query=None, limit=None): assert data_source_id.startswith('carddef:') parts = data_source_id.split(':') try: @@ -178,10 +178,13 @@ class CardDef(FormDef): criterias.extend(form_page.get_view_criterias()) order_by = custom_view.order_by + if query: + criterias.append(ILike('digest', query)) items = [x.get_data_source_structured_item() for x in carddef.data_class().select( clause=criterias, - order_by=order_by)] + order_by=order_by, + limit=limit)] if order_by is None: items.sort(key=lambda x: misc.simplify(x['text'])) return items diff --git a/wcs/data_sources.py b/wcs/data_sources.py index 73ab1b716..251948c76 100644 --- a/wcs/data_sources.py +++ b/wcs/data_sources.py @@ -335,6 +335,8 @@ class NamedDataSource(XmlStorableObject): return True if self.type == 'json' and self.query_parameter: return True + if self.type and self.type.startswith('carddef:'): + return True return False def migrate(self): @@ -416,7 +418,10 @@ class NamedDataSource(XmlStorableObject): return self.data_source.get('value') if self.type == 'json' and self.query_parameter: return '/api/autocomplete/%s' % ( - get_session().get_data_source_query_url_token(self.get_json_query_url())) + get_session().get_data_source_query_info_token(self.get_json_query_url())) + if self.type and self.type.startswith('carddef:'): + return '/api/autocomplete/%s' % ( + get_session().get_data_source_query_info_token(self.type)) return None def get_value_by_id(self, param_name, param_value): diff --git a/wcs/sessions.py b/wcs/sessions.py index caf447b60..3ffb612c0 100644 --- a/wcs/sessions.py +++ b/wcs/sessions.py @@ -142,18 +142,21 @@ class BasicSession(Session): del session.visiting_objects[object_key] session.store() - def get_data_source_query_url_token(self, url): + def get_data_source_query_info_token(self, info): + # info can be an URL in case of remote JSON source or a cards + # reference, like carddef:foobar. It is stored in a dictionary + # with _url_ in its name for historical reasons. if not self.data_source_query_url_tokens: self.data_source_query_url_tokens = {} for key, value in self.data_source_query_url_tokens.items(): - if value == url: + if value == info: return key key = str(uuid.uuid4()) - self.data_source_query_url_tokens[key] = url + self.data_source_query_url_tokens[key] = info self.store() return key - def get_data_source_query_url_from_token(self, token): + def get_data_source_query_info_from_token(self, token): if not self.data_source_query_url_tokens: return None return self.data_source_query_url_tokens.get(token)