misc: add dynamic autocomplete for <select> of cards (#47152)
This commit is contained in:
parent
342ab5251d
commit
73b138dc64
|
@ -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
|
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):
|
def test_form_data_keywords(pub):
|
||||||
formdef = create_formdef()
|
formdef = create_formdef()
|
||||||
formdef.keywords = 'hello,world'
|
formdef.keywords = 'hello,world'
|
||||||
|
|
13
wcs/api.py
13
wcs/api.py
|
@ -829,11 +829,18 @@ class ApiTrackingCodeDirectory(Directory):
|
||||||
|
|
||||||
class AutocompleteDirectory(Directory):
|
class AutocompleteDirectory(Directory):
|
||||||
def _q_lookup(self, component):
|
def _q_lookup(self, component):
|
||||||
url = get_session().get_data_source_query_url_from_token(component)
|
info = get_session().get_data_source_query_info_from_token(component)
|
||||||
if not url:
|
if not info:
|
||||||
raise AccessForbiddenError()
|
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'])
|
url += urllib.quote(get_request().form['q'])
|
||||||
unsigned_url = url
|
|
||||||
url = sign_url_auto_orig(url)
|
url = sign_url_auto_orig(url)
|
||||||
get_response().set_content_type('application/json')
|
get_response().set_content_type('application/json')
|
||||||
return misc.urlopen(url).read()
|
return misc.urlopen(url).read()
|
||||||
|
|
|
@ -19,7 +19,7 @@ import types
|
||||||
|
|
||||||
from quixote import get_publisher
|
from quixote import get_publisher
|
||||||
from .qommon import _, N_, misc
|
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.carddata import CardData
|
||||||
from wcs.formdef import FormDef
|
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)
|
yield (data_source_id, '%s - %s' % (carddef['name'], custom_view.title), data_source_id)
|
||||||
|
|
||||||
@classmethod
|
@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:')
|
assert data_source_id.startswith('carddef:')
|
||||||
parts = data_source_id.split(':')
|
parts = data_source_id.split(':')
|
||||||
try:
|
try:
|
||||||
|
@ -178,10 +178,13 @@ class CardDef(FormDef):
|
||||||
criterias.extend(form_page.get_view_criterias())
|
criterias.extend(form_page.get_view_criterias())
|
||||||
order_by = custom_view.order_by
|
order_by = custom_view.order_by
|
||||||
|
|
||||||
|
if query:
|
||||||
|
criterias.append(ILike('digest', query))
|
||||||
items = [x.get_data_source_structured_item()
|
items = [x.get_data_source_structured_item()
|
||||||
for x in carddef.data_class().select(
|
for x in carddef.data_class().select(
|
||||||
clause=criterias,
|
clause=criterias,
|
||||||
order_by=order_by)]
|
order_by=order_by,
|
||||||
|
limit=limit)]
|
||||||
if order_by is None:
|
if order_by is None:
|
||||||
items.sort(key=lambda x: misc.simplify(x['text']))
|
items.sort(key=lambda x: misc.simplify(x['text']))
|
||||||
return items
|
return items
|
||||||
|
|
|
@ -335,6 +335,8 @@ class NamedDataSource(XmlStorableObject):
|
||||||
return True
|
return True
|
||||||
if self.type == 'json' and self.query_parameter:
|
if self.type == 'json' and self.query_parameter:
|
||||||
return True
|
return True
|
||||||
|
if self.type and self.type.startswith('carddef:'):
|
||||||
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def migrate(self):
|
def migrate(self):
|
||||||
|
@ -416,7 +418,10 @@ class NamedDataSource(XmlStorableObject):
|
||||||
return self.data_source.get('value')
|
return self.data_source.get('value')
|
||||||
if self.type == 'json' and self.query_parameter:
|
if self.type == 'json' and self.query_parameter:
|
||||||
return '/api/autocomplete/%s' % (
|
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
|
return None
|
||||||
|
|
||||||
def get_value_by_id(self, param_name, param_value):
|
def get_value_by_id(self, param_name, param_value):
|
||||||
|
|
|
@ -142,18 +142,21 @@ class BasicSession(Session):
|
||||||
del session.visiting_objects[object_key]
|
del session.visiting_objects[object_key]
|
||||||
session.store()
|
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:
|
if not self.data_source_query_url_tokens:
|
||||||
self.data_source_query_url_tokens = {}
|
self.data_source_query_url_tokens = {}
|
||||||
for key, value in self.data_source_query_url_tokens.items():
|
for key, value in self.data_source_query_url_tokens.items():
|
||||||
if value == url:
|
if value == info:
|
||||||
return key
|
return key
|
||||||
key = str(uuid.uuid4())
|
key = str(uuid.uuid4())
|
||||||
self.data_source_query_url_tokens[key] = url
|
self.data_source_query_url_tokens[key] = info
|
||||||
self.store()
|
self.store()
|
||||||
return key
|
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:
|
if not self.data_source_query_url_tokens:
|
||||||
return None
|
return None
|
||||||
return self.data_source_query_url_tokens.get(token)
|
return self.data_source_query_url_tokens.get(token)
|
||||||
|
|
Loading…
Reference in New Issue