misc: add dynamic autocomplete for <select> of cards (#47152)

This commit is contained in:
Frédéric Péters 2020-09-29 21:07:22 +02:00
parent 342ab5251d
commit 73b138dc64
5 changed files with 72 additions and 11 deletions

View File

@ -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'

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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)