From 472a62bc1a0580be19ab540a118f70247164bcf3 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Tue, 12 Nov 2024 15:01:23 +0100 Subject: [PATCH 01/44] intermediary commit --- oarepo_requests/resolvers/ui.py | 2 +- oarepo_requests/resources/record/resource.py | 1 - .../resources/record/types/resource.py | 1 - oarepo_requests/services/oarepo/config.py | 21 +++++++++++++++++++ oarepo_requests/types/edit_record.py | 14 ++++++++++++- oarepo_requests/types/new_version.py | 14 ++++++++++++- oarepo_requests/utils.py | 9 ++++++++ tests/test_requests/test_new_version.py | 1 + tests/test_ui/test_ui_resource.py | 1 + 9 files changed, 59 insertions(+), 5 deletions(-) diff --git a/oarepo_requests/resolvers/ui.py b/oarepo_requests/resolvers/ui.py index d76a6850..50ccaedc 100644 --- a/oarepo_requests/resolvers/ui.py +++ b/oarepo_requests/resolvers/ui.py @@ -1,3 +1,4 @@ +from invenio_pidstore.errors import PIDDoesNotExistError from invenio_records_resources.resources.errors import PermissionDeniedError from invenio_search.engine import dsl from invenio_users_resources.proxies import ( @@ -5,7 +6,6 @@ current_users_service, ) from oarepo_runtime.i18n import gettext as _ -from invenio_pidstore.errors import PIDDoesNotExistError from ..proxies import current_oarepo_requests from ..utils import get_matching_service_for_refdict diff --git a/oarepo_requests/resources/record/resource.py b/oarepo_requests/resources/record/resource.py index f7507c27..e160e062 100644 --- a/oarepo_requests/resources/record/resource.py +++ b/oarepo_requests/resources/record/resource.py @@ -1,4 +1,3 @@ - from flask import g from flask_resources import resource_requestctx, response_handler, route from invenio_records_resources.resources import RecordResource diff --git a/oarepo_requests/resources/record/types/resource.py b/oarepo_requests/resources/record/types/resource.py index 8a60af03..ce5c833a 100644 --- a/oarepo_requests/resources/record/types/resource.py +++ b/oarepo_requests/resources/record/types/resource.py @@ -1,4 +1,3 @@ - from flask import g from flask_resources import resource_requestctx, response_handler, route from flask_resources.resources import Resource diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index eee89aff..ca1c967d 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -46,6 +46,26 @@ def expand(self, obj, context): self._resolve(obj, context) return super().expand(obj, context) + +class RequestTypeSpecificLinks(RequestLink): + """Utility class for keeping track of and resolve links.""" + + def __init__(self, when=None, vars=None): + """Constructor.""" + self._when_func = when + self._vars_func = vars + + def should_render(self, obj, ctx): + """Determine if the link should be rendered.""" + if not hasattr(obj.type, "links"): + return False + return super().should_render(obj, ctx) + + def expand(self, obj, context): + """Expand the URI Template.""" + return obj.type.links(request=obj, **context) + + class OARepoRequestsServiceConfig(RequestsServiceConfig): service_id = "oarepo_requests" @@ -62,4 +82,5 @@ class OARepoRequestsServiceConfig(RequestsServiceConfig): ), "receiver": RequestEntityLink("{+entity_self}", entity="receiver"), "receiver_html": RequestEntityLink("{+entity_self_html}", entity="receiver"), + "type_specific": RequestTypeSpecificLinks(), } diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index 9ecbf3f9..4e059de4 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -9,7 +9,11 @@ from oarepo_requests.actions.edit_topic import EditTopicAcceptAction -from ..utils import is_auto_approved, request_identity_matches +from ..utils import ( + get_record_service_for_record_cls, + is_auto_approved, + request_identity_matches, +) from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -28,6 +32,14 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } + def links(self, request, **kwargs): + if request.status == "accepted": + service = get_record_service_for_record_cls(request.topic.record_cls) + record_item = service.read_draft( + kwargs["identity"], request.topic._parse_ref_dict_id() + ) + return {"topic_redirect_link": record_item["links"]["edit_html"]} + @classmethod @property def available_actions(cls): diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index 8e0ebf74..4614e127 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -8,7 +8,11 @@ from typing_extensions import override from ..actions.new_version import NewVersionAcceptAction -from ..utils import is_auto_approved, request_identity_matches +from ..utils import ( + get_record_service_for_record_cls, + is_auto_approved, + request_identity_matches, +) from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -29,6 +33,14 @@ class NewVersionRequestType( ), } + def links(self, request, **kwargs): + if request.status == "accepted": + service = get_record_service_for_record_cls(request.topic.record_cls) + record_item = service.read_draft( + kwargs["identity"], request.topic._parse_ref_dict_id() + ) + return {"topic_redirect_link": record_item["links"]["edit_html"]} + @classmethod @property def available_actions(cls): diff --git a/oarepo_requests/utils.py b/oarepo_requests/utils.py index da453308..7cdfe7d8 100644 --- a/oarepo_requests/utils.py +++ b/oarepo_requests/utils.py @@ -1,5 +1,6 @@ import copy +from flask import current_app from invenio_access.permissions import system_identity from invenio_pidstore.errors import PersistentIdentifierError from invenio_records_resources.proxies import current_service_registry @@ -209,3 +210,11 @@ def merge_resource_configs(config_to_merge_in, original_config): getattr(original_config, copy_from_original_key), ) return actual_config + + +def get_record_service_for_record_cls(record_cls): + if not record_cls: + return None + if "OAREPO_PRIMARY_RECORD_SERVICE" in current_app.config: + service_id = current_app.config["OAREPO_PRIMARY_RECORD_SERVICE"][record_cls] + return current_service_registry.get(service_id) diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py index 1e63cd2f..ba83b00d 100644 --- a/tests/test_requests/test_new_version.py +++ b/tests/test_requests/test_new_version.py @@ -37,6 +37,7 @@ def test_new_version_autoaccept( assert request["status"] == "accepted" assert not request["is_open"] assert request["is_closed"] + assert "topic_redirect_link" in request["links"]["type_specific"] assert "draft_record:links:self" in request["payload"] assert "draft_record:links:self_html" in request["payload"] diff --git a/tests/test_ui/test_ui_resource.py b/tests/test_ui/test_ui_resource.py index aa2c6fd2..94f75df8 100644 --- a/tests/test_ui/test_ui_resource.py +++ b/tests/test_ui/test_ui_resource.py @@ -94,6 +94,7 @@ def test_request_detail_page( assert c.status_code == 200 print(c.text) + def test_form_config(app, client, record_ui_resource, fake_manifest): with client.get("/requests/configs/publish_draft") as c: assert c.json == { From 16d2b99f7e4604187b8323518255250830b1837d Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 15 Nov 2024 15:32:22 +0100 Subject: [PATCH 02/44] request entity links --- oarepo_requests/services/oarepo/config.py | 71 +++++++++++++++-------- oarepo_requests/types/edit_record.py | 18 ++---- oarepo_requests/types/new_version.py | 18 ++---- setup.cfg | 2 +- tests/test_requests/test_new_version.py | 41 ++++++++++++- 5 files changed, 101 insertions(+), 49 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index ca1c967d..928b6378 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -2,13 +2,13 @@ from invenio_requests.services import RequestsServiceConfig from invenio_requests.services.requests import RequestLink -from oarepo_requests.resolvers.ui import resolve +from oarepo_requests.utils import get_record_service_for_record_cls class RequestEntityLink(RequestLink): def __init__(self, uritemplate, when=None, vars=None, entity="topic"): super().__init__(uritemplate, when, vars) - self.entity = entity + self._entity = entity def vars(self, record: Request, vars): super().vars(record, vars) @@ -23,21 +23,29 @@ def should_render(self, obj, ctx): return True def _resolve(self, obj, ctx): - reference_dict = getattr(obj, self.entity).reference_dict + reference_dict = getattr(obj, self._entity).reference_dict key = "entity:" + ":".join( f"{x[0]}:{x[1]}" for x in sorted(reference_dict.items()) ) if key in ctx: return ctx[key] try: - entity = resolve(ctx["identity"], reference_dict) + topic = obj.topic.resolve() + service = get_record_service_for_record_cls(obj.topic.record_cls) + reader = ( + service.read_draft + if getattr(topic, "is_draft", False) + else service.read + ) + entity = reader(ctx["identity"], obj.topic._parse_ref_dict_id()) except Exception: # noqa entity = {} ctx[key] = entity return entity def _expand_entity(self, entity, vars): - vars.update({f"entity_{k}": v for k, v in entity.get("links", {}).items()}) + if hasattr(entity, "links"): + vars.update({f"entity_{k}": v for k, v in entity.links.items()}) def expand(self, obj, context): """Expand the URI Template.""" @@ -47,23 +55,35 @@ def expand(self, obj, context): return super().expand(obj, context) -class RequestTypeSpecificLinks(RequestLink): +class RequestEntityLinks(RequestEntityLink): """Utility class for keeping track of and resolve links.""" - def __init__(self, when=None, vars=None): + def __init__(self, *request_entity_links, entity="topic", when=None, vars=None): """Constructor.""" + self._request_entity_links = [ + {name: RequestEntityLink(link, entity=entity, **kwargs)} + for name, link, kwargs in request_entity_links + ] + self._entity = entity self._when_func = when self._vars_func = vars - def should_render(self, obj, ctx): - """Determine if the link should be rendered.""" - if not hasattr(obj.type, "links"): - return False - return super().should_render(obj, ctx) - def expand(self, obj, context): - """Expand the URI Template.""" - return obj.type.links(request=obj, **context) + res = {} + for link_dict in self._request_entity_links: + name = list(link_dict.keys())[0] + link = list(link_dict.values())[0] + if link.should_render(obj, context): + res[name] = link.expand(obj, context) + if hasattr(obj.type, "type_links"): + entity = self._resolve(obj, context) + res.update( + obj.type.type_links( + request=obj, + **(context | {"cur_entity": entity, "entity_type": self._entity}), + ) + ) + return res class OARepoRequestsServiceConfig(RequestsServiceConfig): @@ -74,13 +94,18 @@ class OARepoRequestsServiceConfig(RequestsServiceConfig): "comments": RequestLink("{+api}/requests/extended/{id}/comments"), "timeline": RequestLink("{+api}/requests/extended/{id}/timeline"), "self_html": RequestLink("{+ui}/requests/{id}"), - "topic": RequestEntityLink("{+entity_self}"), - "topic_html": RequestEntityLink("{+entity_self_html}"), - "created_by": RequestEntityLink("{+entity_self}", entity="created_by"), - "created_by_html": RequestEntityLink( - "{+entity_self_html}", entity="created_by" + "topic": RequestEntityLinks( + ("self", "{+entity_self}", {}), # can't use self=RequestEntityLink... + ("self_html", "{+entity_self_html}", {}), + ), + "created_by": RequestEntityLinks( + ("self", "{+entity_self}", {}), + ("self_html", "{+entity_self_html}", {}), + entity="created_by", + ), + "receiver": RequestEntityLinks( + ("self", "{+entity_self}", {}), + ("self_html", "{+entity_self_html}", {}), + entity="receiver", ), - "receiver": RequestEntityLink("{+entity_self}", entity="receiver"), - "receiver_html": RequestEntityLink("{+entity_self_html}", entity="receiver"), - "type_specific": RequestTypeSpecificLinks(), } diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index 4e059de4..a240b371 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -9,11 +9,7 @@ from oarepo_requests.actions.edit_topic import EditTopicAcceptAction -from ..utils import ( - get_record_service_for_record_cls, - is_auto_approved, - request_identity_matches, -) +from ..utils import is_auto_approved, request_identity_matches from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -32,13 +28,11 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } - def links(self, request, **kwargs): - if request.status == "accepted": - service = get_record_service_for_record_cls(request.topic.record_cls) - record_item = service.read_draft( - kwargs["identity"], request.topic._parse_ref_dict_id() - ) - return {"topic_redirect_link": record_item["links"]["edit_html"]} + def type_links(self, request, **kwargs): + if request.status == "accepted" and kwargs["entity_type"] == "topic": + return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} + else: + return {} @classmethod @property diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index 60ff7da7..f57dfb59 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -9,11 +9,7 @@ from typing_extensions import override from ..actions.new_version import NewVersionAcceptAction -from ..utils import ( - get_record_service_for_record_cls, - is_auto_approved, - request_identity_matches, -) +from ..utils import is_auto_approved, request_identity_matches from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -35,13 +31,11 @@ class NewVersionRequestType( "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def links(self, request, **kwargs): - if request.status == "accepted": - service = get_record_service_for_record_cls(request.topic.record_cls) - record_item = service.read_draft( - kwargs["identity"], request.topic._parse_ref_dict_id() - ) - return {"topic_redirect_link": record_item["links"]["edit_html"]} + def type_links(self, request, **kwargs): + if request.status == "accepted" and kwargs["entity_type"] == "topic": + return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} + else: + return {} @classmethod @property diff --git a/setup.cfg b/setup.cfg index 4bb9dc3f..e54864e8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.2.11 +version = 2.2.12 description = authors = Ronald Krist readme = README.md diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py index 3097f7c0..256b9a6c 100644 --- a/tests/test_requests/test_new_version.py +++ b/tests/test_requests/test_new_version.py @@ -37,7 +37,6 @@ def test_new_version_autoaccept( assert request["status"] == "accepted" assert not request["is_open"] assert request["is_closed"] - assert "topic_redirect_link" in request["links"]["type_specific"] assert "draft_record:links:self" in request["payload"] assert "draft_record:links:self_html" in request["payload"] @@ -116,3 +115,43 @@ def test_new_version_files( assert len(record1["entries"]) == 1 assert len(record2["entries"]) == 0 + + +def test_redirect_url( + vocab_cf, + logged_client, + users, + urls, + new_version_data_function, + record_factory, + search_clear, +): + creator = users[0] + creator_client = logged_client(creator) + record1 = record_factory(creator.identity) + + resp_request_create = creator_client.post( + urls["BASE_URL_REQUESTS"], + json=new_version_data_function(record1["id"]), + ) + resp_request_submit = creator_client.post( + link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + ) + # is request accepted and closed? + request = creator_client.get( + f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', + ).json + + ThesisDraft.index.refresh() + draft_search = creator_client.get(f"/user/thesis/").json["hits"][ + "hits" + ] # a link is in another pull request for now + new_version = [ + x + for x in draft_search + if x["parent"]["id"] == record1.parent["id"] and x["state"] == "draft" + ][0] + assert ( + request["links"]["topic"]["topic_redirect_link"] + == f"https://127.0.0.1:5000/thesis/{new_version['id']}/edit" + ) From a60c94448cdb3fd9fcd387820961e61af7267585 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 15 Nov 2024 15:38:52 +0100 Subject: [PATCH 03/44] publish links test fix --- tests/test_requests/test_publish.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_requests/test_publish.py b/tests/test_requests/test_publish.py index ae892b61..b133516a 100644 --- a/tests/test_requests/test_publish.py +++ b/tests/test_requests/test_publish.py @@ -26,7 +26,8 @@ def test_publish_service(users, record_service, default_workflow_json, search_cl ) assert "created_by" in submit_result.links assert "topic" in submit_result.links - assert "topic_html" in submit_result.links + assert "self" in submit_result.links["topic"] + assert "self_html" in submit_result.links["topic"] accept_result = current_invenio_requests_service.execute_action( receiver.identity, request.id, "accept" From 709ab511a952eda39c045f740e3b72b6af09ba24 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 18 Nov 2024 12:42:19 +0100 Subject: [PATCH 04/44] ui resolvers allow keeping all links --- oarepo_requests/resolvers/ui.py | 52 ++++++++++++----------- oarepo_requests/services/oarepo/config.py | 15 ++----- oarepo_requests/types/edit_record.py | 2 +- oarepo_requests/types/new_version.py | 2 +- oarepo_requests/utils.py | 9 ---- 5 files changed, 34 insertions(+), 46 deletions(-) diff --git a/oarepo_requests/resolvers/ui.py b/oarepo_requests/resolvers/ui.py index 50ccaedc..79b31cd9 100644 --- a/oarepo_requests/resolvers/ui.py +++ b/oarepo_requests/resolvers/ui.py @@ -11,27 +11,29 @@ from ..utils import get_matching_service_for_refdict -def resolve(identity, reference): +def resolve(identity, reference, **kwargs): reference_type = list(reference.keys())[0] entity_resolvers = current_oarepo_requests.entity_reference_ui_resolvers if reference_type in entity_resolvers: - return entity_resolvers[reference_type].resolve_one(identity, reference) + return entity_resolvers[reference_type].resolve_one( + identity, reference, **kwargs + ) else: # TODO log warning - return entity_resolvers["fallback"].resolve_one(identity, reference) + return entity_resolvers["fallback"].resolve_one(identity, reference, **kwargs) -def fallback_label_result(reference): +def fallback_label_result(reference, **kwargs): id_ = list(reference.values())[0] return f"id: {id_}" -def fallback_result(reference): +def fallback_result(reference, **kwargs): type = list(reference.keys())[0] return { "reference": reference, "type": type, - "label": fallback_label_result(reference), + "label": fallback_label_result(reference, **kwargs), } @@ -48,20 +50,20 @@ def _search_many(self, identity, values, *args, **kwargs): def _search_one(self, identity, reference, *args, **kwargs): raise NotImplementedError("Parent entity ui resolver should be abstract") - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): raise NotImplementedError("Parent entity ui resolver should be abstract") - def resolve_one(self, identity, reference): + def resolve_one(self, identity, reference, **kwargs): try: record = self._search_one(identity, reference) if not record: - return fallback_result(reference) + return fallback_result(reference, **kwargs) except PIDDoesNotExistError: - return fallback_result(reference) - resolved = self._resolve(record, reference) + return fallback_result(reference, **kwargs) + resolved = self._resolve(record, reference, **kwargs) return resolved - def resolve_many(self, identity, values): + def resolve_many(self, identity, values, **kwargs): # the pattern is broken here by using values instead of reference? search_results = self._search_many(identity, values) ret = [] @@ -72,7 +74,7 @@ def resolve_many(self, identity, values): ) return ret - def _resolve_links(self, record): + def _resolve_links(self, record, keep_all_links=False, **kwargs): links = {} record_links = {} if isinstance(record, dict): @@ -81,6 +83,8 @@ def _resolve_links(self, record): elif hasattr(record, "data"): if "links" in record.data: record_links = record.data["links"] + if keep_all_links: + return record_links for link_type in ("self", "self_html"): if link_type in record_links: links[link_type] = record_links[link_type] @@ -109,13 +113,13 @@ def _search_one(self, identity, reference, *args, **kwargs): except PermissionDeniedError: return None - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): label = record.data["name"] ret = { "reference": reference, "type": "group", "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret @@ -142,7 +146,7 @@ def _search_one(self, identity, reference, *args, **kwargs): except PermissionDeniedError: return None - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): if record.data["id"] == "system": label = _("System user") @@ -155,12 +159,12 @@ def _resolve(self, record, reference): elif "username" in record.data and record.data["username"]: label = record.data["username"] else: - label = fallback_label_result(reference) + label = fallback_label_result(reference, **kwargs) ret = { "reference": reference, "type": "user", "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret @@ -185,16 +189,16 @@ def _search_one(self, identity, reference, *args, **kwargs): service = get_matching_service_for_refdict(reference) return service.read(identity, id).data - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): if "metadata" in record and "title" in record["metadata"]: label = record["metadata"]["title"] else: - label = fallback_label_result(reference) + label = fallback_label_result(reference, **kwargs) ret = { "reference": reference, "type": list(reference.keys())[0], "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret @@ -243,16 +247,16 @@ def _search_one(self, identity, reference, *args, **kwargs): response = response.data return response - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): if "metadata" in record and "title" in record["metadata"]: label = record["metadata"]["title"] else: - label = fallback_label_result(reference) + label = fallback_label_result(reference, **kwargs) ret = { "reference": reference, "type": list(reference.keys())[0], "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 928b6378..795e8c92 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -2,7 +2,7 @@ from invenio_requests.services import RequestsServiceConfig from invenio_requests.services.requests import RequestLink -from oarepo_requests.utils import get_record_service_for_record_cls +from oarepo_requests.resolvers.ui import resolve class RequestEntityLink(RequestLink): @@ -30,14 +30,7 @@ def _resolve(self, obj, ctx): if key in ctx: return ctx[key] try: - topic = obj.topic.resolve() - service = get_record_service_for_record_cls(obj.topic.record_cls) - reader = ( - service.read_draft - if getattr(topic, "is_draft", False) - else service.read - ) - entity = reader(ctx["identity"], obj.topic._parse_ref_dict_id()) + entity = resolve(ctx["identity"], reference_dict, keep_all_links=True) except Exception: # noqa entity = {} ctx[key] = entity @@ -75,10 +68,10 @@ def expand(self, obj, context): link = list(link_dict.values())[0] if link.should_render(obj, context): res[name] = link.expand(obj, context) - if hasattr(obj.type, "type_links"): + if hasattr(obj.type, "extra_request_links"): entity = self._resolve(obj, context) res.update( - obj.type.type_links( + obj.type.extra_request_links( request=obj, **(context | {"cur_entity": entity, "entity_type": self._entity}), ) diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index a240b371..b578e82d 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -28,7 +28,7 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } - def type_links(self, request, **kwargs): + def extra_request_links(self, request, **kwargs): if request.status == "accepted" and kwargs["entity_type"] == "topic": return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} else: diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index f57dfb59..4e6ef494 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -31,7 +31,7 @@ class NewVersionRequestType( "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def type_links(self, request, **kwargs): + def extra_request_links(self, request, **kwargs): if request.status == "accepted" and kwargs["entity_type"] == "topic": return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} else: diff --git a/oarepo_requests/utils.py b/oarepo_requests/utils.py index 7cdfe7d8..da453308 100644 --- a/oarepo_requests/utils.py +++ b/oarepo_requests/utils.py @@ -1,6 +1,5 @@ import copy -from flask import current_app from invenio_access.permissions import system_identity from invenio_pidstore.errors import PersistentIdentifierError from invenio_records_resources.proxies import current_service_registry @@ -210,11 +209,3 @@ def merge_resource_configs(config_to_merge_in, original_config): getattr(original_config, copy_from_original_key), ) return actual_config - - -def get_record_service_for_record_cls(record_cls): - if not record_cls: - return None - if "OAREPO_PRIMARY_RECORD_SERVICE" in current_app.config: - service_id = current_app.config["OAREPO_PRIMARY_RECORD_SERVICE"][record_cls] - return current_service_registry.get(service_id) From 0eb62e3c49b9e401aad27db94cbaf0e6a469a5ed Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 18 Nov 2024 13:13:15 +0100 Subject: [PATCH 05/44] bug fix --- oarepo_requests/services/oarepo/config.py | 4 ++-- tests/test_requests/test_publish.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 795e8c92..1dc138c5 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -37,8 +37,8 @@ def _resolve(self, obj, ctx): return entity def _expand_entity(self, entity, vars): - if hasattr(entity, "links"): - vars.update({f"entity_{k}": v for k, v in entity.links.items()}) + if "links" in entity: + vars.update({f"entity_{k}": v for k, v in entity["links"].items()}) def expand(self, obj, context): """Expand the URI Template.""" diff --git a/tests/test_requests/test_publish.py b/tests/test_requests/test_publish.py index b133516a..b4f0e9b6 100644 --- a/tests/test_requests/test_publish.py +++ b/tests/test_requests/test_publish.py @@ -24,6 +24,11 @@ def test_publish_service(users, record_service, default_workflow_json, search_cl submit_result = current_invenio_requests_service.execute_action( creator.identity, request.id, "submit" ) + assert "created_by" in request.links + assert "topic" in request.links + assert "self" in request.links["topic"] + assert "self_html" in request.links["topic"] + assert "created_by" in submit_result.links assert "topic" in submit_result.links assert "self" in submit_result.links["topic"] From d17d91a19b606ab56767aa10ed4066c9f9ef3d79 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 18 Nov 2024 16:20:58 +0100 Subject: [PATCH 06/44] endpoint for directly creating events --- oarepo_requests/resources/events/resource.py | 49 +++++++++++- tests/test_requests/test_workflows.py | 83 ++++++++++++++++++++ 2 files changed, 131 insertions(+), 1 deletion(-) diff --git a/oarepo_requests/resources/events/resource.py b/oarepo_requests/resources/events/resource.py index 884dd35d..44c4ee1c 100644 --- a/oarepo_requests/resources/events/resource.py +++ b/oarepo_requests/resources/events/resource.py @@ -1,9 +1,29 @@ -from flask_resources import route +from copy import deepcopy + +from flask import g +from flask_resources import ( + from_conf, + request_body_parser, + request_parser, + resource_requestctx, + response_handler, + route, +) from invenio_records_resources.resources.errors import ErrorHandlersMixin +from invenio_records_resources.resources.records.resource import request_extra_args +from invenio_requests.proxies import current_event_type_registry from invenio_requests.resources.events.resource import RequestCommentsResource class OARepoRequestsCommentsResource(RequestCommentsResource, ErrorHandlersMixin): + list_view_args_parser = request_parser( + from_conf("request_list_view_args"), location="view_args" + ) + data_parser = request_body_parser( + parsers=from_conf("request_body_parsers"), + default_content_type=from_conf("default_content_type"), + ) + def create_url_rules(self): """Create the URL rules for the record resource.""" base_routes = super().create_url_rules() @@ -11,6 +31,13 @@ def create_url_rules(self): url_rules = [ route("POST", routes["list-extended"], self.create_extended), + route("POST", routes["timeline"], self.create_event), + route( + "POST", + routes["timeline-extended"], + self.create_event, + endpoint="create_event_extended", + ), route("GET", routes["item-extended"], self.read_extended), route("PUT", routes["item-extended"], self.update_extended), route("DELETE", routes["item-extended"], self.delete_extended), @@ -33,3 +60,23 @@ def delete_extended(self): def search_extended(self): return super().search() + + # for now we can just use the invenio method without hardcoded event type + # todo - where is the event type specified + # todo - extended endpoint? + @list_view_args_parser + @request_extra_args + @data_parser + @response_handler() + def create_event(self): + """Create a comment.""" + data = deepcopy(resource_requestctx.data) if resource_requestctx.data else {} + type_ = current_event_type_registry.lookup(data.get("type"), quiet=True) + item = self.service.create( + identity=g.identity, + request_id=resource_requestctx.view_args["request_id"], + data=data, + event_type=type_, + expand=resource_requestctx.args.get("expand", False), + ) + return item.to_dict(), 201 diff --git a/tests/test_requests/test_workflows.py b/tests/test_requests/test_workflows.py index de5e749d..048b7cd6 100644 --- a/tests/test_requests/test_workflows.py +++ b/tests/test_requests/test_workflows.py @@ -412,6 +412,89 @@ def test_workflow_events( assert create_event_u1 +def test_workflow_events_resource( + logged_client, + users, + urls, + patch_requests_permissions, + record_service, + publish_request_data_function, + serialization_result, + ui_serialization_result, + events_resource_data, + create_draft_via_resource, + events_service, + search_clear, +): + user1 = users[0] + user2 = users[1] + + user1_client = logged_client(user1) + user2_client = logged_client(user2) + + draft1 = create_draft_via_resource(user1_client, custom_workflow="with_approve") + record_id = draft1.json["id"] + + approve_request_data = { + "request_type": "approve_draft", + "topic": {"thesis_draft": str(record_id)}, + } + resp_request_create = user1_client.post( + urls["BASE_URL_REQUESTS"], + json=approve_request_data, + ) + resp_request_submit = user1_client.post( + link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + ) + + read_from_record = user1_client.get( + f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true", + ) + + request_id = read_from_record.json["expanded"]["requests"][0]["id"] + json = {**events_resource_data, "type": TestEventType.type_id} + create_event_u1 = user1_client.post( + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + ) + create_event_u2 = user2_client.post( + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + ) + + assert create_event_u1.status_code == 403 + assert create_event_u2.status_code == 201 + + record_receiver = user2_client.get( + f'{urls["BASE_URL"]}{record_id}/draft?expand=true' + ).json + accept = user2_client.post( + link_api2testclient( + record_receiver["expanded"]["requests"][0]["links"]["actions"]["accept"] + ), + ) + assert accept.status_code == 200 + publishing_record = record_service.read_draft(user1.identity, record_id)._record + assert publishing_record["state"] == "publishing" + + read_from_record = user2_client.get( + f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true", + ) + publish_request = [ + request + for request in read_from_record.json["expanded"]["requests"] + if request["type"] == "publish_draft" + ][0] + request_id = publish_request["id"] + + create_event_u1 = user1_client.post( + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + ) + create_event_u2 = user2_client.post( + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + ) + assert create_event_u1.status_code == 201 + assert create_event_u2.status_code == 403 + + def test_delete_log( vocab_cf, patch_requests_permissions, From c905e5bc2c4a88ca3809ac410831c9a96b4b2ef9 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Tue, 12 Nov 2024 15:01:23 +0100 Subject: [PATCH 07/44] intermediary commit --- oarepo_requests/resolvers/ui.py | 2 +- oarepo_requests/services/oarepo/config.py | 21 +++++++++++++++++++++ oarepo_requests/types/edit_record.py | 14 +++++++++++++- oarepo_requests/types/new_version.py | 14 +++++++++++++- oarepo_requests/utils.py | 9 +++++++++ tests/test_requests/test_new_version.py | 1 + tests/test_ui/test_ui_resource.py | 1 + 7 files changed, 59 insertions(+), 3 deletions(-) diff --git a/oarepo_requests/resolvers/ui.py b/oarepo_requests/resolvers/ui.py index d76a6850..50ccaedc 100644 --- a/oarepo_requests/resolvers/ui.py +++ b/oarepo_requests/resolvers/ui.py @@ -1,3 +1,4 @@ +from invenio_pidstore.errors import PIDDoesNotExistError from invenio_records_resources.resources.errors import PermissionDeniedError from invenio_search.engine import dsl from invenio_users_resources.proxies import ( @@ -5,7 +6,6 @@ current_users_service, ) from oarepo_runtime.i18n import gettext as _ -from invenio_pidstore.errors import PIDDoesNotExistError from ..proxies import current_oarepo_requests from ..utils import get_matching_service_for_refdict diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index eee89aff..ca1c967d 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -46,6 +46,26 @@ def expand(self, obj, context): self._resolve(obj, context) return super().expand(obj, context) + +class RequestTypeSpecificLinks(RequestLink): + """Utility class for keeping track of and resolve links.""" + + def __init__(self, when=None, vars=None): + """Constructor.""" + self._when_func = when + self._vars_func = vars + + def should_render(self, obj, ctx): + """Determine if the link should be rendered.""" + if not hasattr(obj.type, "links"): + return False + return super().should_render(obj, ctx) + + def expand(self, obj, context): + """Expand the URI Template.""" + return obj.type.links(request=obj, **context) + + class OARepoRequestsServiceConfig(RequestsServiceConfig): service_id = "oarepo_requests" @@ -62,4 +82,5 @@ class OARepoRequestsServiceConfig(RequestsServiceConfig): ), "receiver": RequestEntityLink("{+entity_self}", entity="receiver"), "receiver_html": RequestEntityLink("{+entity_self_html}", entity="receiver"), + "type_specific": RequestTypeSpecificLinks(), } diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index 9ecbf3f9..4e059de4 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -9,7 +9,11 @@ from oarepo_requests.actions.edit_topic import EditTopicAcceptAction -from ..utils import is_auto_approved, request_identity_matches +from ..utils import ( + get_record_service_for_record_cls, + is_auto_approved, + request_identity_matches, +) from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -28,6 +32,14 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } + def links(self, request, **kwargs): + if request.status == "accepted": + service = get_record_service_for_record_cls(request.topic.record_cls) + record_item = service.read_draft( + kwargs["identity"], request.topic._parse_ref_dict_id() + ) + return {"topic_redirect_link": record_item["links"]["edit_html"]} + @classmethod @property def available_actions(cls): diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index c24363ba..60ff7da7 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -9,7 +9,11 @@ from typing_extensions import override from ..actions.new_version import NewVersionAcceptAction -from ..utils import is_auto_approved, request_identity_matches +from ..utils import ( + get_record_service_for_record_cls, + is_auto_approved, + request_identity_matches, +) from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -31,6 +35,14 @@ class NewVersionRequestType( "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } + def links(self, request, **kwargs): + if request.status == "accepted": + service = get_record_service_for_record_cls(request.topic.record_cls) + record_item = service.read_draft( + kwargs["identity"], request.topic._parse_ref_dict_id() + ) + return {"topic_redirect_link": record_item["links"]["edit_html"]} + @classmethod @property def available_actions(cls): diff --git a/oarepo_requests/utils.py b/oarepo_requests/utils.py index da453308..7cdfe7d8 100644 --- a/oarepo_requests/utils.py +++ b/oarepo_requests/utils.py @@ -1,5 +1,6 @@ import copy +from flask import current_app from invenio_access.permissions import system_identity from invenio_pidstore.errors import PersistentIdentifierError from invenio_records_resources.proxies import current_service_registry @@ -209,3 +210,11 @@ def merge_resource_configs(config_to_merge_in, original_config): getattr(original_config, copy_from_original_key), ) return actual_config + + +def get_record_service_for_record_cls(record_cls): + if not record_cls: + return None + if "OAREPO_PRIMARY_RECORD_SERVICE" in current_app.config: + service_id = current_app.config["OAREPO_PRIMARY_RECORD_SERVICE"][record_cls] + return current_service_registry.get(service_id) diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py index 86bbb05f..3097f7c0 100644 --- a/tests/test_requests/test_new_version.py +++ b/tests/test_requests/test_new_version.py @@ -37,6 +37,7 @@ def test_new_version_autoaccept( assert request["status"] == "accepted" assert not request["is_open"] assert request["is_closed"] + assert "topic_redirect_link" in request["links"]["type_specific"] assert "draft_record:links:self" in request["payload"] assert "draft_record:links:self_html" in request["payload"] diff --git a/tests/test_ui/test_ui_resource.py b/tests/test_ui/test_ui_resource.py index aa2c6fd2..94f75df8 100644 --- a/tests/test_ui/test_ui_resource.py +++ b/tests/test_ui/test_ui_resource.py @@ -94,6 +94,7 @@ def test_request_detail_page( assert c.status_code == 200 print(c.text) + def test_form_config(app, client, record_ui_resource, fake_manifest): with client.get("/requests/configs/publish_draft") as c: assert c.json == { From e3ba8acb68e2e4196b30cea7b548eed62f68dbf1 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 15 Nov 2024 15:32:22 +0100 Subject: [PATCH 08/44] request entity links --- oarepo_requests/services/oarepo/config.py | 71 +++++++++++++++-------- oarepo_requests/types/edit_record.py | 18 ++---- oarepo_requests/types/new_version.py | 18 ++---- setup.cfg | 2 +- tests/test_requests/test_new_version.py | 41 ++++++++++++- 5 files changed, 101 insertions(+), 49 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index ca1c967d..928b6378 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -2,13 +2,13 @@ from invenio_requests.services import RequestsServiceConfig from invenio_requests.services.requests import RequestLink -from oarepo_requests.resolvers.ui import resolve +from oarepo_requests.utils import get_record_service_for_record_cls class RequestEntityLink(RequestLink): def __init__(self, uritemplate, when=None, vars=None, entity="topic"): super().__init__(uritemplate, when, vars) - self.entity = entity + self._entity = entity def vars(self, record: Request, vars): super().vars(record, vars) @@ -23,21 +23,29 @@ def should_render(self, obj, ctx): return True def _resolve(self, obj, ctx): - reference_dict = getattr(obj, self.entity).reference_dict + reference_dict = getattr(obj, self._entity).reference_dict key = "entity:" + ":".join( f"{x[0]}:{x[1]}" for x in sorted(reference_dict.items()) ) if key in ctx: return ctx[key] try: - entity = resolve(ctx["identity"], reference_dict) + topic = obj.topic.resolve() + service = get_record_service_for_record_cls(obj.topic.record_cls) + reader = ( + service.read_draft + if getattr(topic, "is_draft", False) + else service.read + ) + entity = reader(ctx["identity"], obj.topic._parse_ref_dict_id()) except Exception: # noqa entity = {} ctx[key] = entity return entity def _expand_entity(self, entity, vars): - vars.update({f"entity_{k}": v for k, v in entity.get("links", {}).items()}) + if hasattr(entity, "links"): + vars.update({f"entity_{k}": v for k, v in entity.links.items()}) def expand(self, obj, context): """Expand the URI Template.""" @@ -47,23 +55,35 @@ def expand(self, obj, context): return super().expand(obj, context) -class RequestTypeSpecificLinks(RequestLink): +class RequestEntityLinks(RequestEntityLink): """Utility class for keeping track of and resolve links.""" - def __init__(self, when=None, vars=None): + def __init__(self, *request_entity_links, entity="topic", when=None, vars=None): """Constructor.""" + self._request_entity_links = [ + {name: RequestEntityLink(link, entity=entity, **kwargs)} + for name, link, kwargs in request_entity_links + ] + self._entity = entity self._when_func = when self._vars_func = vars - def should_render(self, obj, ctx): - """Determine if the link should be rendered.""" - if not hasattr(obj.type, "links"): - return False - return super().should_render(obj, ctx) - def expand(self, obj, context): - """Expand the URI Template.""" - return obj.type.links(request=obj, **context) + res = {} + for link_dict in self._request_entity_links: + name = list(link_dict.keys())[0] + link = list(link_dict.values())[0] + if link.should_render(obj, context): + res[name] = link.expand(obj, context) + if hasattr(obj.type, "type_links"): + entity = self._resolve(obj, context) + res.update( + obj.type.type_links( + request=obj, + **(context | {"cur_entity": entity, "entity_type": self._entity}), + ) + ) + return res class OARepoRequestsServiceConfig(RequestsServiceConfig): @@ -74,13 +94,18 @@ class OARepoRequestsServiceConfig(RequestsServiceConfig): "comments": RequestLink("{+api}/requests/extended/{id}/comments"), "timeline": RequestLink("{+api}/requests/extended/{id}/timeline"), "self_html": RequestLink("{+ui}/requests/{id}"), - "topic": RequestEntityLink("{+entity_self}"), - "topic_html": RequestEntityLink("{+entity_self_html}"), - "created_by": RequestEntityLink("{+entity_self}", entity="created_by"), - "created_by_html": RequestEntityLink( - "{+entity_self_html}", entity="created_by" + "topic": RequestEntityLinks( + ("self", "{+entity_self}", {}), # can't use self=RequestEntityLink... + ("self_html", "{+entity_self_html}", {}), + ), + "created_by": RequestEntityLinks( + ("self", "{+entity_self}", {}), + ("self_html", "{+entity_self_html}", {}), + entity="created_by", + ), + "receiver": RequestEntityLinks( + ("self", "{+entity_self}", {}), + ("self_html", "{+entity_self_html}", {}), + entity="receiver", ), - "receiver": RequestEntityLink("{+entity_self}", entity="receiver"), - "receiver_html": RequestEntityLink("{+entity_self_html}", entity="receiver"), - "type_specific": RequestTypeSpecificLinks(), } diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index 4e059de4..a240b371 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -9,11 +9,7 @@ from oarepo_requests.actions.edit_topic import EditTopicAcceptAction -from ..utils import ( - get_record_service_for_record_cls, - is_auto_approved, - request_identity_matches, -) +from ..utils import is_auto_approved, request_identity_matches from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -32,13 +28,11 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } - def links(self, request, **kwargs): - if request.status == "accepted": - service = get_record_service_for_record_cls(request.topic.record_cls) - record_item = service.read_draft( - kwargs["identity"], request.topic._parse_ref_dict_id() - ) - return {"topic_redirect_link": record_item["links"]["edit_html"]} + def type_links(self, request, **kwargs): + if request.status == "accepted" and kwargs["entity_type"] == "topic": + return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} + else: + return {} @classmethod @property diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index 60ff7da7..f57dfb59 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -9,11 +9,7 @@ from typing_extensions import override from ..actions.new_version import NewVersionAcceptAction -from ..utils import ( - get_record_service_for_record_cls, - is_auto_approved, - request_identity_matches, -) +from ..utils import is_auto_approved, request_identity_matches from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes @@ -35,13 +31,11 @@ class NewVersionRequestType( "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def links(self, request, **kwargs): - if request.status == "accepted": - service = get_record_service_for_record_cls(request.topic.record_cls) - record_item = service.read_draft( - kwargs["identity"], request.topic._parse_ref_dict_id() - ) - return {"topic_redirect_link": record_item["links"]["edit_html"]} + def type_links(self, request, **kwargs): + if request.status == "accepted" and kwargs["entity_type"] == "topic": + return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} + else: + return {} @classmethod @property diff --git a/setup.cfg b/setup.cfg index 4bb9dc3f..e54864e8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.2.11 +version = 2.2.12 description = authors = Ronald Krist readme = README.md diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py index 3097f7c0..256b9a6c 100644 --- a/tests/test_requests/test_new_version.py +++ b/tests/test_requests/test_new_version.py @@ -37,7 +37,6 @@ def test_new_version_autoaccept( assert request["status"] == "accepted" assert not request["is_open"] assert request["is_closed"] - assert "topic_redirect_link" in request["links"]["type_specific"] assert "draft_record:links:self" in request["payload"] assert "draft_record:links:self_html" in request["payload"] @@ -116,3 +115,43 @@ def test_new_version_files( assert len(record1["entries"]) == 1 assert len(record2["entries"]) == 0 + + +def test_redirect_url( + vocab_cf, + logged_client, + users, + urls, + new_version_data_function, + record_factory, + search_clear, +): + creator = users[0] + creator_client = logged_client(creator) + record1 = record_factory(creator.identity) + + resp_request_create = creator_client.post( + urls["BASE_URL_REQUESTS"], + json=new_version_data_function(record1["id"]), + ) + resp_request_submit = creator_client.post( + link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + ) + # is request accepted and closed? + request = creator_client.get( + f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', + ).json + + ThesisDraft.index.refresh() + draft_search = creator_client.get(f"/user/thesis/").json["hits"][ + "hits" + ] # a link is in another pull request for now + new_version = [ + x + for x in draft_search + if x["parent"]["id"] == record1.parent["id"] and x["state"] == "draft" + ][0] + assert ( + request["links"]["topic"]["topic_redirect_link"] + == f"https://127.0.0.1:5000/thesis/{new_version['id']}/edit" + ) From 3d7122608f6864dc7397b9c4765deecbba111203 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 15 Nov 2024 15:38:52 +0100 Subject: [PATCH 09/44] publish links test fix --- tests/test_requests/test_publish.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_requests/test_publish.py b/tests/test_requests/test_publish.py index ae892b61..b133516a 100644 --- a/tests/test_requests/test_publish.py +++ b/tests/test_requests/test_publish.py @@ -26,7 +26,8 @@ def test_publish_service(users, record_service, default_workflow_json, search_cl ) assert "created_by" in submit_result.links assert "topic" in submit_result.links - assert "topic_html" in submit_result.links + assert "self" in submit_result.links["topic"] + assert "self_html" in submit_result.links["topic"] accept_result = current_invenio_requests_service.execute_action( receiver.identity, request.id, "accept" From 3d3ffc804c839653cac741708902571046be5088 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 18 Nov 2024 12:42:19 +0100 Subject: [PATCH 10/44] ui resolvers allow keeping all links --- oarepo_requests/resolvers/ui.py | 52 ++++++++++++----------- oarepo_requests/services/oarepo/config.py | 15 ++----- oarepo_requests/types/edit_record.py | 2 +- oarepo_requests/types/new_version.py | 2 +- oarepo_requests/utils.py | 9 ---- 5 files changed, 34 insertions(+), 46 deletions(-) diff --git a/oarepo_requests/resolvers/ui.py b/oarepo_requests/resolvers/ui.py index 50ccaedc..79b31cd9 100644 --- a/oarepo_requests/resolvers/ui.py +++ b/oarepo_requests/resolvers/ui.py @@ -11,27 +11,29 @@ from ..utils import get_matching_service_for_refdict -def resolve(identity, reference): +def resolve(identity, reference, **kwargs): reference_type = list(reference.keys())[0] entity_resolvers = current_oarepo_requests.entity_reference_ui_resolvers if reference_type in entity_resolvers: - return entity_resolvers[reference_type].resolve_one(identity, reference) + return entity_resolvers[reference_type].resolve_one( + identity, reference, **kwargs + ) else: # TODO log warning - return entity_resolvers["fallback"].resolve_one(identity, reference) + return entity_resolvers["fallback"].resolve_one(identity, reference, **kwargs) -def fallback_label_result(reference): +def fallback_label_result(reference, **kwargs): id_ = list(reference.values())[0] return f"id: {id_}" -def fallback_result(reference): +def fallback_result(reference, **kwargs): type = list(reference.keys())[0] return { "reference": reference, "type": type, - "label": fallback_label_result(reference), + "label": fallback_label_result(reference, **kwargs), } @@ -48,20 +50,20 @@ def _search_many(self, identity, values, *args, **kwargs): def _search_one(self, identity, reference, *args, **kwargs): raise NotImplementedError("Parent entity ui resolver should be abstract") - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): raise NotImplementedError("Parent entity ui resolver should be abstract") - def resolve_one(self, identity, reference): + def resolve_one(self, identity, reference, **kwargs): try: record = self._search_one(identity, reference) if not record: - return fallback_result(reference) + return fallback_result(reference, **kwargs) except PIDDoesNotExistError: - return fallback_result(reference) - resolved = self._resolve(record, reference) + return fallback_result(reference, **kwargs) + resolved = self._resolve(record, reference, **kwargs) return resolved - def resolve_many(self, identity, values): + def resolve_many(self, identity, values, **kwargs): # the pattern is broken here by using values instead of reference? search_results = self._search_many(identity, values) ret = [] @@ -72,7 +74,7 @@ def resolve_many(self, identity, values): ) return ret - def _resolve_links(self, record): + def _resolve_links(self, record, keep_all_links=False, **kwargs): links = {} record_links = {} if isinstance(record, dict): @@ -81,6 +83,8 @@ def _resolve_links(self, record): elif hasattr(record, "data"): if "links" in record.data: record_links = record.data["links"] + if keep_all_links: + return record_links for link_type in ("self", "self_html"): if link_type in record_links: links[link_type] = record_links[link_type] @@ -109,13 +113,13 @@ def _search_one(self, identity, reference, *args, **kwargs): except PermissionDeniedError: return None - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): label = record.data["name"] ret = { "reference": reference, "type": "group", "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret @@ -142,7 +146,7 @@ def _search_one(self, identity, reference, *args, **kwargs): except PermissionDeniedError: return None - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): if record.data["id"] == "system": label = _("System user") @@ -155,12 +159,12 @@ def _resolve(self, record, reference): elif "username" in record.data and record.data["username"]: label = record.data["username"] else: - label = fallback_label_result(reference) + label = fallback_label_result(reference, **kwargs) ret = { "reference": reference, "type": "user", "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret @@ -185,16 +189,16 @@ def _search_one(self, identity, reference, *args, **kwargs): service = get_matching_service_for_refdict(reference) return service.read(identity, id).data - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): if "metadata" in record and "title" in record["metadata"]: label = record["metadata"]["title"] else: - label = fallback_label_result(reference) + label = fallback_label_result(reference, **kwargs) ret = { "reference": reference, "type": list(reference.keys())[0], "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret @@ -243,16 +247,16 @@ def _search_one(self, identity, reference, *args, **kwargs): response = response.data return response - def _resolve(self, record, reference): + def _resolve(self, record, reference, **kwargs): if "metadata" in record and "title" in record["metadata"]: label = record["metadata"]["title"] else: - label = fallback_label_result(reference) + label = fallback_label_result(reference, **kwargs) ret = { "reference": reference, "type": list(reference.keys())[0], "label": label, - "links": self._resolve_links(record), + "links": self._resolve_links(record, **kwargs), } return ret diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 928b6378..795e8c92 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -2,7 +2,7 @@ from invenio_requests.services import RequestsServiceConfig from invenio_requests.services.requests import RequestLink -from oarepo_requests.utils import get_record_service_for_record_cls +from oarepo_requests.resolvers.ui import resolve class RequestEntityLink(RequestLink): @@ -30,14 +30,7 @@ def _resolve(self, obj, ctx): if key in ctx: return ctx[key] try: - topic = obj.topic.resolve() - service = get_record_service_for_record_cls(obj.topic.record_cls) - reader = ( - service.read_draft - if getattr(topic, "is_draft", False) - else service.read - ) - entity = reader(ctx["identity"], obj.topic._parse_ref_dict_id()) + entity = resolve(ctx["identity"], reference_dict, keep_all_links=True) except Exception: # noqa entity = {} ctx[key] = entity @@ -75,10 +68,10 @@ def expand(self, obj, context): link = list(link_dict.values())[0] if link.should_render(obj, context): res[name] = link.expand(obj, context) - if hasattr(obj.type, "type_links"): + if hasattr(obj.type, "extra_request_links"): entity = self._resolve(obj, context) res.update( - obj.type.type_links( + obj.type.extra_request_links( request=obj, **(context | {"cur_entity": entity, "entity_type": self._entity}), ) diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index a240b371..b578e82d 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -28,7 +28,7 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } - def type_links(self, request, **kwargs): + def extra_request_links(self, request, **kwargs): if request.status == "accepted" and kwargs["entity_type"] == "topic": return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} else: diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index f57dfb59..4e6ef494 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -31,7 +31,7 @@ class NewVersionRequestType( "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def type_links(self, request, **kwargs): + def extra_request_links(self, request, **kwargs): if request.status == "accepted" and kwargs["entity_type"] == "topic": return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} else: diff --git a/oarepo_requests/utils.py b/oarepo_requests/utils.py index 7cdfe7d8..da453308 100644 --- a/oarepo_requests/utils.py +++ b/oarepo_requests/utils.py @@ -1,6 +1,5 @@ import copy -from flask import current_app from invenio_access.permissions import system_identity from invenio_pidstore.errors import PersistentIdentifierError from invenio_records_resources.proxies import current_service_registry @@ -210,11 +209,3 @@ def merge_resource_configs(config_to_merge_in, original_config): getattr(original_config, copy_from_original_key), ) return actual_config - - -def get_record_service_for_record_cls(record_cls): - if not record_cls: - return None - if "OAREPO_PRIMARY_RECORD_SERVICE" in current_app.config: - service_id = current_app.config["OAREPO_PRIMARY_RECORD_SERVICE"][record_cls] - return current_service_registry.get(service_id) From ae6c682b77620eaf373f610efe74cedf6ef4db00 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 18 Nov 2024 13:13:15 +0100 Subject: [PATCH 11/44] bug fix --- oarepo_requests/services/oarepo/config.py | 4 ++-- tests/test_requests/test_publish.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 795e8c92..1dc138c5 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -37,8 +37,8 @@ def _resolve(self, obj, ctx): return entity def _expand_entity(self, entity, vars): - if hasattr(entity, "links"): - vars.update({f"entity_{k}": v for k, v in entity.links.items()}) + if "links" in entity: + vars.update({f"entity_{k}": v for k, v in entity["links"].items()}) def expand(self, obj, context): """Expand the URI Template.""" diff --git a/tests/test_requests/test_publish.py b/tests/test_requests/test_publish.py index b133516a..b4f0e9b6 100644 --- a/tests/test_requests/test_publish.py +++ b/tests/test_requests/test_publish.py @@ -24,6 +24,11 @@ def test_publish_service(users, record_service, default_workflow_json, search_cl submit_result = current_invenio_requests_service.execute_action( creator.identity, request.id, "submit" ) + assert "created_by" in request.links + assert "topic" in request.links + assert "self" in request.links["topic"] + assert "self_html" in request.links["topic"] + assert "created_by" in submit_result.links assert "topic" in submit_result.links assert "self" in submit_result.links["topic"] From b8074bb96999219f1a5dd3c1133f65f94f12ceb5 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 22 Nov 2024 16:07:07 +0100 Subject: [PATCH 12/44] request links bug fixes --- oarepo_requests/actions/cascade_events.py | 22 ++-- oarepo_requests/resolvers/ui.py | 6 +- oarepo_requests/services/oarepo/config.py | 100 +++++------------- tests/conftest.py | 24 ++++- tests/test_requests/test_new_version.py | 6 +- tests/test_requests/test_timeline.py | 3 +- tests/test_requests/test_topic_resolve.py | 20 ++-- .../test_requests/test_ui_serialialization.py | 15 +-- 8 files changed, 91 insertions(+), 105 deletions(-) diff --git a/oarepo_requests/actions/cascade_events.py b/oarepo_requests/actions/cascade_events.py index 88924e96..1517b005 100644 --- a/oarepo_requests/actions/cascade_events.py +++ b/oarepo_requests/actions/cascade_events.py @@ -74,14 +74,19 @@ def update_topic( old_topic_ref = _get_topic_reference(old_topic) requests_with_topic = _get_requests_with_topic_reference(old_topic_ref) new_topic_ref = ResolverRegistry.reference_entity(new_topic) - for request_from_search in requests_with_topic: + for ( + request_from_search + ) in ( + requests_with_topic._results + ): # result list links might crash before update of the topic + request_from_search_id = request_from_search["uuid"] request_type = current_request_type_registry.lookup( request_from_search["type"], quiet=True ) if hasattr(request_type, "topic_change"): cur_request = ( - Request.get_record(request_from_search["id"]) - if request_from_search["id"] != str(request.id) + Request.get_record(request_from_search_id) + if request_from_search_id != str(request.id) else request ) # request on which the action is executed is recommited later, the change must be done on the same instance request_type.topic_change(cur_request, new_topic_ref, uow) @@ -103,14 +108,19 @@ def cancel_requests_on_topic_delete( topic_ref = _get_topic_reference(topic) requests_with_topic = _get_requests_with_topic_reference(topic_ref) - for request_from_search in requests_with_topic: + for ( + request_from_search + ) in ( + requests_with_topic._results + ): # result list links might crash before update of the topic + request_from_search_id = request_from_search["uuid"] request_type = current_request_type_registry.lookup( request_from_search["type"], quiet=True ) if hasattr(request_type, "on_topic_delete"): - if request_from_search["id"] == str(request.id): + if request_from_search_id == str(request.id): continue - cur_request = Request.get_record(request_from_search["id"]) + cur_request = Request.get_record(request_from_search_id) if cur_request.is_open: request_type.on_topic_delete( cur_request, uow diff --git a/oarepo_requests/resolvers/ui.py b/oarepo_requests/resolvers/ui.py index e6bcd391..e2aff58a 100644 --- a/oarepo_requests/resolvers/ui.py +++ b/oarepo_requests/resolvers/ui.py @@ -196,14 +196,10 @@ def _extract_links_from_resolved_reference( self, resolved_reference: dict ) -> dict[str, str]: """Extract links from a entity.""" - links = {} entity_links = {} if "links" in resolved_reference: entity_links = resolved_reference["links"] - for link_type in ("self", "self_html"): - if link_type in entity_links: - links[link_type] = entity_links[link_type] - return links + return entity_links class GroupEntityReferenceUIResolver(OARepoUIResolver): diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 6fab8b90..e6e598de 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -9,8 +9,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, cast +import logging +from typing import TYPE_CHECKING, Any +from invenio_pidstore.errors import PersistentIdentifierError +from invenio_records_resources.services.base.links import Link from invenio_requests.services import RequestsServiceConfig from invenio_requests.services.requests import RequestLink @@ -18,34 +21,11 @@ if TYPE_CHECKING: from invenio_requests.records.api import Request +log = logging.getLogger(__name__) -class RequestEntityLink(RequestLink): - """Link to an entity within a request.""" - - def __init__( - self, - uritemplate: str, - when: Callable | None = None, - vars: dict | None = None, - entity: str = "topic", - ) -> None: - """Create a new link.""" - super().__init__(uritemplate, when, vars) - self._entity = entity - - def vars(self, record: Request, vars: dict) -> dict: - """Expand the vars with the entity.""" - super().vars(record, vars) - entity = self._resolve(record, vars) - self._expand_entity(entity, vars) - return vars - - def should_render(self, obj: Request, ctx: dict[str, Any]) -> bool: - """Check if the link should be rendered.""" - if not super().should_render(obj, ctx): - return False - return bool(self.expand(obj, ctx)) +class RequestEntityLinks(Link): + """Utility class for keeping track of and resolve links.""" def _resolve(self, obj: Request, ctx: dict[str, Any]) -> dict: """Resolve the entity and put it into the context cache. @@ -54,53 +34,35 @@ def _resolve(self, obj: Request, ctx: dict[str, Any]) -> dict: :param ctx: Context cache :return: The resolved entity """ - reference_dict: dict = getattr(obj, self.entity).reference_dict + reference_dict: dict = getattr(obj, self._entity).reference_dict key = "entity:" + ":".join( f"{x[0]}:{x[1]}" for x in sorted(reference_dict.items()) ) if key in ctx: return ctx[key] try: - entity = resolve(ctx["identity"], reference_dict, keep_all_links=True) - except Exception: # noqa - entity = {} + entity = resolve(ctx["identity"], reference_dict) + except Exception as e: # noqa + if not isinstance(e, PersistentIdentifierError): + log.exception( + "Error resolving %s for identity %s", + reference_dict, + ctx["identity"], + ) + entity = {"links": {}} ctx[key] = entity return entity - def _expand_entity(self, entity: Any, vars: dict) -> None: - """Expand the entity links into the vars.""" - vars.update({f"entity_{k}": v for k, v in entity.get("links", {}).items()}) - - def expand(self, obj: Request, context: dict[str, Any]) -> str: - """Expand the URI Template.""" - # Optimization: pre-resolve the entity and put it into the shared context - # under the key - so that it can be reused by other links - self._resolve(obj, context) - - # now expand the link - return super().expand(obj, context) - - -class RequestEntityLinks(RequestEntityLink): - """Utility class for keeping track of and resolve links.""" - - def __init__(self, *request_entity_links, entity="topic", when=None, vars=None): + def __init__(self, entity: str, when: callable = None): """Constructor.""" - self._request_entity_links = [ - {name: RequestEntityLink(link, entity=entity, **kwargs)} - for name, link, kwargs in request_entity_links - ] self._entity = entity self._when_func = when - self._vars_func = vars - def expand(self, obj, context): + def expand(self, obj: Request, context: dict) -> dict: + """Create the request links.""" res = {} - for link_dict in self._request_entity_links: - name = list(link_dict.keys())[0] - link = list(link_dict.values())[0] - if link.should_render(obj, context): - res[name] = link.expand(obj, context) + res.update(self._resolve(obj, context)["links"]) + if hasattr(obj.type, "extra_request_links"): entity = self._resolve(obj, context) res.update( @@ -109,6 +71,7 @@ def expand(self, obj, context): **(context | {"cur_entity": entity, "entity_type": self._entity}), ) ) + return res @@ -122,18 +85,7 @@ class OARepoRequestsServiceConfig(RequestsServiceConfig): "comments": RequestLink("{+api}/requests/extended/{id}/comments"), "timeline": RequestLink("{+api}/requests/extended/{id}/timeline"), "self_html": RequestLink("{+ui}/requests/{id}"), - "topic": RequestEntityLinks( - ("self", "{+entity_self}", {}), # can't use self=RequestEntityLink... - ("self_html", "{+entity_self_html}", {}), - ), - "created_by": RequestEntityLinks( - ("self", "{+entity_self}", {}), - ("self_html", "{+entity_self_html}", {}), - entity="created_by", - ), - "receiver": RequestEntityLinks( - ("self", "{+entity_self}", {}), - ("self_html", "{+entity_self_html}", {}), - entity="receiver", - ), + "topic": RequestEntityLinks(entity="topic"), + "created_by": RequestEntityLinks(entity="created_by"), + "receiver": RequestEntityLinks(entity="receiver"), } diff --git a/tests/conftest.py b/tests/conftest.py index 840c4ed6..d441bb9e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -46,7 +46,6 @@ from oarepo_workflows.base import Workflow from oarepo_workflows.requests.events import WorkflowEvent from oarepo_workflows.requests.generators import RecipientGeneratorMixin - from thesis.proxies import current_service from thesis.records.api import ThesisDraft @@ -57,9 +56,9 @@ ) from oarepo_requests.receiver import default_workflow_receiver_function from oarepo_requests.services.permissions.generators.conditional import ( - IfRequestedBy, - IfNoNewVersionDraft, IfNoEditDraft, + IfNoNewVersionDraft, + IfRequestedBy, ) from oarepo_requests.services.permissions.workflow_policies import ( RequestBasedWorkflowPermissions, @@ -247,6 +246,7 @@ class TestWorkflowPermissions(RequestBasedWorkflowPermissions): IfInState("draft", [RecordOwners()]), IfInState("publishing", [RecordOwners(), UserGenerator(2)]), IfInState("published", [AnyUser()]), + IfInState("published", [AuthenticatedUser()]), IfInState("deleting", [AnyUser()]), ] @@ -257,6 +257,7 @@ class WithApprovalPermissions(RequestBasedWorkflowPermissions): IfInState("approving", [RecordOwners(), UserGenerator(2)]), IfInState("approved", [RecordOwners(), UserGenerator(2)]), IfInState("publishing", [RecordOwners(), UserGenerator(2)]), + IfInState("published", [AuthenticatedUser()]), IfInState("deleting", [AuthenticatedUser()]), ] @@ -751,7 +752,10 @@ def role(database): def role_ui_serialization(): return { "label": "it-dep", - "links": {"self": "https://127.0.0.1:5000/api/groups/it-dep"}, + "links": { + "avatar": "https://127.0.0.1:5000/api/groups/it-dep/avatar.svg", + "self": "https://127.0.0.1:5000/api/groups/it-dep", + }, "reference": {"group": "it-dep"}, "type": "group", } @@ -851,3 +855,15 @@ def _check_publish_topic_update( assert topic_updated_events[0]["payload"]["new_topic"] == f"thesis.{record_id}" return _check_publish_topic_update + + +@pytest.fixture +def user_links(): + def _user_links(user_id): + return { + "avatar": f"https://127.0.0.1:5000/api/users/{user_id}/avatar.svg", + "records_html": f"https://127.0.0.1:5000/search/records?q=user:{user_id}", + "self": f"https://127.0.0.1:5000/api/users/{user_id}", + } + + return _user_links diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py index 5da6cd68..7d748487 100644 --- a/tests/test_requests/test_new_version.py +++ b/tests/test_requests/test_new_version.py @@ -5,6 +5,7 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. # + from thesis.records.api import ThesisDraft, ThesisRecord from tests.test_requests.utils import link_api2testclient @@ -53,7 +54,9 @@ def test_new_version_autoaccept( # new_version action worked? search = creator_client.get( f'user{urls["BASE_URL"]}', - ).json["hits"]["hits"] + ).json[ + "hits" + ]["hits"] assert len(search) == 2 assert search[0]["id"] != search[1]["id"] assert search[0]["parent"]["id"] == search[1]["parent"]["id"] @@ -143,6 +146,7 @@ def test_redirect_url( link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? + request = creator_client.get( f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', ).json diff --git a/tests/test_requests/test_timeline.py b/tests/test_requests/test_timeline.py index 0b50ea20..c86681a8 100644 --- a/tests/test_requests/test_timeline.py +++ b/tests/test_requests/test_timeline.py @@ -18,6 +18,7 @@ def test_timeline( urls, publish_request_data_function, create_draft_via_resource, + user_links, search_clear, ): creator = users[0] @@ -66,7 +67,7 @@ def test_timeline( "reference": {"user": "1"}, "type": "user", "label": "id: 1", - "links": {"self": "https://127.0.0.1:5000/api/users/1"}, + "links": user_links(1), }, "permissions": {}, "payload": {"content": "test", "format": "html"}, diff --git a/tests/test_requests/test_topic_resolve.py b/tests/test_requests/test_topic_resolve.py index 84cf9728..30075cf3 100644 --- a/tests/test_requests/test_topic_resolve.py +++ b/tests/test_requests/test_topic_resolve.py @@ -112,15 +112,21 @@ def test_ui_resolve_topic( headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) assert resp.status_code == 200 - assert resp.json["topic"] == { - "reference": {"thesis": record1["id"]}, - "type": "thesis", - "label": "blabla", - "links": { + assert ( + resp.json["topic"].items() + >= { + "reference": {"thesis": record1["id"]}, + "type": "thesis", + "label": "blabla", + }.items() + ) + assert ( + resp.json["topic"]["links"].items() + >= { "self": f"https://127.0.0.1:5000/api/thesis/{record1['id']}", "self_html": f"https://127.0.0.1:5000/thesis/{record1['id']}", - }, - } + }.items() + ) assert resp.json["stateful_name"] == "Record deletion requested" assert resp.json["stateful_description"] == ( "Permission to delete record requested. " diff --git a/tests/test_requests/test_ui_serialialization.py b/tests/test_requests/test_ui_serialialization.py index a10b5624..58dd4999 100644 --- a/tests/test_requests/test_ui_serialialization.py +++ b/tests/test_requests/test_ui_serialialization.py @@ -13,7 +13,7 @@ from oarepo_requests.resolvers.ui import FallbackEntityReferenceUIResolver -from .utils import is_valid_subdict, link_api2testclient +from .utils import link_api2testclient def test_user_serialization( @@ -23,6 +23,7 @@ def test_user_serialization( ui_serialization_result, create_draft_via_resource, logged_client, + user_links, search_clear, ): client_fallback_label = logged_client(users[0]) @@ -85,21 +86,21 @@ def test_user_serialization( creator_serialization = { "label": "id: 1", - "links": {"self": "https://127.0.0.1:5000/api/users/1"}, + "links": user_links(1), "reference": {"user": "1"}, "type": "user", } creator_serialization_username = { "label": "beetlesmasher", - "links": {"self": "https://127.0.0.1:5000/api/users/2"}, + "links": user_links(2), "reference": {"user": "2"}, "type": "user", } creator_serialization_fullname = { "label": "Maxipes Fik", - "links": {"self": "https://127.0.0.1:5000/api/users/3"}, + "links": user_links(3), "reference": {"user": "3"}, "type": "user", } @@ -175,9 +176,9 @@ def test_resolver_fallback( expected_result = ui_serialization_result( draft_id, ui_record["expanded"]["requests"][0]["id"] ) - expected_result["created_by"]["label"] = ( - f"id: {creator.id}" # the user resolver uses name or email as label, the fallback doesn't know what to use - ) + expected_result["created_by"][ + "label" + ] = f"id: {creator.id}" # the user resolver uses name or email as label, the fallback doesn't know what to use expected_created_by = {**expected_result["created_by"]} actual_created_by = {**ui_record["expanded"]["requests"][0]["created_by"]} From 4247616f1198f947f9320f99096ac90d49112e6c Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 22 Nov 2024 16:08:40 +0100 Subject: [PATCH 13/44] version bump --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ae81c728..e50afe0e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.3.1 +version = 2.3.2 description = authors = Ronald Krist readme = README.md From 3d45febdf360335be9b42146a80ecedbf8014e3c Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 29 Nov 2024 14:28:27 +0100 Subject: [PATCH 14/44] redirect urls for delete and delete draft --- oarepo_requests/services/oarepo/config.py | 21 ++++++++++++++++++- oarepo_requests/types/delete_draft.py | 6 ++++++ .../types/delete_published_record.py | 6 ++++++ oarepo_requests/types/new_version.py | 4 ++-- tests/test_requests/test_delete.py | 9 +++++++- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index e6e598de..9f180402 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -66,7 +66,7 @@ def expand(self, obj: Request, context: dict) -> dict: if hasattr(obj.type, "extra_request_links"): entity = self._resolve(obj, context) res.update( - obj.type.extra_request_links( + obj.type.extra_entity_links( request=obj, **(context | {"cur_entity": entity, "entity_type": self._entity}), ) @@ -75,6 +75,24 @@ def expand(self, obj: Request, context: dict) -> dict: return res +class RedirectLinks(Link): + + def __init__(self, when: callable = None): + """Constructor.""" + self._when_func = when + + def expand(self, obj: Request, context: dict) -> dict: + """Create the request links.""" + links = {} + available_statuses = {"accept", "decline", "submit"} + for status in available_statuses: + if hasattr(obj.type, f"{status}_redirect_url"): + links[status] = getattr(obj.type, f"{status}_redirect_url")( + obj, context + ) + return links + + class OARepoRequestsServiceConfig(RequestsServiceConfig): """Configuration for the oarepo request service.""" @@ -88,4 +106,5 @@ class OARepoRequestsServiceConfig(RequestsServiceConfig): "topic": RequestEntityLinks(entity="topic"), "created_by": RequestEntityLinks(entity="created_by"), "receiver": RequestEntityLinks(entity="receiver"), + "redirect_urls": RedirectLinks(), } diff --git a/oarepo_requests/types/delete_draft.py b/oarepo_requests/types/delete_draft.py index 680f0380..398aba31 100644 --- a/oarepo_requests/types/delete_draft.py +++ b/oarepo_requests/types/delete_draft.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any +from oarepo_runtime.datastreams.utils import get_record_service_for_record_class from oarepo_runtime.i18n import lazy_gettext as _ from typing_extensions import override @@ -35,6 +36,11 @@ class DeleteDraftRequestType(NonDuplicableOARepoRequestType): dangerous = True + def accept_redirect_url(self, request, context, **kwargs): + topic_cls = request.topic.record_cls + service = get_record_service_for_record_class(topic_cls) + return service.config.links_search["self_html"].expand(None, context) + @classproperty def available_actions(cls) -> dict[str, type[RequestAction]]: """Return available actions for the request type.""" diff --git a/oarepo_requests/types/delete_published_record.py b/oarepo_requests/types/delete_published_record.py index 3d7bce5b..381f7ee5 100644 --- a/oarepo_requests/types/delete_published_record.py +++ b/oarepo_requests/types/delete_published_record.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, Any +from oarepo_runtime.datastreams.utils import get_record_service_for_record_class from oarepo_runtime.i18n import lazy_gettext as _ from typing_extensions import override @@ -37,6 +38,11 @@ class DeletePublishedRecordRequestType(NonDuplicableOARepoRequestType): type_id = "delete_published_record" name = _("Delete record") + def accept_redirect_url(self, request, context, **kwargs): + topic_cls = request.topic.record_cls + service = get_record_service_for_record_class(topic_cls) + return service.config.links_search["self_html"].expand(None, context) + dangerous = True @classproperty diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index 1585ccb0..ae80d162 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -50,9 +50,9 @@ class NewVersionRequestType(NonDuplicableOARepoRequestType): "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def extra_request_links(self, request, **kwargs): + def extra_entity_links(self, request, entity, **kwargs): if request.status == "accepted" and kwargs["entity_type"] == "topic": - return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} + return {"topic_redirect_link": entity["links"]["edit_html"]} else: return {} diff --git a/tests/test_requests/test_delete.py b/tests/test_requests/test_delete.py index dd6efa9e..36f9ca9a 100644 --- a/tests/test_requests/test_delete.py +++ b/tests/test_requests/test_delete.py @@ -39,7 +39,6 @@ def test_delete( resp_request_submit = creator_client.post( link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) - print() record = receiver_client.get(f"{urls['BASE_URL']}{record1['id']}?expand=true") assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { @@ -51,6 +50,10 @@ def test_delete( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) + assert ( + link_api2testclient(delete.json["links"]["redirect_urls"]["accept"]) + == "/thesis/" + ) ThesisRecord.index.refresh() ThesisDraft.index.refresh() @@ -130,4 +133,8 @@ def test_delete_draft( ) # autoapprove suggested here assert request_after.json["status"] == "accepted" assert request_after.json["is_closed"] + assert ( + link_api2testclient(request_after.json["links"]["redirect_urls"]["accept"]) + == "/thesis/" + ) assert read_deleted.status_code == 404 From 74be2d1bdb4155326023e76d1f594499d541bd38 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 2 Dec 2024 12:57:03 +0100 Subject: [PATCH 15/44] event creation endpoint moved --- oarepo_requests/resources/events/config.py | 16 +++++++++++++- oarepo_requests/resources/events/resource.py | 23 ++++++++++++-------- tests/test_requests/test_workflows.py | 8 +++---- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/oarepo_requests/resources/events/config.py b/oarepo_requests/resources/events/config.py index 534242a4..284e65e5 100644 --- a/oarepo_requests/resources/events/config.py +++ b/oarepo_requests/resources/events/config.py @@ -12,6 +12,7 @@ from invenio_requests.resources.events.config import RequestCommentsResourceConfig from oarepo_requests.resources.ui import OARepoRequestEventsUIJSONSerializer +import marshmallow as ma class OARepoRequestsCommentsResourceConfig( @@ -24,8 +25,21 @@ class OARepoRequestsCommentsResourceConfig( routes = { **RequestCommentsResourceConfig.routes, "list-extended": "/extended//comments", - "item-extended": "/extended//comments/", "timeline-extended": "/extended//timeline", + "item-extended": "/extended//comments/", + "event-type": "//timeline/", + "event-type-extended": "/extended//timeline/", + } + + @property + def request_view_args(self): + return {**super().request_view_args, "event_type": ma.fields.Str()} + + @property + def request_item_view_args(self): + return { + **super().request_item_view_args, + "event_type": ma.fields.Str(), } @property diff --git a/oarepo_requests/resources/events/resource.py b/oarepo_requests/resources/events/resource.py index ba6bc2d7..eadea798 100644 --- a/oarepo_requests/resources/events/resource.py +++ b/oarepo_requests/resources/events/resource.py @@ -28,9 +28,16 @@ class OARepoRequestsCommentsResource(RequestCommentsResource, ErrorHandlersMixin): """OARepo extensions to invenio requests comments resource.""" + """ list_view_args_parser = request_parser( from_conf("request_list_view_args"), location="view_args" ) + """ + + item_view_args_parser = request_parser( + from_conf("request_item_view_args"), location="view_args" + ) + data_parser = request_body_parser( parsers=from_conf("request_body_parsers"), default_content_type=from_conf("default_content_type"), @@ -43,10 +50,10 @@ def create_url_rules(self): url_rules = [ route("POST", routes["list-extended"], self.create_extended), - route("POST", routes["timeline"], self.create_event), + route("POST", routes["event-type"], self.create_event), route( "POST", - routes["timeline-extended"], + routes["event-type-extended"], self.create_event, endpoint="create_event_extended", ), @@ -78,21 +85,19 @@ def search_extended(self) -> tuple[dict, int]: """Search for comments.""" return super().search() - # for now we can just use the invenio method without hardcoded event type - # todo - where is the event type specified - # todo - extended endpoint? - @list_view_args_parser + + + @item_view_args_parser @request_extra_args @data_parser @response_handler() def create_event(self): """Create a comment.""" - data = deepcopy(resource_requestctx.data) if resource_requestctx.data else {} - type_ = current_event_type_registry.lookup(data.get("type"), quiet=True) + type_ = current_event_type_registry.lookup(resource_requestctx.view_args["event_type"], quiet=True) item = self.service.create( identity=g.identity, request_id=resource_requestctx.view_args["request_id"], - data=data, + data=resource_requestctx.data, event_type=type_, expand=resource_requestctx.args.get("expand", False), ) diff --git a/tests/test_requests/test_workflows.py b/tests/test_requests/test_workflows.py index 7f66c948..1b6956d5 100644 --- a/tests/test_requests/test_workflows.py +++ b/tests/test_requests/test_workflows.py @@ -461,10 +461,10 @@ def test_workflow_events_resource( request_id = read_from_record.json["expanded"]["requests"][0]["id"] json = {**events_resource_data, "type": TestEventType.type_id} create_event_u1 = user1_client.post( - f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline/{TestEventType.type_id}", json=json ) create_event_u2 = user2_client.post( - f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline/{TestEventType.type_id}", json=json ) assert create_event_u1.status_code == 403 @@ -493,10 +493,10 @@ def test_workflow_events_resource( request_id = publish_request["id"] create_event_u1 = user1_client.post( - f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline/{TestEventType.type_id}", json=json ) create_event_u2 = user2_client.post( - f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline", json=json + f"{urls['BASE_URL_REQUESTS']}{request_id}/timeline/{TestEventType.type_id}", json=json ) assert create_event_u1.status_code == 201 assert create_event_u2.status_code == 403 From e1bfe06d7501eab12dbd420dd27ecdfc96345609 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 2 Dec 2024 15:20:21 +0100 Subject: [PATCH 16/44] deleted unnecessary code --- oarepo_requests/resources/events/config.py | 4 ---- oarepo_requests/resources/events/resource.py | 7 ------- 2 files changed, 11 deletions(-) diff --git a/oarepo_requests/resources/events/config.py b/oarepo_requests/resources/events/config.py index 284e65e5..31b7e658 100644 --- a/oarepo_requests/resources/events/config.py +++ b/oarepo_requests/resources/events/config.py @@ -31,10 +31,6 @@ class OARepoRequestsCommentsResourceConfig( "event-type-extended": "/extended//timeline/", } - @property - def request_view_args(self): - return {**super().request_view_args, "event_type": ma.fields.Str()} - @property def request_item_view_args(self): return { diff --git a/oarepo_requests/resources/events/resource.py b/oarepo_requests/resources/events/resource.py index eadea798..31a33211 100644 --- a/oarepo_requests/resources/events/resource.py +++ b/oarepo_requests/resources/events/resource.py @@ -28,12 +28,6 @@ class OARepoRequestsCommentsResource(RequestCommentsResource, ErrorHandlersMixin): """OARepo extensions to invenio requests comments resource.""" - """ - list_view_args_parser = request_parser( - from_conf("request_list_view_args"), location="view_args" - ) - """ - item_view_args_parser = request_parser( from_conf("request_item_view_args"), location="view_args" ) @@ -86,7 +80,6 @@ def search_extended(self) -> tuple[dict, int]: return super().search() - @item_view_args_parser @request_extra_args @data_parser From b0bd0d70714f8d1f6d6d4b1c4d5ab56f6831dd49 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Tue, 3 Dec 2024 14:04:10 +0100 Subject: [PATCH 17/44] refactors and minor edits --- oarepo_requests/services/oarepo/config.py | 5 ++-- .../services/permissions/workflow_policies.py | 3 +-- oarepo_requests/types/delete_draft.py | 2 +- .../types/delete_published_record.py | 2 +- oarepo_requests/types/edit_record.py | 6 ++--- oarepo_requests/types/new_version.py | 4 +-- tests/conftest.py | 8 +++--- ..._allowed_request_types_link_and_service.py | 24 ++++++++--------- tests/test_requests/test_cascade_events.py | 6 ++--- tests/test_requests/test_create_conditions.py | 10 +++---- tests/test_requests/test_create_inmodel.py | 14 +++++----- tests/test_requests/test_delete.py | 26 +++++++++---------- tests/test_requests/test_edit.py | 8 +++--- tests/test_requests/test_expand.py | 8 +++--- tests/test_requests/test_extended.py | 20 +++++++------- tests/test_requests/test_index_refresh.py | 4 +-- tests/test_requests/test_new_version.py | 10 +++---- .../test_requests/test_param_interpreters.py | 4 +-- tests/test_requests/test_publish.py | 14 +++++----- tests/test_requests/test_record_requests.py | 22 ++++++++-------- tests/test_requests/test_timeline.py | 12 ++++----- tests/test_requests/test_topic_resolve.py | 16 ++++++------ tests/test_requests/test_topic_update.py | 10 +++---- .../test_requests/test_ui_serialialization.py | 6 ++--- tests/test_requests/test_workflows.py | 24 ++++++++--------- tests/test_requests/utils.py | 6 ++--- 26 files changed, 137 insertions(+), 137 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 9f180402..79aa1ddf 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -63,12 +63,13 @@ def expand(self, obj: Request, context: dict) -> dict: res = {} res.update(self._resolve(obj, context)["links"]) - if hasattr(obj.type, "extra_request_links"): + if hasattr(obj.type, "extra_entity_links"): entity = self._resolve(obj, context) res.update( obj.type.extra_entity_links( request=obj, - **(context | {"cur_entity": entity, "entity_type": self._entity}), + entity=entity, + entity_type=self._entity ) ) diff --git a/oarepo_requests/services/permissions/workflow_policies.py b/oarepo_requests/services/permissions/workflow_policies.py index 9dc9da78..2b447762 100644 --- a/oarepo_requests/services/permissions/workflow_policies.py +++ b/oarepo_requests/services/permissions/workflow_policies.py @@ -7,8 +7,7 @@ # """Permissions for requests based on workflows.""" -from oarepo_workflows.requests.permissions import ( - # this is for backward compatibility ... +from oarepo_workflows.requests.permissions import ( # this is for backward compatibility ... CreatorsFromWorkflowRequestsPermissionPolicy, ) from oarepo_workflows.services.permissions import DefaultWorkflowPermissions diff --git a/oarepo_requests/types/delete_draft.py b/oarepo_requests/types/delete_draft.py index 398aba31..5f033ba5 100644 --- a/oarepo_requests/types/delete_draft.py +++ b/oarepo_requests/types/delete_draft.py @@ -36,7 +36,7 @@ class DeleteDraftRequestType(NonDuplicableOARepoRequestType): dangerous = True - def accept_redirect_url(self, request, context, **kwargs): + def accept_redirect_url(self, request: Request, context: dict, **kwargs): topic_cls = request.topic.record_cls service = get_record_service_for_record_class(topic_cls) return service.config.links_search["self_html"].expand(None, context) diff --git a/oarepo_requests/types/delete_published_record.py b/oarepo_requests/types/delete_published_record.py index 381f7ee5..8f8723e5 100644 --- a/oarepo_requests/types/delete_published_record.py +++ b/oarepo_requests/types/delete_published_record.py @@ -38,7 +38,7 @@ class DeletePublishedRecordRequestType(NonDuplicableOARepoRequestType): type_id = "delete_published_record" name = _("Delete record") - def accept_redirect_url(self, request, context, **kwargs): + def accept_redirect_url(self, request: Request, context: dict, **kwargs): topic_cls = request.topic.record_cls service = get_record_service_for_record_class(topic_cls) return service.config.links_search["self_html"].expand(None, context) diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index a6c3f763..07738dcf 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -54,9 +54,9 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } - def extra_request_links(self, request, **kwargs): - if request.status == "accepted" and kwargs["entity_type"] == "topic": - return {"topic_redirect_link": kwargs["cur_entity"]["links"]["edit_html"]} + def extra_entity_links(self, request, entity, entity_type: str, **kwargs): + if request.status == "accepted" and entity_type == "topic": + return {"topic_redirect_link": entity["links"]["edit_html"]} else: return {} diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index ae80d162..73f90e53 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -50,8 +50,8 @@ class NewVersionRequestType(NonDuplicableOARepoRequestType): "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def extra_entity_links(self, request, entity, **kwargs): - if request.status == "accepted" and kwargs["entity_type"] == "topic": + def extra_entity_links(self, request, entity, entity_type: str, **kwargs): + if request.status == "accepted" and entity_type == "topic": return {"topic_redirect_link": entity["links"]["edit_html"]} else: return {} diff --git a/tests/conftest.py b/tests/conftest.py index d441bb9e..2a9ad55e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -65,7 +65,7 @@ ) from oarepo_requests.types import ModelRefTypes, NonDuplicableOARepoRequestType from oarepo_requests.types.events.topic_update import TopicUpdateEventType -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient can_comment_only_receiver = [ Receiver(), @@ -802,9 +802,9 @@ def _create_request_from_link(request_types_json, request_type): def create_request_by_link(get_request_link): def _create_request(client, record, request_type): applicable_requests = client.get( - link_api2testclient(record.json["links"]["applicable-requests"]) + link2testclient(record.json["links"]["applicable-requests"]) ).json["hits"]["hits"] - create_link = link_api2testclient( + create_link = link2testclient( get_request_link(applicable_requests, request_type) ) create_response = client.post(create_link) @@ -818,7 +818,7 @@ def submit_request_by_link(create_request_by_link): def _submit_request(client, record, request_type): create_response = create_request_by_link(client, record, request_type) submit_response = client.post( - link_api2testclient(create_response.json["links"]["actions"]["submit"]) + link2testclient(create_response.json["links"]["actions"]["submit"]) ) return submit_response diff --git a/tests/test_requests/test_allowed_request_types_link_and_service.py b/tests/test_requests/test_allowed_request_types_link_and_service.py index d762c12f..28249d1b 100644 --- a/tests/test_requests/test_allowed_request_types_link_and_service.py +++ b/tests/test_requests/test_allowed_request_types_link_and_service.py @@ -9,7 +9,7 @@ from thesis.ext import ThesisExt from thesis.records.api import ThesisDraft, ThesisRecord -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def test_allowed_request_types_on_draft_service( @@ -79,7 +79,7 @@ def test_allowed_request_types_on_draft_resource( == f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/applicable' ) allowed_request_types = creator_client.get( - link_api2testclient(applicable_requests_link) + link2testclient(applicable_requests_link) ) assert sorted( allowed_request_types.json["hits"]["hits"], key=lambda x: x["type_id"] @@ -106,34 +106,32 @@ def test_allowed_request_types_on_draft_resource( def publish_record( creator_client, urls, publish_request_data_function, draft1, receiver_client ): + id_ = draft1.json["id"] resp_request_create = creator_client.post( urls["BASE_URL_REQUESTS"], json=publish_request_data_function(draft1.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) ThesisRecord.index.refresh() ThesisDraft.index.refresh() record = receiver_client.get( - f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" + f"{urls['BASE_URL']}{id_}/draft?expand=true" ) + assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { "accept", "decline", } publish = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) - - ThesisRecord.index.refresh() - ThesisDraft.index.refresh() - lst = creator_client.get(urls["BASE_URL"]) - return lst.json["hits"]["hits"][0] + return creator_client.get(f"{urls['BASE_URL']}{id_}?expand=true").json def test_allowed_request_types_on_published_resource( @@ -162,7 +160,7 @@ def test_allowed_request_types_on_published_resource( == f'https://127.0.0.1:5000/api/thesis/{published1["id"]}/requests/applicable' ) allowed_request_types = creator_client.get( - link_api2testclient(applicable_requests_link) + link2testclient(applicable_requests_link) ) assert allowed_request_types.status_code == 200 assert sorted( @@ -227,12 +225,12 @@ def test_ui_serialization( applicable_requests_link_published = published1["links"]["applicable-requests"] allowed_request_types_draft = creator_client.get( - link_api2testclient(applicable_requests_link_draft), + link2testclient(applicable_requests_link_draft), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) allowed_request_types_published = creator_client.get( - link_api2testclient(applicable_requests_link_published), + link2testclient(applicable_requests_link_published), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) diff --git a/tests/test_requests/test_cascade_events.py b/tests/test_requests/test_cascade_events.py index 54931ccc..4fdb4b50 100644 --- a/tests/test_requests/test_cascade_events.py +++ b/tests/test_requests/test_cascade_events.py @@ -9,7 +9,7 @@ from oarepo_requests.types.events import TopicDeleteEventType -from .utils import link_api2testclient +from .utils import link2testclient def test_cascade_update( @@ -45,13 +45,13 @@ def test_cascade_update( json=publish_request_data_function(draft2.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(publish_request_create.json["links"]["actions"]["submit"]), + link2testclient(publish_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) publish = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) diff --git a/tests/test_requests/test_create_conditions.py b/tests/test_requests/test_create_conditions.py index 71932f47..ca44f476 100644 --- a/tests/test_requests/test_create_conditions.py +++ b/tests/test_requests/test_create_conditions.py @@ -9,7 +9,7 @@ from oarepo_requests.errors import OpenRequestAlreadyExists -from .utils import link_api2testclient +from .utils import link2testclient def test_can_create( @@ -41,7 +41,7 @@ def test_can_create( ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) with pytest.raises(OpenRequestAlreadyExists): @@ -59,7 +59,7 @@ def test_can_create( # try declining the request for draft2, we should be able to create again then resp_request_submit = creator_client.post( - link_api2testclient( + link2testclient( create_for_request_draft2.json["links"]["actions"]["submit"] ), ) @@ -73,7 +73,7 @@ def test_can_create( f"{urls['BASE_URL']}{draft2.json['id']}/draft?expand=true" ) decline = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] ), ) @@ -115,7 +115,7 @@ def test_can_possibly_create( ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) def find_request_type(requests, type): diff --git a/tests/test_requests/test_create_inmodel.py b/tests/test_requests/test_create_inmodel.py index eed0c307..12ce0aed 100644 --- a/tests/test_requests/test_create_inmodel.py +++ b/tests/test_requests/test_create_inmodel.py @@ -7,7 +7,7 @@ # from thesis.records.api import ThesisRecord -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def pick_request_type(types_list, queried_type): @@ -34,7 +34,7 @@ def test_record( record1 = record_factory(creator.identity) record1 = creator_client.get(f"{urls['BASE_URL']}{record1['id']}?expand=true") - link = link_api2testclient( + link = link2testclient( pick_request_type( record1.json["expanded"]["request_types"], "delete_published_record" )["links"]["actions"]["create"] @@ -43,12 +43,12 @@ def test_record( resp_request_create = creator_client.post(link) assert resp_request_create.status_code == 201 resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get(f"{urls['BASE_URL']}{record1.json['id']}?expand=true") delete = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ) ) @@ -72,7 +72,7 @@ def test_draft( receiver_client = logged_client(receiver) draft1 = create_draft_via_resource(creator_client) - link = link_api2testclient( + link = link2testclient( pick_request_type(draft1.json["expanded"]["request_types"], "publish_draft")[ "links" ]["actions"]["create"] @@ -83,13 +83,13 @@ def test_draft( ) assert resp_request_create.status_code == 201 resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) delete = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ) ) diff --git a/tests/test_requests/test_delete.py b/tests/test_requests/test_delete.py index 36f9ca9a..5dceff87 100644 --- a/tests/test_requests/test_delete.py +++ b/tests/test_requests/test_delete.py @@ -7,7 +7,7 @@ # from thesis.records.api import ThesisDraft, ThesisRecord -from .utils import link_api2testclient +from .utils import link2testclient def test_delete( @@ -37,7 +37,7 @@ def test_delete( json=delete_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get(f"{urls['BASE_URL']}{record1['id']}?expand=true") @@ -46,13 +46,13 @@ def test_delete( "decline", } delete = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) assert ( - link_api2testclient(delete.json["links"]["redirect_urls"]["accept"]) - == "/thesis/" + link2testclient(delete.json["links"]["redirect_urls"]["accept"], ui=True) + == "/thesis/" ) ThesisRecord.index.refresh() @@ -65,11 +65,11 @@ def test_delete( json=delete_record_data_function(record2["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get(f"{urls['BASE_URL']}{record2['id']}?expand=true") decline = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] ) ) @@ -83,14 +83,14 @@ def test_delete( json=delete_record_data_function(record3["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = creator_client.get(f"{urls['BASE_URL']}{record3['id']}?expand=true") assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { "cancel" } cancel = creator_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["cancel"] ), ) @@ -119,12 +119,12 @@ def test_delete_draft( assert read.status_code == 200 resp_request_create = creator_client.post( - link_api2testclient( + link2testclient( get_request_link(read.json["expanded"]["request_types"], "delete_draft") ) ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) read_deleted = creator_client.get(f"{urls['BASE_URL']}{draft_id}/draft?expand=true") @@ -134,7 +134,7 @@ def test_delete_draft( assert request_after.json["status"] == "accepted" assert request_after.json["is_closed"] assert ( - link_api2testclient(request_after.json["links"]["redirect_urls"]["accept"]) - == "/thesis/" + link2testclient(request_after.json["links"]["redirect_urls"]["accept"], ui=True) + == "/thesis/" ) assert read_deleted.status_code == 404 diff --git a/tests/test_requests/test_edit.py b/tests/test_requests/test_edit.py index 31e1baa3..ae5f1ed8 100644 --- a/tests/test_requests/test_edit.py +++ b/tests/test_requests/test_edit.py @@ -7,7 +7,7 @@ # from thesis.records.api import ThesisDraft, ThesisRecord -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def test_edit_autoaccept( @@ -36,7 +36,7 @@ def test_edit_autoaccept( json=edit_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? request = creator_client.get( @@ -55,7 +55,9 @@ def test_edit_autoaccept( # edit action worked? search = creator_client.get( f'user{urls["BASE_URL"]}', - ).json["hits"]["hits"] + ).json[ + "hits" + ]["hits"] assert len(search) == 1 assert search[0]["links"]["self"].endswith("/draft") assert search[0]["id"] == id_ diff --git a/tests/test_requests/test_expand.py b/tests/test_requests/test_expand.py index c50e9345..000254fa 100644 --- a/tests/test_requests/test_expand.py +++ b/tests/test_requests/test_expand.py @@ -6,7 +6,7 @@ # details. # from tests.test_requests.test_create_inmodel import pick_request_type -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def test_requests_field( @@ -24,7 +24,7 @@ def test_requests_field( receiver_client = logged_client(receiver) draft1 = create_draft_via_resource(creator_client) - link = link_api2testclient( + link = link2testclient( pick_request_type(draft1.json["expanded"]["request_types"], "publish_draft")[ "links" ]["actions"]["create"] @@ -33,7 +33,7 @@ def test_requests_field( resp_request_create = creator_client.post(link) assert resp_request_create.status_code == 201 resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get(f"{urls['BASE_URL']}{draft1.json['id']}/draft") expanded_record = receiver_client.get( @@ -63,7 +63,7 @@ def test_autoaccept_receiver( json=edit_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) request = creator_client.get( f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}?expand=true' diff --git a/tests/test_requests/test_extended.py b/tests/test_requests/test_extended.py index 28c51163..d1701d17 100644 --- a/tests/test_requests/test_extended.py +++ b/tests/test_requests/test_extended.py @@ -8,7 +8,7 @@ from invenio_requests.records.api import RequestEvent from thesis.records.api import ThesisDraft -from .utils import is_valid_subdict, link_api2testclient +from .utils import is_valid_subdict, link2testclient def test_listing( @@ -62,7 +62,7 @@ def test_read_extended( json=publish_request_data_function(draft_id), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) old_call = creator_client.get( @@ -106,15 +106,15 @@ def test_update_self_link( json=publish_request_data_function(draft1.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]) + link2testclient(resp_request_create.json["links"]["actions"]["submit"]) ) read_before = creator_client.get( - link_api2testclient(resp_request_submit.json["links"]["self"]), + link2testclient(resp_request_submit.json["links"]["self"]), ) read_from_record = creator_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true", ) - link_to_extended = link_api2testclient( + link_to_extended = link2testclient( read_from_record.json["expanded"]["requests"][0]["links"]["self"] ) @@ -125,7 +125,7 @@ def test_update_self_link( ) assert update_extended.status_code == 200 read_after = creator_client.get( - link_api2testclient(resp_request_submit.json["links"]["self"]), + link2testclient(resp_request_submit.json["links"]["self"]), ) assert read_before.json["title"] == "" assert read_after.json["title"] == "lalala" @@ -151,20 +151,20 @@ def test_events_resource( json=publish_request_data_function(draft1.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]) + link2testclient(resp_request_create.json["links"]["actions"]["submit"]) ) read_before = creator_client.get( - link_api2testclient(resp_request_submit.json["links"]["self"]), + link2testclient(resp_request_submit.json["links"]["self"]), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) read_from_record = creator_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true", ) - comments_link = link_api2testclient( + comments_link = link2testclient( read_from_record.json["expanded"]["requests"][0]["links"]["comments"] ) - timeline_link = link_api2testclient( + timeline_link = link2testclient( read_from_record.json["expanded"]["requests"][0]["links"]["timeline"] ) diff --git a/tests/test_requests/test_index_refresh.py b/tests/test_requests/test_index_refresh.py index a00af7b9..f0275302 100644 --- a/tests/test_requests/test_index_refresh.py +++ b/tests/test_requests/test_index_refresh.py @@ -5,7 +5,7 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. # -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def test_search( @@ -30,7 +30,7 @@ def test_search( assert len(requests_search["hits"]["hits"]) == 1 - link = link_api2testclient(requests_search["hits"]["hits"][0]["links"]["self"]) + link = link2testclient(requests_search["hits"]["hits"][0]["links"]["self"]) extended_link = link.replace("/requests/", "/requests/extended/") update = creator_client.put( diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py index 7d748487..dd167754 100644 --- a/tests/test_requests/test_new_version.py +++ b/tests/test_requests/test_new_version.py @@ -8,7 +8,7 @@ from thesis.records.api import ThesisDraft, ThesisRecord -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def test_new_version_autoaccept( @@ -35,7 +35,7 @@ def test_new_version_autoaccept( json=new_version_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? request = creator_client.get( @@ -90,10 +90,10 @@ def test_new_version_files( ) resp_request_submit1 = creator_client.post( - link_api2testclient(resp_request_create1.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create1.json["links"]["actions"]["submit"]), ) resp_request_submit2 = creator_client.post( - link_api2testclient(resp_request_create2.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create2.json["links"]["actions"]["submit"]), ) ThesisDraft.index.refresh() @@ -143,7 +143,7 @@ def test_redirect_url( json=new_version_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? diff --git a/tests/test_requests/test_param_interpreters.py b/tests/test_requests/test_param_interpreters.py index 7add533c..94cfe4c6 100644 --- a/tests/test_requests/test_param_interpreters.py +++ b/tests/test_requests/test_param_interpreters.py @@ -5,7 +5,7 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. # -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def _init(users, logged_client, create_draft_via_resource, submit_request, urls): @@ -107,7 +107,7 @@ def test_open_param_interpreter( read = user2_client.get(f'{urls["BASE_URL"]}{draft1.json["id"]}/draft?expand=true') publish = user2_client.post( - link_api2testclient( + link2testclient( read.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ) ) diff --git a/tests/test_requests/test_publish.py b/tests/test_requests/test_publish.py index 0c4c11a4..60f98716 100644 --- a/tests/test_requests/test_publish.py +++ b/tests/test_requests/test_publish.py @@ -9,7 +9,7 @@ from thesis.records.api import ThesisDraft, ThesisRecord -from .utils import link_api2testclient +from .utils import link2testclient def test_publish_service(users, record_service, default_workflow_json, search_clear): @@ -81,7 +81,7 @@ def test_publish( ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) ThesisRecord.index.refresh() ThesisDraft.index.refresh() @@ -94,7 +94,7 @@ def test_publish( "decline", } publish = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) @@ -120,13 +120,13 @@ def test_publish( json=publish_request_data_function(draft2.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get( f"{urls['BASE_URL']}{draft2.json['id']}/draft?expand=true" ) decline = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] ), ) @@ -141,7 +141,7 @@ def test_publish( json=publish_request_data_function(draft3.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = creator_client.get( f"{urls['BASE_URL']}{draft3.json['id']}/draft?expand=true" @@ -150,7 +150,7 @@ def test_publish( "cancel" } cancel = creator_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["cancel"] ), ) diff --git a/tests/test_requests/test_record_requests.py b/tests/test_requests/test_record_requests.py index e21e078d..65e5dad0 100644 --- a/tests/test_requests/test_record_requests.py +++ b/tests/test_requests/test_record_requests.py @@ -7,7 +7,7 @@ # from thesis.records.api import ThesisDraft, ThesisRecord -from .utils import link_api2testclient +from .utils import link2testclient def test_read_requests_on_draft( @@ -34,13 +34,13 @@ def test_read_requests_on_draft( json=publish_request_data_function(draft1.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(r1.json["links"]["actions"]["submit"]), + link2testclient(r1.json["links"]["actions"]["submit"]), ) record = receiver_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) decline = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] ), ) @@ -54,9 +54,9 @@ def test_read_requests_on_draft( json=publish_request_data_function(draft2.json["id"]), ) - creator_client.get(link_api2testclient(r1.json["links"]["actions"]["submit"])) - creator_client.get(link_api2testclient(r2.json["links"]["actions"]["submit"])) - creator_client.get(link_api2testclient(r3.json["links"]["actions"]["submit"])) + creator_client.get(link2testclient(r1.json["links"]["actions"]["submit"])) + creator_client.get(link2testclient(r2.json["links"]["actions"]["submit"])) + creator_client.get(link2testclient(r3.json["links"]["actions"]["submit"])) resp1 = creator_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft/requests" @@ -96,11 +96,11 @@ def test_read_requests_on_record( json=delete_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(r1.json["links"]["actions"]["submit"]), + link2testclient(r1.json["links"]["actions"]["submit"]), ) record = receiver_client.get(f"{urls['BASE_URL']}{record1['id']}?expand=true") decline = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] ), ) @@ -114,9 +114,9 @@ def test_read_requests_on_record( json=delete_record_data_function(record2["id"]), ) - creator_client.post(link_api2testclient(r1.json["links"]["actions"]["submit"])) - creator_client.post(link_api2testclient(r2.json["links"]["actions"]["submit"])) - creator_client.post(link_api2testclient(r3.json["links"]["actions"]["submit"])) + creator_client.post(link2testclient(r1.json["links"]["actions"]["submit"])) + creator_client.post(link2testclient(r2.json["links"]["actions"]["submit"])) + creator_client.post(link2testclient(r3.json["links"]["actions"]["submit"])) resp1 = creator_client.get(f"{urls['BASE_URL']}{record1['id']}/requests").json[ "hits" diff --git a/tests/test_requests/test_timeline.py b/tests/test_requests/test_timeline.py index c86681a8..6443ae1f 100644 --- a/tests/test_requests/test_timeline.py +++ b/tests/test_requests/test_timeline.py @@ -8,7 +8,7 @@ from invenio_requests.records.api import RequestEvent from tests.test_requests.test_create_inmodel import pick_request_type -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient def test_timeline( @@ -25,7 +25,7 @@ def test_timeline( creator_client = logged_client(creator) draft1 = create_draft_via_resource(creator_client) - link = link_api2testclient( + link = link2testclient( pick_request_type(draft1.json["expanded"]["request_types"], "publish_draft")[ "links" ]["actions"]["create"] @@ -35,26 +35,26 @@ def test_timeline( assert publish_request_resp.status_code == 201 publish_request_submit_resp = creator_client.post( - link_api2testclient(publish_request_resp.json["links"]["actions"]["submit"]), + link2testclient(publish_request_resp.json["links"]["actions"]["submit"]), ) assert publish_request_submit_resp.status_code == 200 comment_resp = creator_client.post( - link_api2testclient(publish_request_resp.json["links"]["comments"]), + link2testclient(publish_request_resp.json["links"]["comments"]), json={"payload": {"content": "test"}}, ) assert comment_resp.status_code == 201 RequestEvent.index.refresh() timeline_resp = creator_client.get( - link_api2testclient(publish_request_resp.json["links"]["timeline"]), + link2testclient(publish_request_resp.json["links"]["timeline"]), ) assert timeline_resp.status_code == 200 assert len(timeline_resp.json["hits"]["hits"]) == 1 # vnd serialization timeline_resp = creator_client.get( - link_api2testclient(publish_request_resp.json["links"]["timeline"]), + link2testclient(publish_request_resp.json["links"]["timeline"]), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) assert timeline_resp.status_code == 200 diff --git a/tests/test_requests/test_topic_resolve.py b/tests/test_requests/test_topic_resolve.py index 30075cf3..e585374c 100644 --- a/tests/test_requests/test_topic_resolve.py +++ b/tests/test_requests/test_topic_resolve.py @@ -10,7 +10,7 @@ from invenio_access.permissions import system_identity from thesis.records.api import ThesisDraft, ThesisRecord -from .utils import link_api2testclient +from .utils import link2testclient def test_resolve_topic( @@ -38,12 +38,12 @@ def test_resolve_topic( json=delete_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) assert resp_request_submit.json["status"] == "submitted" resp = creator_client.get( - link_api2testclient(resp_request_create.json["links"]["self"]), + link2testclient(resp_request_create.json["links"]["self"]), query_string={"expand": "true"}, ) assert resp.status_code == 200 @@ -60,13 +60,13 @@ def test_resolve_topic( ThesisRecord.index.refresh() resp = creator_client.get( - link_api2testclient(resp_request_create.json["links"]["self"]), + link2testclient(resp_request_create.json["links"]["self"]), ) assert resp.status_code == 200 assert resp.json["topic"] == {"thesis": record1["id"]} resp = creator_client.get( - link_api2testclient(resp_request_create.json["links"]["self"]), + link2testclient(resp_request_create.json["links"]["self"]), query_string={"expand": "true"}, ) assert resp.status_code == 200 @@ -103,12 +103,12 @@ def test_ui_resolve_topic( json=delete_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) assert resp_request_submit.json["status"] == "submitted" resp = creator_client.get( - link_api2testclient(resp_request_create.json["links"]["self"]), + link2testclient(resp_request_create.json["links"]["self"]), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) assert resp.status_code == 200 @@ -137,7 +137,7 @@ def test_ui_resolve_topic( ThesisRecord.index.refresh() resp = creator_client.get( - link_api2testclient(resp_request_create.json["links"]["self"]), + link2testclient(resp_request_create.json["links"]["self"]), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) assert resp.status_code == 200 diff --git a/tests/test_requests/test_topic_update.py b/tests/test_requests/test_topic_update.py index 102b48fe..88972707 100644 --- a/tests/test_requests/test_topic_update.py +++ b/tests/test_requests/test_topic_update.py @@ -7,7 +7,7 @@ # from thesis.records.api import ThesisDraft -from .utils import link_api2testclient +from .utils import link2testclient def test_publish( @@ -32,13 +32,13 @@ def test_publish( json=publish_request_data_function(draft1.json["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = receiver_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) publish = receiver_client.post( - link_api2testclient( + link2testclient( record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) @@ -65,7 +65,7 @@ def test_edit( json=edit_record_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? request = creator_client.get( @@ -95,7 +95,7 @@ def test_new_version( json=new_version_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? request = creator_client.get( diff --git a/tests/test_requests/test_ui_serialialization.py b/tests/test_requests/test_ui_serialialization.py index 58dd4999..008a7a0e 100644 --- a/tests/test_requests/test_ui_serialialization.py +++ b/tests/test_requests/test_ui_serialialization.py @@ -13,7 +13,7 @@ from oarepo_requests.resolvers.ui import FallbackEntityReferenceUIResolver -from .utils import link_api2testclient +from .utils import link2testclient def test_user_serialization( @@ -62,7 +62,7 @@ def test_user_serialization( ) resp_request_submit = client_fallback_label.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) pprint(resp_request_submit.json) @@ -160,7 +160,7 @@ def test_resolver_fallback( ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), headers={"Accept": "application/vnd.inveniordm.v1+json"}, ) assert resp_request_submit.json["stateful_name"] == "Submitted for review" diff --git a/tests/test_requests/test_workflows.py b/tests/test_requests/test_workflows.py index 60c283e7..db163c7a 100644 --- a/tests/test_requests/test_workflows.py +++ b/tests/test_requests/test_workflows.py @@ -17,7 +17,7 @@ CreatorsFromWorkflowRequestsPermissionPolicy, ) from tests.conftest import TestEventType -from tests.test_requests.utils import link_api2testclient +from tests.test_requests.utils import link2testclient @unit_of_work() @@ -108,7 +108,7 @@ def test_publish_with_workflows( assert resp_request_create.status_code == 201 resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) assert resp_request_submit.status_code == 200 @@ -131,7 +131,7 @@ def test_publish_with_workflows( ].keys() accept = receiver_client.post( - link_api2testclient( + link2testclient( record_receiver["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) @@ -170,7 +170,7 @@ def test_autorequest( ) assert resp_request_create.status_code == 201 resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) approving_record = record_service.read_draft(creator.identity, record_id)._record assert resp_request_submit.status_code == 200 @@ -179,7 +179,7 @@ def test_autorequest( f'{urls["BASE_URL"]}{record_id}/draft?expand=true' ).json accept = receiver_client.post( - link_api2testclient( + link2testclient( record_receiver["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) @@ -220,7 +220,7 @@ def test_if_no_new_version_draft( json=new_version_data_function(id_), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) request = creator_client.get( f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', @@ -243,7 +243,7 @@ def test_if_no_new_version_draft( json=edit_record_data_function(id2_), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) request = creator_client.get( f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', @@ -288,7 +288,7 @@ def test_if_no_edit_draft( json=edit_record_data_function(id_), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) request = creator_client.get( f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', @@ -311,7 +311,7 @@ def test_if_no_edit_draft( json=new_version_data_function(id2_), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) request = creator_client.get( f'{urls["BASE_URL_REQUESTS"]}{resp_request_create.json["id"]}', @@ -358,7 +358,7 @@ def test_workflow_events( json=approve_request_data, ) resp_request_submit = user1_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) read_from_record = user1_client.get( @@ -385,7 +385,7 @@ def test_workflow_events( f'{urls["BASE_URL"]}{record_id}/draft?expand=true' ).json accept = user2_client.post( - link_api2testclient( + link2testclient( record_receiver["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) @@ -451,7 +451,7 @@ def test_delete_log( ) accept = receiver_client.post( - link_api2testclient(request_receiver.json["links"]["actions"]["accept"]) + link2testclient(request_receiver.json["links"]["actions"]["accept"]) ) post_delete_record_read = receiver_client.get(f"{urls['BASE_URL']}{record_id}") post_delete_request_read_json = receiver_client.get( diff --git a/tests/test_requests/utils.py b/tests/test_requests/utils.py index 1ca4e4c8..500c1911 100644 --- a/tests/test_requests/utils.py +++ b/tests/test_requests/utils.py @@ -8,9 +8,9 @@ from collections import defaultdict -def link_api2testclient(api_link): - base_string = "https://127.0.0.1:5000/api/" - return api_link[len(base_string) - 1 :] +def link2testclient(link, ui=False): + base_string = "https://127.0.0.1:5000/api/" if not ui else "https://127.0.0.1:5000/" + return link[len(base_string) - 1 :] # from chatgpt From 42235b6d617d81e8fd501100e8eac1517aa54b22 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Tue, 3 Dec 2024 16:09:35 +0100 Subject: [PATCH 18/44] comment --- oarepo_requests/resources/events/resource.py | 1 + 1 file changed, 1 insertion(+) diff --git a/oarepo_requests/resources/events/resource.py b/oarepo_requests/resources/events/resource.py index 31a33211..c6e777eb 100644 --- a/oarepo_requests/resources/events/resource.py +++ b/oarepo_requests/resources/events/resource.py @@ -80,6 +80,7 @@ def search_extended(self) -> tuple[dict, int]: return super().search() + # list args parser in invenio parses request_id input through UUID instead of Str; does this have any relevance for us? @item_view_args_parser @request_extra_args @data_parser From 6ad132a1b50ee136dcd35eded35c92640178753e Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Tue, 3 Dec 2024 21:27:10 +0100 Subject: [PATCH 19/44] Better filtering of requests --- oarepo_requests/actions/components.py | 6 ++--- oarepo_requests/actions/publish_draft.py | 12 ++++++---- oarepo_requests/config.py | 2 ++ oarepo_requests/invenio_patches.py | 22 ++++++++----------- oarepo_requests/resolvers/ui.py | 9 +++++--- oarepo_requests/resources/draft/config.py | 2 ++ .../resources/draft/types/config.py | 2 ++ oarepo_requests/resources/events/config.py | 2 ++ oarepo_requests/resources/events/resource.py | 2 ++ oarepo_requests/resources/oarepo/config.py | 2 ++ oarepo_requests/resources/record/config.py | 2 ++ .../permissions/generators/__init__.py | 2 ++ .../services/permissions/identity.py | 2 ++ .../services/permissions/workflow_policies.py | 2 ++ oarepo_requests/services/results.py | 4 +++- oarepo_requests/services/schema.py | 2 ++ .../translations/_only_for_translations.py | 2 ++ oarepo_requests/types/__init__.py | 2 ++ oarepo_requests/types/events/__init__.py | 2 ++ oarepo_requests/types/events/topic_delete.py | 2 ++ oarepo_requests/types/events/topic_update.py | 2 ++ oarepo_requests/types/events/validation.py | 2 ++ oarepo_requests/typing.py | 2 ++ oarepo_requests/ui/components/__init__.py | 2 ++ oarepo_requests/ui/config.py | 4 +++- oarepo_requests/ui/resource.py | 14 +++++++----- .../common/actions/index.js | 7 ++++++ .../common/components/index.js | 7 ++++++ .../common/contexts/index.js | 7 ++++++ .../js/oarepo_requests_ui/common/index.js | 7 ++++++ .../oarepo_requests_ui/common/utils/hooks.js | 7 ++++++ .../oarepo_requests_ui/common/utils/index.js | 7 ++++++ .../common/utils/objects.js | 7 ++++++ .../oarepo_requests_ui/common/utils/utils.js | 7 ++++++ .../oarepo_requests_ui/custom-components.js | 8 +++++++ .../record-requests/components/index.js | 7 ++++++ .../record-requests/index.js | 7 ++++++ .../record-requests/types.d.ts | 7 ++++++ .../request-detail/components/index.js | 7 ++++++ .../request-detail/index.js | 7 ++++++ .../request-detail/types.d.ts | 7 ++++++ .../oarepo_requests_ui/i18next.js | 12 +++++----- .../oarepo_requests_ui/messages/index.js | 8 +++++++ oarepo_requests/ui/theme/webpack.py | 2 ++ oarepo_requests/utils.py | 1 + setup.cfg | 2 +- 46 files changed, 204 insertions(+), 36 deletions(-) diff --git a/oarepo_requests/actions/components.py b/oarepo_requests/actions/components.py index a140705b..4e52a615 100644 --- a/oarepo_requests/actions/components.py +++ b/oarepo_requests/actions/components.py @@ -17,8 +17,6 @@ from typing import ( TYPE_CHECKING, Any, - ContextManager, - Generator, override, ) @@ -28,6 +26,8 @@ from oarepo_requests.services.permissions.identity import request_active if TYPE_CHECKING: + from collections.abc import Generator + from flask_principal import Identity from invenio_records_resources.services.uow import UnitOfWork from invenio_requests.records.api import Request @@ -52,7 +52,7 @@ def apply( uow: UnitOfWork, *args: Any, **kwargs: Any, - ) -> ContextManager: + ) -> contextlib.AbstractContextManager: """Apply the component. Must return a context manager diff --git a/oarepo_requests/actions/publish_draft.py b/oarepo_requests/actions/publish_draft.py index 3e591f8c..f866f8a0 100644 --- a/oarepo_requests/actions/publish_draft.py +++ b/oarepo_requests/actions/publish_draft.py @@ -30,27 +30,32 @@ from invenio_requests.customizations import RequestType from invenio_requests.customizations.actions import RequestAction + class PublishMixin: """Mixin for publish actions.""" def can_execute(self: RequestAction) -> bool: """Check if the action can be executed.""" - if not super().can_execute(): # type: ignore + if not super().can_execute(): # type: ignore return False try: from ..types.publish_draft import PublishDraftRequestType + topic = self.request.topic.resolve() PublishDraftRequestType.validate_topic(system_identity, topic) return True - except: # noqa E722: used for displaying buttons, so ignore errors here + except: # noqa E722: used for displaying buttons, so ignore errors here return False + class PublishDraftSubmitAction(PublishMixin, OARepoSubmitAction): """Submit action for publishing draft requests.""" -class PublishDraftAcceptAction(PublishMixin, AddTopicLinksOnPayloadMixin, OARepoAcceptAction): +class PublishDraftAcceptAction( + PublishMixin, AddTopicLinksOnPayloadMixin, OARepoAcceptAction +): """Accept action for publishing draft requests.""" self_link = "published_record:links:self" @@ -58,7 +63,6 @@ class PublishDraftAcceptAction(PublishMixin, AddTopicLinksOnPayloadMixin, OARepo name = _("Publish") - def apply( self, identity: Identity, diff --git a/oarepo_requests/config.py b/oarepo_requests/config.py index 840627db..977f3485 100644 --- a/oarepo_requests/config.py +++ b/oarepo_requests/config.py @@ -7,6 +7,8 @@ # """Default configuration of oarepo-requests.""" +from __future__ import annotations + import invenio_requests.config import oarepo_workflows # noqa from invenio_requests.customizations import CommentEventType, LogEventType diff --git a/oarepo_requests/invenio_patches.py b/oarepo_requests/invenio_patches.py index 941158ea..0e922b03 100644 --- a/oarepo_requests/invenio_patches.py +++ b/oarepo_requests/invenio_patches.py @@ -29,13 +29,11 @@ from marshmallow import fields from opensearch_dsl.query import Bool -from oarepo_requests.proxies import current_oarepo_requests from oarepo_requests.resources.ui import ( OARepoRequestEventsUIJSONSerializer, OARepoRequestsUIJSONSerializer, ) from oarepo_requests.services.oarepo.config import OARepoRequestsServiceConfig -from oarepo_requests.utils import create_query_term_for_reference if TYPE_CHECKING: from flask.blueprints import BlueprintSetupState @@ -55,23 +53,21 @@ def apply(self, identity: Identity, search: Query, params: dict[str, str]) -> Qu return search -class RequestReceiverFilterParam(FilterParam): - """Filter requests by receiver. +class RequestNotOwnerFilterParam(FilterParam): + """Filter requests that are not owned by the current user. - Note: This is different from the invenio handling. Invenio requires receiver to be - a user, we handle it as a more generic reference. + Note: invenio still does check that the user has the right to see the request, + so this is just a filter to narrow down the search to requests, that the user + can approve. """ def apply(self, identity: Identity, search: Query, params: dict[str, str]) -> Query: """Apply the filter to the search.""" value = params.pop(self.param_name, None) - terms = dsl.Q("match_none") if value is not None: - references = current_oarepo_requests.identity_to_entity_references(identity) - for reference in references: - query_term = create_query_term_for_reference(self.field_name, reference) - terms |= query_term - search = search.filter(Bool(filter=terms)) + search = search.filter( + Bool(must_not=[dsl.Q("term", **{self.field_name: identity.id})]) + ) return search @@ -92,7 +88,7 @@ class EnhancedRequestSearchOptions(RequestSearchOptions): params_interpreters_cls = RequestSearchOptions.params_interpreters_cls + [ RequestOwnerFilterParam.factory("mine", "created_by.user"), - RequestReceiverFilterParam.factory("assigned", "receiver"), + RequestNotOwnerFilterParam.factory("assigned", "created_by.user"), IsClosedParam.factory("is_closed"), ] diff --git a/oarepo_requests/resolvers/ui.py b/oarepo_requests/resolvers/ui.py index 8f33695f..480cde61 100644 --- a/oarepo_requests/resolvers/ui.py +++ b/oarepo_requests/resolvers/ui.py @@ -548,6 +548,8 @@ def _get_entity_ui_representation( class KeywordUIEntityResolver(OARepoUIResolver): + """UI resolver for keyword-like entities.""" + keyword = None @override @@ -562,7 +564,7 @@ def _get_id(self, entity: dict) -> str: def _search_many( self, identity: Identity, ids: list[str], *args: Any, **kwargs: Any ) -> list[dict]: - """Returns list of references of keyword entities. + """Return list of references of keyword entities. :param identity: identity of the user :param ids: ids to search for @@ -576,7 +578,7 @@ def _search_many( def _search_one( self, identity: Identity, _id: str, *args: Any, **kwargs: Any ) -> dict | None: - """Returns keyword entity reference. + """Return keyword entity reference. :param identity: identity of the user :param _id: the keyword value @@ -586,6 +588,8 @@ def _search_one( class AutoApproveUIEntityResolver(KeywordUIEntityResolver): + """UI resolver for auto approve entities.""" + keyword = "auto_approve" @override @@ -598,7 +602,6 @@ def _get_entity_ui_representation( :reference: reference to the entity :return: UI representation of the entity """ - return UIResolvedReference( reference=reference, type=self.keyword, diff --git a/oarepo_requests/resources/draft/config.py b/oarepo_requests/resources/draft/config.py index 39d579e8..13dd1ea4 100644 --- a/oarepo_requests/resources/draft/config.py +++ b/oarepo_requests/resources/draft/config.py @@ -7,6 +7,8 @@ # """Configuration of the draft record requests resource.""" +from __future__ import annotations + from oarepo_requests.resources.record.config import RecordRequestsResourceConfig diff --git a/oarepo_requests/resources/draft/types/config.py b/oarepo_requests/resources/draft/types/config.py index bf85ab49..19631bc6 100644 --- a/oarepo_requests/resources/draft/types/config.py +++ b/oarepo_requests/resources/draft/types/config.py @@ -7,6 +7,8 @@ # """Draft request types resource configuration.""" +from __future__ import annotations + from oarepo_requests.resources.record.types.config import ( RecordRequestTypesResourceConfig, ) diff --git a/oarepo_requests/resources/events/config.py b/oarepo_requests/resources/events/config.py index 534242a4..01ca50f3 100644 --- a/oarepo_requests/resources/events/config.py +++ b/oarepo_requests/resources/events/config.py @@ -7,6 +7,8 @@ # """Resource configuration for events and comments.""" +from __future__ import annotations + from flask_resources import ResponseHandler from invenio_records_resources.services.base.config import ConfiguratorMixin from invenio_requests.resources.events.config import RequestCommentsResourceConfig diff --git a/oarepo_requests/resources/events/resource.py b/oarepo_requests/resources/events/resource.py index 5b75b0c8..d5a0136c 100644 --- a/oarepo_requests/resources/events/resource.py +++ b/oarepo_requests/resources/events/resource.py @@ -7,6 +7,8 @@ # """Resource for request events/comments that lives on the extended url.""" +from __future__ import annotations + from flask_resources import route from invenio_records_resources.resources.errors import ErrorHandlersMixin from invenio_requests.resources.events.resource import RequestCommentsResource diff --git a/oarepo_requests/resources/oarepo/config.py b/oarepo_requests/resources/oarepo/config.py index 5aaaa5d7..4a44427c 100644 --- a/oarepo_requests/resources/oarepo/config.py +++ b/oarepo_requests/resources/oarepo/config.py @@ -7,6 +7,8 @@ # """Config for the extended requests API.""" +from __future__ import annotations + from flask_resources import ResponseHandler from invenio_records_resources.services.base.config import ConfiguratorMixin from invenio_requests.resources import RequestsResourceConfig diff --git a/oarepo_requests/resources/record/config.py b/oarepo_requests/resources/record/config.py index bfc702d7..27083bba 100644 --- a/oarepo_requests/resources/record/config.py +++ b/oarepo_requests/resources/record/config.py @@ -7,6 +7,8 @@ # """Configuration of the record requests resource.""" +from __future__ import annotations + import marshmallow as ma from flask_resources import JSONSerializer, ResponseHandler from invenio_records_resources.resources import RecordResourceConfig diff --git a/oarepo_requests/services/permissions/generators/__init__.py b/oarepo_requests/services/permissions/generators/__init__.py index fc966713..3e5a43af 100644 --- a/oarepo_requests/services/permissions/generators/__init__.py +++ b/oarepo_requests/services/permissions/generators/__init__.py @@ -7,6 +7,8 @@ # """Permission generators.""" +from __future__ import annotations + from .active import RequestActive from .conditional import ( IfEventOnRequestType, diff --git a/oarepo_requests/services/permissions/identity.py b/oarepo_requests/services/permissions/identity.py index 7e138caf..34477c6d 100644 --- a/oarepo_requests/services/permissions/identity.py +++ b/oarepo_requests/services/permissions/identity.py @@ -7,6 +7,8 @@ # """Request needs.""" +from __future__ import annotations + from invenio_access.permissions import SystemRoleNeed request_active = SystemRoleNeed("request") diff --git a/oarepo_requests/services/permissions/workflow_policies.py b/oarepo_requests/services/permissions/workflow_policies.py index 9dc9da78..f947d5a3 100644 --- a/oarepo_requests/services/permissions/workflow_policies.py +++ b/oarepo_requests/services/permissions/workflow_policies.py @@ -7,6 +7,8 @@ # """Permissions for requests based on workflows.""" +from __future__ import annotations + from oarepo_workflows.requests.permissions import ( # this is for backward compatibility ... CreatorsFromWorkflowRequestsPermissionPolicy, diff --git a/oarepo_requests/services/results.py b/oarepo_requests/services/results.py index 777eef98..bd405238 100644 --- a/oarepo_requests/services/results.py +++ b/oarepo_requests/services/results.py @@ -9,7 +9,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Iterator, cast +from typing import TYPE_CHECKING, Any, cast from invenio_records_resources.services import LinksTemplate from invenio_records_resources.services.errors import PermissionDeniedError @@ -24,6 +24,8 @@ ) if TYPE_CHECKING: + from collections.abc import Iterator + from flask_principal import Identity from invenio_records_resources.records.api import Record from invenio_requests.customizations.request_types import RequestType diff --git a/oarepo_requests/services/schema.py b/oarepo_requests/services/schema.py index 51bba187..874deaba 100644 --- a/oarepo_requests/services/schema.py +++ b/oarepo_requests/services/schema.py @@ -7,6 +7,8 @@ # """Enhancements to the request schema.""" +from __future__ import annotations + from typing import Any import marshmallow as ma diff --git a/oarepo_requests/translations/_only_for_translations.py b/oarepo_requests/translations/_only_for_translations.py index 62e29c78..24c65d27 100644 --- a/oarepo_requests/translations/_only_for_translations.py +++ b/oarepo_requests/translations/_only_for_translations.py @@ -5,6 +5,8 @@ # modify it under the terms of the MIT License; see LICENSE file for more # details. # +from __future__ import annotations + from oarepo_runtime.i18n import lazy_gettext as _ _("Create Request") diff --git a/oarepo_requests/types/__init__.py b/oarepo_requests/types/__init__.py index 27316423..940200d6 100644 --- a/oarepo_requests/types/__init__.py +++ b/oarepo_requests/types/__init__.py @@ -7,6 +7,8 @@ # """Request types defined in oarepo-requests.""" +from __future__ import annotations + from .delete_published_record import DeletePublishedRecordRequestType from .edit_record import EditPublishedRecordRequestType from .generic import NonDuplicableOARepoRequestType diff --git a/oarepo_requests/types/events/__init__.py b/oarepo_requests/types/events/__init__.py index a2e6cacb..7ee284d0 100644 --- a/oarepo_requests/types/events/__init__.py +++ b/oarepo_requests/types/events/__init__.py @@ -7,6 +7,8 @@ # """Request events.""" +from __future__ import annotations + from .topic_delete import TopicDeleteEventType from .topic_update import TopicUpdateEventType diff --git a/oarepo_requests/types/events/topic_delete.py b/oarepo_requests/types/events/topic_delete.py index 0f42cd70..09f06c3c 100644 --- a/oarepo_requests/types/events/topic_delete.py +++ b/oarepo_requests/types/events/topic_delete.py @@ -7,6 +7,8 @@ # """Topic delete event type.""" +from __future__ import annotations + from invenio_requests.customizations.event_types import EventType from marshmallow import fields diff --git a/oarepo_requests/types/events/topic_update.py b/oarepo_requests/types/events/topic_update.py index d5cf44fe..f3c48b21 100644 --- a/oarepo_requests/types/events/topic_update.py +++ b/oarepo_requests/types/events/topic_update.py @@ -7,6 +7,8 @@ # """Topic update event type.""" +from __future__ import annotations + from invenio_requests.customizations.event_types import EventType from marshmallow import fields diff --git a/oarepo_requests/types/events/validation.py b/oarepo_requests/types/events/validation.py index f6734516..d0011986 100644 --- a/oarepo_requests/types/events/validation.py +++ b/oarepo_requests/types/events/validation.py @@ -7,6 +7,8 @@ # """Validation of event types.""" +from __future__ import annotations + def _serialized_topic_validator(value: str) -> str: """Validate the serialized topic. It must be a string with model and id separated by a single dot.""" diff --git a/oarepo_requests/typing.py b/oarepo_requests/typing.py index 46b98dd6..20f16ea0 100644 --- a/oarepo_requests/typing.py +++ b/oarepo_requests/typing.py @@ -7,4 +7,6 @@ # """Base types for requests.""" +from __future__ import annotations + type EntityReference = dict[str, str] diff --git a/oarepo_requests/ui/components/__init__.py b/oarepo_requests/ui/components/__init__.py index 422ad0ae..28045da0 100644 --- a/oarepo_requests/ui/components/__init__.py +++ b/oarepo_requests/ui/components/__init__.py @@ -7,6 +7,8 @@ # """UI components.""" +from __future__ import annotations + from oarepo_requests.ui.components.action_labels import ActionLabelsComponent from oarepo_requests.ui.components.custom_fields import ( FormConfigCustomFieldsComponent, diff --git a/oarepo_requests/ui/config.py b/oarepo_requests/ui/config.py index 587139ad..ad29e2d5 100644 --- a/oarepo_requests/ui/config.py +++ b/oarepo_requests/ui/config.py @@ -10,7 +10,7 @@ from __future__ import annotations import inspect -from typing import TYPE_CHECKING, Any, Mapping +from typing import TYPE_CHECKING, Any import marshmallow as ma from flask import current_app @@ -35,6 +35,8 @@ ) if TYPE_CHECKING: + from collections.abc import Mapping + from flask_resources.serializers.base import BaseSerializer from invenio_records_resources.records import Record from invenio_requests.customizations.request_types import RequestType diff --git a/oarepo_requests/ui/resource.py b/oarepo_requests/ui/resource.py index 087cfdbc..b3aac80a 100644 --- a/oarepo_requests/ui/resource.py +++ b/oarepo_requests/ui/resource.py @@ -7,10 +7,11 @@ # """UI resource for requests - python part.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING, Any from flask import Response, g -from flask_principal import Identity from flask_resources import resource_requestctx, route from invenio_records_resources.proxies import current_service_registry from invenio_records_resources.resources.records.resource import ( @@ -18,13 +19,16 @@ request_view_args, ) from invenio_records_resources.services import LinksTemplate -from invenio_requests.records.api import Request -from invenio_requests.services import RequestsService from oarepo_ui.proxies import current_oarepo_ui from oarepo_ui.resources.resource import UIResource from oarepo_ui.resources.templating.data import FieldData -from oarepo_requests.ui.config import RequestUIResourceConfig +if TYPE_CHECKING: + from flask_principal import Identity + from invenio_requests.records.api import Request + from invenio_requests.services import RequestsService + + from oarepo_requests.ui.config import RequestUIResourceConfig def make_links_absolute(links: dict[str, str | Any], api_prefix: str) -> None: diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/index.js index ef13ce6d..733cbdd0 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ import { REQUEST_TYPE } from "@js/oarepo_requests_common"; import Accept from "./Accept"; import Decline from "./Decline"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/index.js index 5c177589..a340df44 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export * from "./DefaultView"; export * from "./RequestModalContent"; export * from "./RequestModal"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/contexts/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/contexts/index.js index 8e55b46d..cbfd5653 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/contexts/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/contexts/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export { RequestContextProvider, useRequestContext } from "./RequestContext"; export { ConfirmModalContextProvider, diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/index.js index 8ea0b8f8..5d6f3e6f 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export * from "./actions"; export * from "./contexts"; export * from "./utils"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/hooks.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/hooks.js index 046cd396..94c0c7bc 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/hooks.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/hooks.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ import React, { useState, useCallback, useRef } from "react"; import { i18next } from "@translations/oarepo_requests_ui/i18next"; import { Message, Icon } from "semantic-ui-react"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/index.js index 70bd6b39..497c5fc3 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export * from "./utils"; export * from "./objects"; export * from "./hooks"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/objects.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/objects.js index 2381627d..f481afa9 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/objects.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/objects.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export const REQUEST_TYPE = { CREATE: 'create', SUBMIT: 'submit', diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/utils.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/utils.js index 9cc1cadc..a9aaaafa 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/utils.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/utils/utils.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ import _isEmpty from "lodash/isEmpty"; import { httpVnd } from "@js/oarepo_ui"; import _set from "lodash/set"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/custom-components.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/custom-components.js index 9643553c..a6307b85 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/custom-components.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/custom-components.js @@ -1,3 +1,11 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ + /* This is the registration file for custom components. The components should not be included here, but only referenced. The sample component below can be used to start up working on your own custom diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/components/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/components/index.js index 8e72a560..2199929a 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/components/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/components/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export { RequestListContainer } from "./RequestListContainer"; export { CreateRequestButtonGroup } from "./CreateRequestButtonGroup"; export { RequestModal, RequestModalContent } from "@js/oarepo_requests_common"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/index.js index 34769140..a04c104b 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ import React from "react"; import ReactDOM from "react-dom"; import { RecordRequests } from "./components"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/types.d.ts b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/types.d.ts index 593fcc45..01249281 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/types.d.ts +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/record-requests/types.d.ts @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export interface Request { name: string; description: string; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/components/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/components/index.js index 22463079..374f9737 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/components/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/components/index.js @@ -1 +1,8 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export { RequestDetail } from "./RequestDetail"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/index.js index a6e4a297..512e8330 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/index.js @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ import React from "react"; import ReactDOM from "react-dom"; import { FormConfigProvider } from "@js/oarepo_ui"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/types.d.ts b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/types.d.ts index 593fcc45..01249281 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/types.d.ts +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/request-detail/types.d.ts @@ -1,3 +1,10 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ export interface Request { name: string; description: string; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/i18next.js b/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/i18next.js index d5dae7d2..48a2b6b7 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/i18next.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/i18next.js @@ -1,8 +1,10 @@ -// This file is part of React-Invenio-Deposit -// Copyright (C) 2021 Graz University of Technology. -// -// Invenio-app-rdm is free software; you can redistribute it and/or modify it -// under the terms of the MIT License; see LICENSE file for more details. +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ import i18n from "i18next"; diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/messages/index.js b/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/messages/index.js index 5a8c3f18..413e7d40 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/messages/index.js +++ b/oarepo_requests/ui/theme/assets/semantic-ui/translations/oarepo_requests_ui/messages/index.js @@ -1,3 +1,11 @@ +/* + * Copyright (C) 2024 CESNET z.s.p.o. + * + * oarepo-requests is free software; you can redistribute it and/or + * modify it under the terms of the MIT License; see LICENSE file for more + * details. + */ + // This file was autogenerated with oarepo-tools import TRANSLATE_CS from "./cs/LC_MESSAGES/translations.json" import TRANSLATE_EN from "./en/LC_MESSAGES/translations.json" diff --git a/oarepo_requests/ui/theme/webpack.py b/oarepo_requests/ui/theme/webpack.py index 92d26d48..83c488cd 100644 --- a/oarepo_requests/ui/theme/webpack.py +++ b/oarepo_requests/ui/theme/webpack.py @@ -7,6 +7,8 @@ # """Webpack entry points for the UI components of the module (requests components and dialogs).""" +from __future__ import annotations + from invenio_assets.webpack import WebpackThemeBundle theme = WebpackThemeBundle( diff --git a/oarepo_requests/utils.py b/oarepo_requests/utils.py index 14ab0515..9e3c5231 100644 --- a/oarepo_requests/utils.py +++ b/oarepo_requests/utils.py @@ -223,6 +223,7 @@ def reference_to_tuple(reference: EntityReference) -> tuple[str, str]: """ return next(iter(reference.items())) + # TODO: consider moving to oarepo-workflows def get_receiver_for_request_type( request_type: RequestType, identity: Identity, topic: Record diff --git a/setup.cfg b/setup.cfg index cb047f8e..94136fd1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.3.8 +version = 2.3.9 description = authors = Ronald Krist readme = README.md From b923399e53d4851f1ed5fad16b1e55504d975478 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Thu, 5 Dec 2024 10:24:24 +0100 Subject: [PATCH 20/44] NPE in requests without recipients --- oarepo_requests/services/oarepo/config.py | 6 +++++- setup.cfg | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index e19fdc80..f1470d05 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -54,7 +54,11 @@ def _resolve(self, obj: Request, ctx: dict[str, Any]) -> dict: :param ctx: Context cache :return: The resolved entity """ - reference_dict: dict = getattr(obj, self.entity).reference_dict + entity_field_value = getattr(obj, self.entity) + if not entity_field_value: + return {} + + reference_dict: dict = entity_field_value.reference_dict key = "entity:" + ":".join( f"{x[0]}:{x[1]}" for x in sorted(reference_dict.items()) ) diff --git a/setup.cfg b/setup.cfg index cb047f8e..94136fd1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.3.8 +version = 2.3.9 description = authors = Ronald Krist readme = README.md From da4a18b32562738a2912edcb4ae0a86ed1f3f2d0 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Thu, 5 Dec 2024 15:52:03 +0100 Subject: [PATCH 21/44] type checking --- oarepo_requests/types/edit_record.py | 2 +- oarepo_requests/types/new_version.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index 07738dcf..c9cab93b 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -54,7 +54,7 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): ), } - def extra_entity_links(self, request, entity, entity_type: str, **kwargs): + def extra_entity_links(self, request: Request, entity: dict, entity_type: str, **kwargs) -> dict: if request.status == "accepted" and entity_type == "topic": return {"topic_redirect_link": entity["links"]["edit_html"]} else: diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py index 73f90e53..d87158a4 100644 --- a/oarepo_requests/types/new_version.py +++ b/oarepo_requests/types/new_version.py @@ -50,7 +50,7 @@ class NewVersionRequestType(NonDuplicableOARepoRequestType): "keep_files": ma.fields.String(validate=OneOf(["true", "false"])), } - def extra_entity_links(self, request, entity, entity_type: str, **kwargs): + def extra_entity_links(self, request: Request, entity: dict, entity_type: str, **kwargs) -> dict: if request.status == "accepted" and entity_type == "topic": return {"topic_redirect_link": entity["links"]["edit_html"]} else: From 1ae302722f721e7b32db0e32b614b39e8c4e19aa Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Thu, 5 Dec 2024 15:52:11 +0100 Subject: [PATCH 22/44] version bump --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index e50afe0e..94136fd1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.3.2 +version = 2.3.9 description = authors = Ronald Krist readme = README.md From 1f0518528bb0a6fa1bd410436d3f0e9317577e47 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Thu, 5 Dec 2024 16:29:58 +0100 Subject: [PATCH 23/44] version bump --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index cb047f8e..94136fd1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 2.3.8 +version = 2.3.9 description = authors = Ronald Krist readme = README.md From 6a2e89bfd80265b67e6a27bd38d745ae1ac4b92d Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 6 Dec 2024 10:14:19 +0100 Subject: [PATCH 24/44] test refactor bug fix --- tests/test_requests/test_ui_serialialization.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_requests/test_ui_serialialization.py b/tests/test_requests/test_ui_serialialization.py index f110eca6..afe3bcbb 100644 --- a/tests/test_requests/test_ui_serialialization.py +++ b/tests/test_requests/test_ui_serialialization.py @@ -275,7 +275,7 @@ def test_auto_approve( json=new_version_data_function(record1["id"]), ) resp_request_submit = creator_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) # is request accepted and closed? request_json = creator_client.get( From 7bec784b2b9acdb422955aca5fbea80d567aa1c5 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Fri, 6 Dec 2024 10:38:46 +0100 Subject: [PATCH 25/44] test refactor bug fix --- tests/test_requests/test_workflows.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_requests/test_workflows.py b/tests/test_requests/test_workflows.py index 6fa0301f..c1d5a9e2 100644 --- a/tests/test_requests/test_workflows.py +++ b/tests/test_requests/test_workflows.py @@ -451,7 +451,7 @@ def test_workflow_events_resource( json=approve_request_data, ) resp_request_submit = user1_client.post( - link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), + link2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) read_from_record = user1_client.get( @@ -474,7 +474,7 @@ def test_workflow_events_resource( f'{urls["BASE_URL"]}{record_id}/draft?expand=true' ).json accept = user2_client.post( - link_api2testclient( + link2testclient( record_receiver["expanded"]["requests"][0]["links"]["actions"]["accept"] ), ) From 5bbfb438e0ef5e3af531ce3b1aee9e46cdabf0e8 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Fri, 6 Dec 2024 11:30:07 +0100 Subject: [PATCH 26/44] fixed tests --- tests/conftest.py | 4 +++ tests/test_requests/test_no_recipient.py | 31 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/test_requests/test_no_recipient.py diff --git a/tests/conftest.py b/tests/conftest.py index 840c4ed6..da0e947e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -184,6 +184,10 @@ class RequestsWithCT(WorkflowRequestPolicy): IfRequestedBy(UserGenerator(1), [UserGenerator(2)], [UserGenerator(3)]) ], ) + approve_draft = WorkflowRequest( + requesters=[IfInState("draft", [RecordOwners()])], + recipients=[], + ) class RequestsWithAnotherTopicUpdatingRequestType(DefaultRequests): diff --git a/tests/test_requests/test_no_recipient.py b/tests/test_requests/test_no_recipient.py new file mode 100644 index 00000000..71d33a77 --- /dev/null +++ b/tests/test_requests/test_no_recipient.py @@ -0,0 +1,31 @@ +# +# Copyright (C) 2024 CESNET z.s.p.o. +# +# oarepo-requests is free software; you can redistribute it and/or +# modify it under the terms of the MIT License; see LICENSE file for more +# details. +# +def test_no_recipient( + logged_client, + users, + urls, + create_draft_via_resource, + search_clear, +): + creator = users[0] + assert creator.id == 1 + + creator_client = logged_client(creator) + + draft1 = create_draft_via_resource(creator_client, custom_workflow="with_ct") + + resp_request_create = creator_client.post( + urls["BASE_URL_REQUESTS"], + json={ + "request_type": "approve_draft", + "topic": {"thesis_draft": draft1.json["id"]}, + } + ) + print(resp_request_create.json) + assert resp_request_create.status_code == 201 + assert resp_request_create.json == {} From aeb4674a5224a4fe3a94f58dfbb2b51382057518 Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Fri, 6 Dec 2024 11:52:02 +0100 Subject: [PATCH 27/44] fix: Merge mistake --- oarepo_requests/services/oarepo/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 83abadc1..6f018d17 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -34,7 +34,7 @@ def _resolve(self, obj: Request, ctx: dict[str, Any]) -> dict: :param ctx: Context cache :return: The resolved entity """ - entity_field_value = getattr(obj, self.entity) + entity_field_value = getattr(obj, self._entity) if not entity_field_value: return {} From 06201a01a0da56ba9f462e4b5e380416d676efae Mon Sep 17 00:00:00 2001 From: Mirek Simek Date: Fri, 6 Dec 2024 12:18:19 +0100 Subject: [PATCH 28/44] Fixed tests --- oarepo_requests/services/oarepo/config.py | 4 +++- tests/test_requests/test_no_recipient.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/oarepo_requests/services/oarepo/config.py b/oarepo_requests/services/oarepo/config.py index 6f018d17..0345b978 100644 --- a/oarepo_requests/services/oarepo/config.py +++ b/oarepo_requests/services/oarepo/config.py @@ -65,7 +65,9 @@ def __init__(self, entity: str, when: callable = None): def expand(self, obj: Request, context: dict) -> dict: """Create the request links.""" res = {} - res.update(self._resolve(obj, context)["links"]) + resolved = self._resolve(obj, context) + if "links" in resolved: + res.update(resolved["links"]) if hasattr(obj.type, "extra_entity_links"): entity = self._resolve(obj, context) diff --git a/tests/test_requests/test_no_recipient.py b/tests/test_requests/test_no_recipient.py index 71d33a77..76526e9f 100644 --- a/tests/test_requests/test_no_recipient.py +++ b/tests/test_requests/test_no_recipient.py @@ -13,7 +13,7 @@ def test_no_recipient( search_clear, ): creator = users[0] - assert creator.id == 1 + assert creator.id == '1' creator_client = logged_client(creator) @@ -26,6 +26,6 @@ def test_no_recipient( "topic": {"thesis_draft": draft1.json["id"]}, } ) - print(resp_request_create.json) assert resp_request_create.status_code == 201 - assert resp_request_create.json == {} + assert resp_request_create.json['receiver'] is None + assert resp_request_create.json['links']['receiver'] == {} From 358408a1ceed6ca78ea584187c24e76b6de980c3 Mon Sep 17 00:00:00 2001 From: ducica Date: Mon, 9 Dec 2024 13:22:06 +0100 Subject: [PATCH 29/44] using new links structure --- .../common/actions/DirectCreateAndSubmit.jsx | 11 +++++--- .../common/actions/RequestActionButton.jsx | 1 + .../common/components/RequestCommentInput.jsx | 1 + .../common/components/RequestModalContent.jsx | 16 ++++++----- .../common/components/SideRequestInfo.jsx | 16 ++++++----- .../common/components/TopicPreview.jsx | 2 +- .../oarepo_requests_ui/common/utils/hooks.js | 27 +++++++++++-------- .../record-requests/index.js | 4 +-- .../components/RequestDetail.jsx | 2 +- 9 files changed, 49 insertions(+), 31 deletions(-) diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/DirectCreateAndSubmit.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/DirectCreateAndSubmit.jsx index 24a828ad..8221796c 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/DirectCreateAndSubmit.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/DirectCreateAndSubmit.jsx @@ -22,6 +22,7 @@ const DirectCreateAndSubmit = ({ isLoading, mutate: createAndSubmit, isError, + error, reset, } = useAction({ action: saveAndSubmit, @@ -69,9 +70,13 @@ const DirectCreateAndSubmit = ({ {isError && ( - {i18next.t( - "Request not created successfully. Please try again in a moment." - )} + {error?.response?.data?.errors?.length > 0 + ? i18next.t( + "Record has validation errors. Redirecting to form..." + ) + : i18next.t( + "Request not created successfully. Please try again in a moment." + )} )} diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/RequestActionButton.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/RequestActionButton.jsx index a4febd72..7a548f9d 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/RequestActionButton.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/actions/RequestActionButton.jsx @@ -27,6 +27,7 @@ export const RequestActionButton = ({ requestOrRequestType: requestOrRequestType, formik, modalControl, + requestActionName, }); const handleClick = () => { diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestCommentInput.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestCommentInput.jsx index d1ee5fb5..a96e9c27 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestCommentInput.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestCommentInput.jsx @@ -37,6 +37,7 @@ export const RequestCommentInput = ({ editorConfig={{ auto_focus: true, min_height: 100, + width: "100%", toolbar: "blocks | bold italic | bullist numlist | outdent indent | undo redo", }} diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestModalContent.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestModalContent.jsx index 7305aa20..6c51f06f 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestModalContent.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/RequestModalContent.jsx @@ -38,13 +38,15 @@ export const RequestModalContent = ({ {description && ( {" "} - - ({i18next.t("Request details")}) - + {request?.links?.self_html && ( + + ({i18next.t("Request details")}) + + )} )} diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx index 2b3350c2..c47a80b7 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/SideRequestInfo.jsx @@ -15,9 +15,9 @@ export const SideRequestInfo = ({ request }) => { - {_has(request, "links.created_by_html") ? ( + {_has(request, "links.created_by.self_html") ? ( @@ -34,9 +34,9 @@ export const SideRequestInfo = ({ request }) => { - {_has(request, "links.receiver_html") ? ( + {_has(request, "links.receiver.self_html") ? ( @@ -61,11 +61,15 @@ export const SideRequestInfo = ({ request }) => { {i18next.t("Created")} {request.created} - {request?.links?.topic_html && ( + {request?.links?.topic?.self_html && ( {i18next.t("Topic")} - + {request?.topic?.label ? _truncate(request?.topic?.label, { length: 350, diff --git a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/TopicPreview.jsx b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/TopicPreview.jsx index 4b3e17ff..488ebb82 100644 --- a/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/TopicPreview.jsx +++ b/oarepo_requests/ui/theme/assets/semantic-ui/js/oarepo_requests_ui/common/components/TopicPreview.jsx @@ -31,7 +31,7 @@ export const TopicPreview = ({ request }) => { )}