diff --git a/.env.demo b/.env.demo index aa0ff9c1b..e23f905a2 100644 --- a/.env.demo +++ b/.env.demo @@ -60,7 +60,7 @@ LAYMAN_CLIENT_URL=http://layman_client:3000/client/ # client LAYMAN_CLIENT_PUBLIC_URL=http://localhost/client/ -LAYMAN_CLIENT_VERSION=v1.6.1 +LAYMAN_CLIENT_VERSION=89ae01037056b9043a23f271a132eb19b103680b # extra hosts to be added to /etc/hosts EXTRA_HOST1=1.2.3.4:1.2.3.4 diff --git a/.env.dev b/.env.dev index 8b9bb4840..7818f0864 100644 --- a/.env.dev +++ b/.env.dev @@ -62,7 +62,7 @@ LAYMAN_CLIENT_URL=http://layman_client:3000/client/ # client LAYMAN_CLIENT_PUBLIC_URL=http://localhost:3000/client/ -LAYMAN_CLIENT_VERSION=v1.6.1 +LAYMAN_CLIENT_VERSION=89ae01037056b9043a23f271a132eb19b103680b ############################################################################## diff --git a/.env.test b/.env.test index 26d9a6ec5..7eb90a89b 100644 --- a/.env.test +++ b/.env.test @@ -62,7 +62,7 @@ LAYMAN_CLIENT_URL=http://layman_client_test:3000/client/ # client LAYMAN_CLIENT_PUBLIC_URL=http://layman_test_run_1:8000/client/ -LAYMAN_CLIENT_VERSION=v1.6.1 +LAYMAN_CLIENT_VERSION=89ae01037056b9043a23f271a132eb19b103680b ############################################################################## diff --git a/CHANGELOG.md b/CHANGELOG.md index aac5d8cee..42a01e904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## v1.12.0 + {release-date} +### Upgrade requirements + +### Migrations and checks + +### Changes +- [#273](https://github.com/jirik/layman/issues/273) Endpoints [GET Layers](doc/rest.md#get-layers) and [GET Maps](doc/rest.md#get-maps) can filter and reorder results according to new query parameters. + ## v1.11.0 2021-03-16 ### Upgrade requirements diff --git a/doc/rest.md b/doc/rest.md index 14277dcca..d43b561b8 100644 --- a/doc/rest.md +++ b/doc/rest.md @@ -34,7 +34,9 @@ Get list of published layers. #### Request -No action parameters. +Query parameters: +- *full_text_filter*: String. List of words separated by space. Only layers with at least one of them in title will be return. Search is case-insensitive, unaccent and did lemmatization for English. By default, layers are ordered by search rank in response if this filter is used. + #### Response Content-Type: `application/json` @@ -390,7 +392,9 @@ JSON object with one attribute: Get list of published maps (map compositions). #### Request -No action parameters. +Query parameters: +- *full_text_filter*: String. List of words separated by space. Only maps with at least one of them in title will be return. Search is case-insensitive, unaccent and did lemmatization for English. By default, maps are ordered by search rank in response if this filter is used. + #### Response Content-Type: `application/json` diff --git a/src/layman/common/prime_db_schema/migrate_test.py b/src/layman/common/prime_db_schema/migrate_test.py index a7cbcf46d..6e4fa702e 100644 --- a/src/layman/common/prime_db_schema/migrate_test.py +++ b/src/layman/common/prime_db_schema/migrate_test.py @@ -28,6 +28,9 @@ def save_upgrade_status(): with app.app_context(): upgrade.upgrade_v1_9.initialize_data_versioning() upgrade.upgrade_v1_10.alter_schema() + upgrade.upgrade_v1_10.update_style_type_in_db() + upgrade.upgrade_v1_12.install_unaccent_to_db() + upgrade.set_current_data_version(current_version) diff --git a/src/layman/common/prime_db_schema/publications.py b/src/layman/common/prime_db_schema/publications.py index 80cbb35ed..65460920b 100644 --- a/src/layman/common/prime_db_schema/publications.py +++ b/src/layman/common/prime_db_schema/publications.py @@ -11,8 +11,50 @@ psycopg2.extras.register_uuid() -def get_publication_infos(workspace_name=None, pub_type=None, style_type=None, reader=None, writer=None): - sql_basic = f""" +def get_publication_infos(workspace_name=None, pub_type=None, style_type=None, + reader=None, writer=None, + full_text_filter=None, + order_by_list=None, + ordering_full_text=None, + ): + order_by_list = order_by_list or [] + + where_params_def = [ + (workspace_name, 'w.name = %s', (workspace_name,)), + (pub_type, 'p.type = %s', (pub_type,)), + (style_type, 'p.style_type::text = %s', (style_type,)), + (reader == settings.ANONYM_USER, 'p.everyone_can_read = TRUE', tuple()), + (reader and reader != settings.ANONYM_USER, f"""(p.everyone_can_read = TRUE + or (u.id is not null and w.name = %s) + or EXISTS(select 1 + from {DB_SCHEMA}.rights r inner join + {DB_SCHEMA}.users u2 on r.id_user = u2.id inner join + {DB_SCHEMA}.workspaces w2 on w2.id = u2.id_workspace + where r.id_publication = p.id + and r.type = 'read' + and w2.name = %s))""", (reader, reader,)), + (writer == settings.ANONYM_USER, 'p.everyone_can_write = TRUE', tuple()), + (writer and writer != settings.ANONYM_USER, f"""(p.everyone_can_write = TRUE + or (u.id is not null and w.name = %s) + or EXISTS(select 1 + from {DB_SCHEMA}.rights r inner join + {DB_SCHEMA}.users u2 on r.id_user = u2.id inner join + {DB_SCHEMA}.workspaces w2 on w2.id = u2.id_workspace + where r.id_publication = p.id + and r.type = 'write' + and w2.name = %s))""", (writer, writer,)), + (full_text_filter, '_prime_schema.my_unaccent(p.title) @@ to_tsquery(unaccent(%s))', (full_text_filter,)), + ] + + order_by_definition = { + 'full_text': ('ts_rank_cd(_prime_schema.my_unaccent(p.title), to_tsquery(unaccent(%s))) DESC', (ordering_full_text,)), + } + + assert all(ordering_item in order_by_definition.keys() for ordering_item in order_by_list) + + ######################################################### + # SELECT clause + select_clause = f""" select p.id as id_publication, w.name as workspace_name, p.type, @@ -42,44 +84,40 @@ def get_publication_infos(workspace_name=None, pub_type=None, style_type=None, r {DB_SCHEMA}.publications p on p.id_workspace = w.id left join {DB_SCHEMA}.users u on u.id_workspace = w.id """ - query_params = (ROLE_EVERYONE, ROLE_EVERYONE, ) - where_parts = [] - where_params_def = [(workspace_name, 'w.name = %s', (workspace_name, )), - (pub_type, 'p.type = %s', (pub_type, )), - (style_type, 'p.style_type::text = %s', (style_type, )), - (reader == settings.ANONYM_USER, 'p.everyone_can_read = TRUE', tuple()), - (reader and reader != settings.ANONYM_USER, f"""(p.everyone_can_read = TRUE - or (u.id is not null and w.name = %s) - or EXISTS(select 1 - from {DB_SCHEMA}.rights r inner join - {DB_SCHEMA}.users u2 on r.id_user = u2.id inner join - {DB_SCHEMA}.workspaces w2 on w2.id = u2.id_workspace - where r.id_publication = p.id - and r.type = 'read' - and w2.name = %s))""", (reader, reader,)), - (writer == settings.ANONYM_USER, 'p.everyone_can_write = TRUE', tuple()), - (writer and writer != settings.ANONYM_USER, f"""(p.everyone_can_write = TRUE - or (u.id is not null and w.name = %s) - or EXISTS(select 1 - from {DB_SCHEMA}.rights r inner join - {DB_SCHEMA}.users u2 on r.id_user = u2.id inner join - {DB_SCHEMA}.workspaces w2 on w2.id = u2.id_workspace - where r.id_publication = p.id - and r.type = 'write' - and w2.name = %s))""", (writer, writer,)), - ] + select_params = (ROLE_EVERYONE, ROLE_EVERYONE, ) + + ######################################################### + # WHERE clause + where_params = tuple() + where_parts = list() for (value, where_part, params, ) in where_params_def: if value: where_parts.append(where_part) - query_params = query_params + params - + where_params = where_params + params where_clause = '' if where_parts: where_clause = 'WHERE ' + '\n AND '.join(where_parts) + '\n' - order_by_clause = 'ORDER BY w.name ASC, p.name ASC\n' - select = sql_basic + where_clause + order_by_clause - values = util.run_query(select, query_params) + ######################################################### + # ORDER BY clause + order_by_params = tuple() + order_by_parts = list() + for order_by_part in order_by_list: + order_by_parts.append(order_by_definition[order_by_part][0]) + order_by_params = order_by_params + order_by_definition[order_by_part][1] + + order_by_parts.append('w.name ASC') + order_by_parts.append('p.name ASC') + order_by_clause = 'ORDER BY ' + ', '.join(order_by_parts) + + ######################################################### + # Put it together + sql_params = select_params + where_params + order_by_params + select = select_clause + where_clause + order_by_clause + values = util.run_query(select, sql_params) + + # print(f'get_publication_infos:\n\n order_by_clause={order_by_clause},\n where_clause={where_clause},\n sql_params={sql_params},' + # f'\n order_by_list={order_by_list},\n full_text_ordering={full_text_ordering}') infos = {(workspace_name, type, diff --git a/src/layman/common/prime_db_schema/publications_test.py b/src/layman/common/prime_db_schema/publications_test.py index 23d4c6bcc..8e6c8644f 100644 --- a/src/layman/common/prime_db_schema/publications_test.py +++ b/src/layman/common/prime_db_schema/publications_test.py @@ -151,22 +151,27 @@ class TestSelectPublicationsComplex: publications = [ (workspace1, MAP_TYPE, 'test_select_publications_publication1e', {'headers': authn_headers_user1, + 'title': 'Příliš žluťoučký Kůň úpěl ďábelské ódy', 'access_rights': {'read': settings.RIGHTS_EVERYONE_ROLE, 'write': settings.RIGHTS_EVERYONE_ROLE}, }), (workspace1, MAP_TYPE, 'test_select_publications_publication1o', {'headers': authn_headers_user1, + 'title': 'kun Karel', 'access_rights': {'read': workspace1, 'write': workspace1}, }), (workspace1, MAP_TYPE, 'test_select_publications_publication1oe', {'headers': authn_headers_user1, + 'title': 'jedna dva tři čtyři', 'access_rights': {'read': settings.RIGHTS_EVERYONE_ROLE, 'write': workspace1}, }), (workspace2, MAP_TYPE, 'test_select_publications_publication2e', {'headers': authn_headers_user2, + 'title': 'Svíčky is the best game', 'access_rights': {'read': settings.RIGHTS_EVERYONE_ROLE, 'write': settings.RIGHTS_EVERYONE_ROLE}, }), (workspace2, MAP_TYPE, 'test_select_publications_publication2o', {'headers': authn_headers_user2, + 'title': 'druhá mapa JeDnA óda', 'access_rights': {'read': workspace2, 'write': workspace2}, }), ] @@ -204,12 +209,42 @@ def provide_data(self): (workspace2, MAP_TYPE, 'test_select_publications_publication2e'), (workspace2, MAP_TYPE, 'test_select_publications_publication2o'), ]), + ({'full_text_filter': 'dva'}, [(workspace1, MAP_TYPE, 'test_select_publications_publication1oe'), + ]), + ({'full_text_filter': 'games'}, [(workspace2, MAP_TYPE, 'test_select_publications_publication2e'), + ]), + ({'full_text_filter': 'kun'}, [(workspace1, MAP_TYPE, 'test_select_publications_publication1e'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1o'), + ]), + ({'full_text_filter': 'jedna'}, [(workspace1, MAP_TYPE, 'test_select_publications_publication1oe'), + (workspace2, MAP_TYPE, 'test_select_publications_publication2o'), + ]), + ({'full_text_filter': 'upet'}, []), + ({'full_text_filter': 'dva | kun'}, [(workspace1, MAP_TYPE, 'test_select_publications_publication1e'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1o'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1oe'), + ]), + ({'full_text_filter': 'kun & ody'}, [(workspace1, MAP_TYPE, 'test_select_publications_publication1e'), + ]), + ({'order_by_list': ['full_text'], 'ordering_full_text': 'jedna'}, [ + (workspace1, MAP_TYPE, 'test_select_publications_publication1oe'), + (workspace2, MAP_TYPE, 'test_select_publications_publication2o'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1e'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1o'), + (workspace2, MAP_TYPE, 'test_select_publications_publication2e'), + ]), + ({'full_text_filter': 'dva | kun', 'order_by_list': ['full_text'], 'ordering_full_text': 'karel | kun'}, [ + (workspace1, MAP_TYPE, 'test_select_publications_publication1o'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1e'), + (workspace1, MAP_TYPE, 'test_select_publications_publication1oe'), + ]), ]) @pytest.mark.usefixtures('liferay_mock', 'ensure_layman', 'provide_data') def test_get_publications(self, query_params, expected_publications): with app.app_context(): infos = publications.get_publication_infos(**query_params) info_publications = list(infos.keys()) + assert set(expected_publications) == set(info_publications) assert expected_publications == info_publications diff --git a/src/layman/common/prime_db_schema/util.py b/src/layman/common/prime_db_schema/util.py index 0c9628257..9a074226f 100644 --- a/src/layman/common/prime_db_schema/util.py +++ b/src/layman/common/prime_db_schema/util.py @@ -1,5 +1,6 @@ from flask import g, current_app as app import psycopg2 +import re from layman import settings from layman.http import LaymanError @@ -59,3 +60,9 @@ def run_statement(query, data=None, conn_cur=None, encapsulate_exception=True): else: raise exc return rows + + +def to_tsquery_string(value): + value = re.sub(r'[\W_]+', ' ', value, flags=re.UNICODE).strip() + value = value.replace(' ', ' | ') + return value diff --git a/src/layman/common/prime_db_schema/util_test.py b/src/layman/common/prime_db_schema/util_test.py new file mode 100644 index 000000000..1b7be93a1 --- /dev/null +++ b/src/layman/common/prime_db_schema/util_test.py @@ -0,0 +1,14 @@ +import pytest + +from . import util + + +@pytest.mark.parametrize('input_string, exp_result', [ + ('Příliš žluťoučký kůň úpěl ďábelské ódy', 'Příliš | žluťoučký | kůň | úpěl | ďábelské | ódy'), + (' #@ Příliš žluťoučký kůň úpěl ďábelské ódy \n', 'Příliš | žluťoučký | kůň | úpěl | ďábelské | ódy'), + ('Pří_liš', 'Pří | liš'), + ('\'Too yellow horse\' means "Příliš žluťoučký kůň".', 'Too | yellow | horse | means | Příliš | žluťoučký | kůň'), + ('\tThe Fačřš_tÚŮTŤsa " a34432[;] ;.\\Ra\'\'ts ', 'The | Fačřš | tÚŮTŤsa | a34432 | Ra | ts'), +]) +def test_to_tsquery_string(input_string, exp_result): + assert util.to_tsquery_string(input_string) == exp_result diff --git a/src/layman/common/rest.py b/src/layman/common/rest.py index c98653691..d8eac1a46 100644 --- a/src/layman/common/rest.py +++ b/src/layman/common/rest.py @@ -1,18 +1,19 @@ +from flask import jsonify import re -from layman import settings +from layman import settings, util as layman_util +from layman.common.prime_db_schema import util as prime_db_schema_util from .util import PUBLICATION_NAME_ONLY_PATTERN -from layman.util import USERNAME_ONLY_PATTERN, get_publication_types def _get_pub_type_pattern(): - publ_type_names = [publ_type['rest_path_name'] for publ_type in get_publication_types().values()] + publ_type_names = [publ_type['rest_path_name'] for publ_type in layman_util.get_publication_types().values()] publ_type_pattern = r"(?P" + "|".join(publ_type_names) + r")" return publ_type_pattern def _get_workspace_multi_publication_path_pattern(): - workspace_pattern = r"(?P" + USERNAME_ONLY_PATTERN + r")" + workspace_pattern = r"(?P" + layman_util.USERNAME_ONLY_PATTERN + r")" return f"^/rest/({settings.REST_WORKSPACES_PREFIX}/)?" + workspace_pattern + "/" + _get_pub_type_pattern() @@ -51,7 +52,7 @@ def get_url_name_to_publication_type(): if _URL_NAME_TO_PUBLICATION_TYPE is None: _URL_NAME_TO_PUBLICATION_TYPE = { publ_type['rest_path_name']: publ_type - for publ_type in get_publication_types().values() + for publ_type in layman_util.get_publication_types().values() } return _URL_NAME_TO_PUBLICATION_TYPE @@ -98,3 +99,38 @@ def setup_post_access_rights(request_form, kwargs, actor_name): else: access_rights = list({x.strip() for x in request_form['access_rights.' + type].split(',')}) kwargs['access_rights'][type] = access_rights + + +def get_publications(publication_type, user, request_args): + + full_text_filter = None + if 'full_text_filter' in request_args: + full_text_filter = prime_db_schema_util.to_tsquery_string(request_args.get('full_text_filter')) or None + + order_by_list = [] + ordering_full_text = None + if full_text_filter: + ordering_full_text = full_text_filter + order_by_list = ['full_text'] + + publication_infos_whole = layman_util.get_publication_infos(publ_type=publication_type, + context={'actor_name': user, + 'access_type': 'read' + }, + full_text_filter=full_text_filter, + order_by_list=order_by_list, + ordering_full_text=ordering_full_text, + ) + + infos = [ + { + 'name': name, + 'workspace': workspace, + 'title': info.get("title"), + 'url': layman_util.get_workspace_publication_url(publication_type, workspace, name), + 'uuid': info["uuid"], + 'access_rights': info['access_rights'], + } + for (workspace, _, name), info in publication_infos_whole.items() + ] + return jsonify(infos), 200 diff --git a/src/layman/layer/__init__.py b/src/layman/layer/__init__.py index a886dcfcb..19d3dcd2d 100644 --- a/src/layman/layer/__init__.py +++ b/src/layman/layer/__init__.py @@ -102,3 +102,10 @@ def get_layer_sources(): ), ] NO_STYLE_DEF = STYLE_TYPES_DEF[0] + + +from layman.util import url_for + + +def get_workspace_publication_url(workspace, publication_name): + return url_for('rest_workspace_layer.get', layername=publication_name, username=workspace) diff --git a/src/layman/layer/rest_layers.py b/src/layman/layer/rest_layers.py index 812c5bf47..a7cea8bae 100644 --- a/src/layman/layer/rest_layers.py +++ b/src/layman/layer/rest_layers.py @@ -1,11 +1,11 @@ -from flask import Blueprint, jsonify, g +from flask import Blueprint, g, request from flask import current_app as app -from layman.util import url_for -from layman import util as layman_util, settings +from layman import settings from . import LAYER_TYPE, LAYER_REST_PATH_NAME -from layman.authn import authenticate +from layman.authn import authenticate, get_authn_username from layman.authz import authorize_publications_decorator +from layman.common import rest as rest_common bp = Blueprint('rest_layers', __name__) @@ -21,20 +21,5 @@ def before_request(): def get(): app.logger.info(f"GET Layers, user={g.user}") - user = g.user.get('username') if g.user else settings.ANONYM_USER - layer_infos_whole = layman_util.get_publication_infos(publ_type=LAYER_TYPE, context={'actor_name': user, - 'access_type': 'read', - }) - - infos = [ - { - 'name': name, - 'workspace': workspace, - 'title': info.get("title", None), - 'url': url_for('rest_workspace_layer.get', layername=name, username=workspace), - 'uuid': info["uuid"], - 'access_rights': info['access_rights'], - } - for (workspace, _, name), info in layer_infos_whole.items() - ] - return jsonify(infos), 200 + user = get_authn_username() or settings.ANONYM_USER + return rest_common.get_publications(LAYER_TYPE, user, request.args) diff --git a/src/layman/map/__init__.py b/src/layman/map/__init__.py index 8a9ec2a5b..da5d689d4 100644 --- a/src/layman/map/__init__.py +++ b/src/layman/map/__init__.py @@ -70,3 +70,10 @@ def get_map_type_def(): }, } } + + +from layman.util import url_for + + +def get_workspace_publication_url(workspace, publication_name): + return url_for('rest_workspace_map.get', mapname=publication_name, username=workspace) diff --git a/src/layman/map/rest_maps.py b/src/layman/map/rest_maps.py index 610291d17..c58a2f6fc 100644 --- a/src/layman/map/rest_maps.py +++ b/src/layman/map/rest_maps.py @@ -1,11 +1,11 @@ -from flask import Blueprint, jsonify, g +from flask import Blueprint, g, request from flask import current_app as app -from layman.util import url_for -from layman import util as layman_util, settings +from layman import settings from . import MAP_TYPE, MAP_REST_PATH_NAME -from layman.authn import authenticate +from layman.authn import authenticate, get_authn_username from layman.authz import authorize_publications_decorator +from layman.common import rest as rest_common bp = Blueprint('rest_maps', __name__) @@ -21,20 +21,5 @@ def before_request(): def get(): app.logger.info(f"GET Maps, user={g.user}") - user = g.user.get('username') if g.user else settings.ANONYM_USER - map_infos_whole = layman_util.get_publication_infos(publ_type=MAP_TYPE, context={'actor_name': user, - 'access_type': 'read', - }) - - infos = [ - { - 'name': name, - 'workspace': workspace, - 'title': info.get("title", None), - 'url': url_for('rest_workspace_map.get', mapname=name, username=workspace), - 'uuid': info["uuid"], - 'access_rights': info['access_rights'], - } - for (workspace, _, name), info in map_infos_whole.items() - ] - return jsonify(infos), 200 + user = get_authn_username() or settings.ANONYM_USER + return rest_common.get_publications(MAP_TYPE, user, request.args) diff --git a/src/layman/rest_multipublication_test.py b/src/layman/rest_multipublication_test.py index 6a9f84be3..7a195970e 100644 --- a/src/layman/rest_multipublication_test.py +++ b/src/layman/rest_multipublication_test.py @@ -26,7 +26,8 @@ def check_delete(headers, publication_set = {publication['name'] for publication in delete_json} assert after_delete_publications == publication_set - get_json = process_client.get_workspace_publications(publ_type, workspace=owner, headers=authn_headers_owner) + get_json = process_client.get_workspace_publications(publ_type, workspace=owner, + headers=authn_headers_owner) publication_set = {publication['name'] for publication in get_json} assert remaining_publications == publication_set @@ -41,7 +42,8 @@ def check_delete(headers, ] for (name, access_rights) in publications: - process_client.publish_workspace_publication(publ_type, owner, name, access_rights=access_rights, headers=authn_headers_owner) + process_client.publish_workspace_publication(publ_type, owner, name, access_rights=access_rights, + headers=authn_headers_owner) rv = process_client.get_workspace_publications(publ_type, workspace=owner, headers=authn_headers_owner) assert len(rv) == len(publications) @@ -61,14 +63,23 @@ class TestGetPublications: workspace1 = 'test_get_publications_workspace1' workspace2 = 'test_get_publications_workspace2' authn_headers_user2 = process_client.get_authz_headers(workspace2) - publications = [(workspace1, 'test_get_publications_publication1e', dict()), - (workspace2, 'test_get_publications_publication2e', {'headers': authn_headers_user2, - 'access_rights': {'read': settings.RIGHTS_EVERYONE_ROLE, - 'write': settings.RIGHTS_EVERYONE_ROLE}, }), - (workspace2, 'test_get_publications_publication2o', {'headers': authn_headers_user2, - 'access_rights': {'read': workspace2, - 'write': workspace2}, }), - ] + publications = [ + (workspace1, 'test_get_publications_publication1e', { + 'title': 'Public publication in public workspace' + }), + (workspace2, 'test_get_publications_publication2e', { + 'headers': authn_headers_user2, + 'title': '\'Too yellow horse\' means "Příliš žluťoučký kůň".', + 'access_rights': {'read': settings.RIGHTS_EVERYONE_ROLE, + 'write': settings.RIGHTS_EVERYONE_ROLE}, + }), + (workspace2, 'test_get_publications_publication2o', { + 'headers': authn_headers_user2, + 'title': 'Příliš jiný žluťoučký kůň úpěl ďábelské ódy', + 'access_rights': {'read': workspace2, + 'write': workspace2}, + }), + ] @pytest.fixture(scope="class") def provide_data(self): @@ -83,16 +94,35 @@ def provide_data(self): process_client.delete_workspace_publication(publication_type, publication[0], publication[1], publication[2].get('headers')) - @pytest.mark.parametrize('headers, expected_publications', [ - (authn_headers_user2, [(workspace1, 'test_get_publications_publication1e'), - (workspace2, 'test_get_publications_publication2e'), - (workspace2, 'test_get_publications_publication2o'), - ], ), - (None, [(workspace1, 'test_get_publications_publication1e'), (workspace2, 'test_get_publications_publication2e'), ],), + @pytest.mark.parametrize('headers, query_params, expected_publications', [ + (authn_headers_user2, {}, [(workspace1, 'test_get_publications_publication1e'), + (workspace2, 'test_get_publications_publication2e'), + (workspace2, 'test_get_publications_publication2o'), + ],), + (None, {}, [(workspace1, 'test_get_publications_publication1e'), + (workspace2, 'test_get_publications_publication2e'), + ],), + (authn_headers_user2, {'full_text_filter': 'kůň'}, [(workspace2, 'test_get_publications_publication2e'), + (workspace2, 'test_get_publications_publication2o'), + ],), + (None, {'full_text_filter': 'The Fačřš_tÚŮTŤsa " a34432[;] ;.\\Ra\'\'ts'}, list(),), + (authn_headers_user2, {'full_text_filter': '\'Too yellow horse\' means "Příliš žluťoučký kůň".'}, [ + (workspace2, 'test_get_publications_publication2e'), + (workspace2, 'test_get_publications_publication2o'), + ],), + (authn_headers_user2, {'full_text_filter': 'mean'}, [(workspace2, 'test_get_publications_publication2e'), + ],), + (authn_headers_user2, {'full_text_filter': 'jiný další kůň'}, [(workspace2, 'test_get_publications_publication2o'), + (workspace2, 'test_get_publications_publication2e'), + ],), + (authn_headers_user2, {'full_text_filter': 'workspace publication'}, [ + (workspace1, 'test_get_publications_publication1e'), + ],), ]) @pytest.mark.parametrize('publication_type', process_client.PUBLICATION_TYPES) @pytest.mark.usefixtures('liferay_mock', 'ensure_layman', 'provide_data') - def test_get_publications(self, publication_type, headers, expected_publications): - infos = process_client.get_publications(publication_type, headers, ) - info_publications = [(info.get('workspace'), info.get('name')) for info in infos] + def test_get_publications(self, publication_type, headers, query_params, expected_publications): + infos = process_client.get_publications(publication_type, headers, query_params=query_params) + info_publications = [(info['workspace'], info['name']) for info in infos] + assert set(expected_publications) == set(info_publications) assert expected_publications == info_publications diff --git a/src/layman/upgrade/__init__.py b/src/layman/upgrade/__init__.py index 106070803..525cc341b 100644 --- a/src/layman/upgrade/__init__.py +++ b/src/layman/upgrade/__init__.py @@ -1,7 +1,7 @@ import logging import psycopg2 -from layman.upgrade import upgrade_v1_8, upgrade_v1_9, upgrade_v1_10 +from layman.upgrade import upgrade_v1_8, upgrade_v1_9, upgrade_v1_10, upgrade_v1_12 from layman import settings from layman.common.prime_db_schema import util as db_util DB_SCHEMA = settings.LAYMAN_PRIME_SCHEMA @@ -20,6 +20,8 @@ upgrade_v1_10.migrate_input_sld_directory_to_input_style, upgrade_v1_10.update_style_type_in_db, ]), + ((1, 12, 0), [upgrade_v1_12.install_unaccent_to_db, + ]) ] diff --git a/src/layman/upgrade/upgrade_v1_12.py b/src/layman/upgrade/upgrade_v1_12.py new file mode 100644 index 000000000..6cfd3a904 --- /dev/null +++ b/src/layman/upgrade/upgrade_v1_12.py @@ -0,0 +1,13 @@ +from layman.common.prime_db_schema import util as db_util + + +def install_unaccent_to_db(): + statement = '''CREATE EXTENSION IF NOT EXISTS unaccent; +drop index if exists _prime_schema.title_tsv_idx; +drop function if exists _prime_schema.my_unaccent; + +CREATE FUNCTION _prime_schema.my_unaccent(text) RETURNS tsvector LANGUAGE SQL IMMUTABLE AS +'SELECT to_tsvector(unaccent($1))'; +CREATE INDEX title_tsv_idx ON _prime_schema.publications USING GIST (_prime_schema.my_unaccent(title)); +''' + db_util.run_statement(statement) diff --git a/src/layman/util.py b/src/layman/util.py index c1accfd55..6784ca5ae 100644 --- a/src/layman/util.py +++ b/src/layman/util.py @@ -184,6 +184,17 @@ def get_publication_modules(use_cache=True): return result +def get_publication_module(publication_type, use_cache=True): + modules = get_publication_modules(use_cache=use_cache) + module = next(module for module in modules if publication_type in module.PUBLICATION_TYPES) + return module + + +def get_workspace_publication_url(publication_type, workspace, publication_name, use_cache=True): + publ_module = get_publication_module(publication_type, use_cache=use_cache) + return publ_module.get_workspace_publication_url(workspace, publication_name) + + def get_providers_from_source_names(source_names, skip_modules=None): skip_modules = skip_modules or set() provider_names = list(OrderedDict.fromkeys(map( @@ -300,13 +311,22 @@ def get_publication_info(workspace, publ_type, publ_name, context=None): return result -def get_publication_infos(workspace=None, publ_type=None, context=None, style_type=None): +def get_publication_infos(workspace=None, publ_type=None, context=None, style_type=None, + full_text_filter=None, + order_by_list=None, + ordering_full_text=None, + ): from layman.common.prime_db_schema import publications context = context or {} reader = (context.get('actor_name') or settings.ANONYM_USER) if context.get('access_type') == 'read' else None writer = (context.get('actor_name') or settings.ANONYM_USER) if context.get('access_type') == 'write' else None - infos = publications.get_publication_infos(workspace, publ_type, style_type, reader=reader, writer=writer) + infos = publications.get_publication_infos(workspace, publ_type, style_type, + reader=reader, writer=writer, + full_text_filter=full_text_filter, + order_by_list=order_by_list, + ordering_full_text=ordering_full_text, + ) return infos diff --git a/test/process_client.py b/test/process_client.py index ab2179daf..7e11798ed 100644 --- a/test/process_client.py +++ b/test/process_client.py @@ -270,13 +270,14 @@ def get_workspace_publications(publication_type, workspace, headers=None, ): get_workspace_layers = partial(get_workspace_publications, LAYER_TYPE) -def get_publications(publication_type, headers=None, ): +def get_publications(publication_type, headers=None, query_params=None): headers = headers or {} + query_params = query_params or {} publication_type_def = PUBLICATION_TYPES_DEF[publication_type] with app.app_context(): r_url = url_for(publication_type_def.get_publications_url) - r = requests.get(r_url, headers=headers) + r = requests.get(r_url, headers=headers, params=query_params) raise_layman_error(r) return r.json() diff --git a/version.txt b/version.txt index d16d78c50..6a716ad6f 100644 --- a/version.txt +++ b/version.txt @@ -1,2 +1,2 @@ -v1.11.0 -2021-03-16T13:00:00Z +v1.12.0-dev +2021-03-16T13:00:01Z