trier par pertinence les résultats d'une recherche full text (#16253) #1063

Merged
fpeters merged 2 commits from wip/16253-order-by-rank-on-query into main 2024-01-26 08:47:27 +01:00
7 changed files with 83 additions and 37 deletions

View File

@ -2697,7 +2697,7 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user, auth):
sign_uri(url, user=local_user, orig=access.access_identifier, key=access.access_key), **kwargs
)
resp = get_url('/api/forms/test/list?full=on')
resp = get_url('/api/forms/test/list?full=on&order_by=id')
assert len(resp.json) == 10
assert resp.json[0]['fields']['foobar'] == 'FOO BAR1'
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'
@ -2715,7 +2715,7 @@ def test_api_access_restrict_to_anonymised_data(pub, local_user, auth):
access.restrict_to_anonymised_data = True
access.store()
resp = get_url('/api/forms/test/list?full=on')
resp = get_url('/api/forms/test/list?full=on&order_by=id')
assert len(resp.json) == 10
assert 'foobar' not in resp.json[0]['fields']
assert resp.json[0]['fields']['foobar2'] == 'FOO BAR 2'

View File

@ -537,7 +537,6 @@ def test_backoffice_listing_fts(pub):
assert resp.text.count('data-link') == 17
resp = app.get('/backoffice/management/form-title/')
resp.forms['listing-settings']['filter'] = 'all'
resp.forms['listing-settings']['q'] = 'foo'
resp.forms['listing-settings']['limit'] = '100'
resp = resp.forms['listing-settings'].submit()
assert resp.pyquery('tbody tr').length == 50
@ -545,9 +544,29 @@ def test_backoffice_listing_fts(pub):
'%s-%s' % (formdef.id, i) for i in range(50, 0, -1)
]
# search on text (foo is on all formdata so it gets the same set of results, but ordered differently)
resp.forms['listing-settings']['q'] = 'foo'
resp = resp.forms['listing-settings'].submit()
assert resp.pyquery('tbody tr').length == 50
assert {x.text for x in resp.pyquery('tbody tr .cell-id a')} == {
'%s-%s' % (formdef.id, i) for i in range(50, 0, -1)
} # same set
assert [x.text for x in resp.pyquery('tbody tr .cell-id a')] != [
'%s-%s' % (formdef.id, i) for i in range(50, 0, -1)
] # but different order
# get first row, check it has b'foo' in its item field
formdata = formdef.data_class().get(resp.pyquery('tbody tr .cell-id a')[0].attrib['href'].strip('/'))
assert formdata.data[formdef.fields[1].id] == 'foo'
resp.forms['listing-settings']['q'] = 'baz'
resp = resp.forms['listing-settings'].submit()
assert resp.pyquery('tbody tr').length == 24
results = [x.text for x in resp.pyquery('tbody tr .cell-id a')]
# force order, check it's same set but different order
resp.forms['listing-settings']['order_by'] = '-receipt_time'
resp = resp.forms['listing-settings'].submit()
assert {x.text for x in resp.pyquery('tbody tr .cell-id a')} == set(results)
assert [x.text for x in resp.pyquery('tbody tr .cell-id a')] != results
def test_backoffice_legacy_urls(pub):

View File

@ -1173,21 +1173,25 @@ def test_backoffice_custom_view_sort_field(pub):
items=['foo', 'bar', 'baz'],
display_locations=['validation', 'summary', 'listings'],
),
fields.StringField(
id='2',
label='field 2',
),
]
formdef.workflow_roles = {'_receiver': 1}
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.data = {'1': 'foo', '1_display': 'foo'}
formdata.data = {'1': 'foo', '1_display': 'foo', '2': 'foo foo'}
formdata.jump_status('new')
formdata.store()
formdata = formdef.data_class()()
formdata.data = {'1': 'bar', '1_display': 'bar'}
formdata.data = {'1': 'bar', '1_display': 'bar', '2': 'foo foo'}
formdata.jump_status('new')
formdata.store()
formdata = formdef.data_class()()
formdata.data = {'1': 'baz', '1_display': 'baz'}
formdata.data = {'1': 'baz', '1_display': 'baz', '2': 'foo'}
formdata.jump_status('new')
formdata.store()
@ -1220,6 +1224,19 @@ def test_backoffice_custom_view_sort_field(pub):
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
assert resp.text.count('<tr') == 4
# check rank takes over when searching on text
custom_view.order_by = '-f1'
custom_view.store()
resp = app.get('/backoffice/management/form-title/shared-custom-test-view/')
resp.forms['listing-settings']['q'] = 'foo'
resp = resp.forms['listing-settings'].submit()
assert re.findall(r'<a href="(\d)/">1-(\d)</a>', resp.text) == [('1', '1'), ('2', '2'), ('3', '3')]
# but can still be overridden by query string
resp.forms['listing-settings']['order_by'] = '-f1'
resp = resp.forms['listing-settings'].submit()
assert re.findall(r'<a href="(\d)/">1-(\d)</a>', resp.text) == [('1', '1'), ('3', '3'), ('2', '2')]
def test_carddata_custom_view(pub):
user = create_user(pub)

View File

@ -216,8 +216,8 @@ def test_backoffice_csv(pub):
assert resp_csv.text.splitlines() == [
'"3rd field (identifier)","3rd field"',
'"foo",""',
'"A","aa"',
'"C","cc"',
'"A","aa"',
]

View File

@ -1485,6 +1485,23 @@ class FormPage(Directory, TempfileDirectoryMixin):
return r.getvalue()
def get_default_order_by(self, system_default_order_by='-receipt_time'):
default_order_by = get_publisher().get_site_option('default-sort-order') or system_default_order_by
if self.view:
default_order_by = self.view.order_by or default_order_by
return default_order_by

Le tri par défaut, qui va être -receipt_time sur les tableaux de traitement (mais -id sur l'export), aussi ça peut être overridé dans la configuration système (paramétrage qui serait à regarder un jour, pas l'impression que ça soit encore utilisé).

Et s'il y a une vue elle peut définir un tri, qui sera donc le tri par défaut de cette vue.

Le tri par défaut, qui va être -receipt_time sur les tableaux de traitement (mais -id sur l'export), aussi ça peut être overridé dans la configuration système (paramétrage qui serait à regarder un jour, pas l'impression que ça soit encore utilisé). Et s'il y a une vue elle peut définir un tri, qui sera donc le tri par défaut de cette vue.
def get_order_by_from_query(self, system_default_order_by='-receipt_time'):
order_by = misc.get_order_by_or_400(get_request().form.get('order_by'))
default_order_by = self.get_default_order_by(system_default_order_by=system_default_order_by)
if get_request().form.get('q') and not order_by:
order_by = 'rank'
if not order_by:
order_by = default_order_by

S'il n'y a pas de order_by explicite dans la query string, s'il y a une recherche full text on trie par pertinence, sinon on prend le tri par défaut.

Ces deux méthodes vont permettre un comportement cohérent plus bas, où il y avait du code qui n'était pas toujours pareil selon les circonstances.

S'il n'y a pas de order_by explicite dans la query string, s'il y a une recherche full text on trie par pertinence, sinon on prend le tri par défaut. Ces deux méthodes vont permettre un comportement cohérent plus bas, où il y avait du code qui n'était pas toujours pareil selon les circonstances.
return order_by
def get_fields_sidebar(
self,
selected_filter,
@ -1508,8 +1525,8 @@ class FormPage(Directory, TempfileDirectoryMixin):
if limit:
r += htmltext('<input type="hidden" name="limit" value="%s"/>') % limit
if order_by is None:
order_by = get_publisher().get_site_option('default-sort-order') or '-receipt_time'
if not order_by or order_by == self.get_default_order_by():
order_by = ''
r += htmltext('<input type="hidden" name="order_by" value="%s"/>') % order_by
r += htmltext('<h3>%s</h3>') % self.search_label
@ -2351,13 +2368,9 @@ class FormPage(Directory, TempfileDirectoryMixin):
get_request().form.get('limit', get_publisher().get_site_option('default-page-size') or 20)
)
offset = misc.get_int_or_400(get_request().form.get('offset', 0))
order_by = misc.get_order_by_or_400(get_request().form.get('order_by'))
if self.view and not order_by:
order_by = self.view.order_by
if not order_by:
order_by = get_publisher().get_site_option('default-sort-order') or '-receipt_time'
query = get_request().form.get('q')
order_by = self.get_order_by_from_query()
query = get_request().form.get('q')
qs = ''
if get_request().get_query():
qs = '?' + get_request().get_query()
@ -2558,7 +2571,7 @@ class FormPage(Directory, TempfileDirectoryMixin):
user = get_request().user
query = get_request().form.get('q')
criterias = self.get_criterias_from_query()
order_by = misc.get_order_by_or_400(get_request().form.get('order_by', None))
order_by = self.get_order_by_from_query(system_default_order_by='-id')
skip_header_line = bool(get_request().form.get('skip_header_line'))
count = self.formdef.data_class().count()
@ -2616,7 +2629,7 @@ class FormPage(Directory, TempfileDirectoryMixin):
user = get_user_from_api_query_string() or get_request().user
query = get_request().form.get('q')
criterias = self.get_criterias_from_query()
order_by = misc.get_order_by_or_400(get_request().form.get('order_by', None))
order_by = self.get_order_by_from_query(system_default_order_by='-id')
skip_header_line = bool(get_request().form.get('skip_header_line'))
count = self.formdef.data_class().count()
@ -2650,7 +2663,7 @@ class FormPage(Directory, TempfileDirectoryMixin):
user = get_request().user
query = get_request().form.get('q')
criterias = self.get_criterias_from_query()
order_by = misc.get_order_by_or_400(get_request().form.get('order_by', None))
order_by = self.get_order_by_from_query()
job = JsonFileExportAfterJob(
self.formdef,
@ -2675,9 +2688,7 @@ class FormPage(Directory, TempfileDirectoryMixin):
selected_filter = self.get_filter_from_query(default='all')
selected_filter_operator = self.get_filter_operator_from_query()
criterias = self.get_criterias_from_query()
order_by = misc.get_order_by_or_400(get_request().form.get('order_by', None))
if self.view and not order_by:
order_by = self.view.order_by
order_by = self.get_order_by_from_query()
query = get_request().form.get('q') if not anonymise else None
offset = None
if 'offset' in get_request().form:

View File

@ -1133,7 +1133,7 @@ def get_int_or_400(value):
def get_order_by_or_400(value):
if value is None:
if value in (None, ''):

On peut désormais avoir un order_by en chaine vide, on considère ça comme valide et le tri par défaut s'appliquera.

On peut désormais avoir un order_by en chaine vide, on considère ça comme valide et le tri par défaut s'appliquera.
return None
if not re.match(r'-?[a-z0-9_-]+$', value):
raise RequestError()

View File

@ -106,20 +106,17 @@ function prepare_row_links() {
}
function prepare_column_headers() {
if ($('input[name="order_by"]').length == 0) {
/* if we don't have an order_by field, that means we do not support server
* side sorting, so we abort here */
return;

Obsolète, on est en SQL on a tout le temps le tri serveur.

Obsolète, on est en SQL on a tout le temps le tri serveur.
}
var current_key = $('input[name="order_by"]').val();
var sort_key = null;
var reversed = true;
if (current_key[0] === '-') {
sort_key = current_key.substring(1);
reversed = true;
} else {
sort_key = current_key;
reversed = false;
var reversed = false;
if (current_key) {
if (current_key[0] === '-') {
sort_key = current_key.substring(1);
reversed = true;
} else {
sort_key = current_key;
reversed = false;
}
}
if (reversed) {
$('#listing thead th[data-field-sort-key="' + sort_key + '"]').addClass('headerSortUp');
@ -127,10 +124,12 @@ function prepare_column_headers() {
$('#listing thead th[data-field-sort-key="' + sort_key + '"]').addClass('headerSortDown');
}
$('#listing thead th[data-field-sort-key]').addClass('header').click(function() {
new_key = $(this).data('field-sort-key');
if (current_key === sort_key) {
if (reversed === false) {
var new_key = $(this).data('field-sort-key');
if (sort_key === new_key) { // same column, reverse on second click, reset on third click
if (! reversed) {

Cf le commentaire, un clic sur une colonne trie sur celle-ci, un second clic inverse le tri, un troisième retire le tri particulier; ça permet ainsi de revenir au tri par défaut.

Cf le commentaire, un clic sur une colonne trie sur celle-ci, un second clic inverse le tri, un troisième retire le tri particulier; ça permet ainsi de revenir au tri par défaut.
new_key = '-' + new_key
} else {
new_key = ''
}
}
$('input[name="order_by"]').val(new_key);