From c8962114116b0ec64e83758960f2ae0598da1eda Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 26 Aug 2024 13:36:07 +0200 Subject: [PATCH 1/3] new version request --- oarepo_requests/actions/new_version.py | 18 +++++++ oarepo_requests/types/new_version.py | 34 +++++++++++++ setup.cfg | 1 + tests/conftest.py | 16 ++++++ ..._allowed_request_types_link_and_service.py | 8 +++ tests/test_requests/test_new_version.py | 49 +++++++++++++++++++ tests/test_ui/test_ui_resource.py | 2 +- 7 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 oarepo_requests/actions/new_version.py create mode 100644 oarepo_requests/types/new_version.py create mode 100644 tests/test_requests/test_new_version.py diff --git a/oarepo_requests/actions/new_version.py b/oarepo_requests/actions/new_version.py new file mode 100644 index 00000000..72268aa4 --- /dev/null +++ b/oarepo_requests/actions/new_version.py @@ -0,0 +1,18 @@ +from oarepo_runtime.datastreams.utils import get_record_service_for_record + +from .generic import AddTopicLinksOnPayloadMixin, OARepoAcceptAction + + +class NewVersionAcceptAction(AddTopicLinksOnPayloadMixin, OARepoAcceptAction): + self_link = "draft_record:links:self" + self_html_link = "draft_record:links:self_html" + + def apply(self, identity, request_type, topic, uow, *args, **kwargs): + topic_service = get_record_service_for_record(topic) + if not topic_service: + raise KeyError(f"topic {topic} service not found") + new_version_topic = topic_service.new_version(identity, topic["id"], uow=uow) + + return super().apply( + identity, request_type, new_version_topic, uow, *args, **kwargs + ) diff --git a/oarepo_requests/types/new_version.py b/oarepo_requests/types/new_version.py new file mode 100644 index 00000000..66f966ba --- /dev/null +++ b/oarepo_requests/types/new_version.py @@ -0,0 +1,34 @@ +import marshmallow as ma +from oarepo_runtime.i18n import lazy_gettext as _ + +from ..actions.new_version import NewVersionAcceptAction +from .generic import NonDuplicableOARepoRequestType +from .ref_types import ModelRefTypes + + +class NewVersionRequestType( + NonDuplicableOARepoRequestType +): # NewVersionFromPublishedRecord? or just new_version + type_id = "new_version" + name = _("New Version") + payload_schema = { + "draft_record.links.self": ma.fields.Str( + attribute="draft_record:links:self", + data_key="draft_record:links:self", + ), + "draft_record.links.self_html": ma.fields.Str( + attribute="draft_record:links:self_html", + data_key="draft_record:links:self_html", + ), + } + + @classmethod + @property + def available_actions(cls): + return { + **super().available_actions, + "accept": NewVersionAcceptAction, + } + + description = _("Request requesting creation of new version of a published record.") + allowed_topic_ref_types = ModelRefTypes(published=True, draft=False) diff --git a/setup.cfg b/setup.cfg index 7cce451a..8d8f5c51 100644 --- a/setup.cfg +++ b/setup.cfg @@ -49,6 +49,7 @@ invenio_requests.types = delete_published_record = oarepo_requests.types.delete_record:DeletePublishedRecordRequestType edit_published_record = oarepo_requests.types.edit_record:EditPublishedRecordRequestType publish_draft = oarepo_requests.types.publish_draft:PublishDraftRequestType + new_version = oarepo_requests.types.new_version:NewVersionRequestType invenio_requests.entity_resolvers = auto_approve = oarepo_requests.resolvers.autoapprove:AutoApproveResolver oarepo_workflows.state_changed_notifiers = diff --git a/tests/conftest.py b/tests/conftest.py index 987a18e3..6ac472a2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,6 +69,11 @@ class DefaultRequests(WorkflowRequestPolicy): recipients=[AutoApprove()], transitions=WorkflowTransitions(), ) + new_version = WorkflowRequest( + requesters=[IfInState("published", [RecordOwners()])], + recipients=[AutoApprove()], + transitions=WorkflowTransitions(), + ) class UserGenerator(RecipientGeneratorMixin, Generator): @@ -247,6 +252,17 @@ def ret_data(record_id): return ret_data +@pytest.fixture() +def new_version_data_function(): + def ret_data(record_id): + return { + "request_type": "new_version", + "topic": {"thesis": record_id}, + } + + return ret_data + + @pytest.fixture() def delete_record_data_function(): def ret_data(record_id): 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 2e7b2e6e..44b044ea 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 @@ -171,4 +171,12 @@ def test_allowed_request_types_on_published_resource( }, "type_id": "edit_published_record", }, + { + "links": { + "actions": { + "create": f'https://127.0.0.1:5000/api/thesis/{published1["id"]}/requests/new_version' + } + }, + "type_id": "new_version", + }, ] diff --git a/tests/test_requests/test_new_version.py b/tests/test_requests/test_new_version.py new file mode 100644 index 00000000..4588d594 --- /dev/null +++ b/tests/test_requests/test_new_version.py @@ -0,0 +1,49 @@ +from thesis.records.api import ThesisDraft, ThesisRecord + +from tests.test_requests.utils import link_api2testclient + + +def test_new_version_autoaccept( + 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 + + assert request["status"] == "accepted" + assert not request["is_open"] + assert request["is_closed"] + + assert "draft_record:links:self" in request["payload"] + assert "draft_record:links:self_html" in request["payload"] + + ThesisRecord.index.refresh() + ThesisDraft.index.refresh() + # new_version action worked? + search = creator_client.get( + f'user{urls["BASE_URL"]}', + ).json[ + "hits" + ]["hits"] + assert len(search) == 2 + assert search[0]["id"] != search[1]["id"] + assert search[0]["parent"]["id"] == search[1]["parent"]["id"] diff --git a/tests/test_ui/test_ui_resource.py b/tests/test_ui/test_ui_resource.py index 16282c6b..57f66834 100644 --- a/tests/test_ui/test_ui_resource.py +++ b/tests/test_ui/test_ui_resource.py @@ -32,7 +32,7 @@ def test_record_delete_request_present( with logged_client(users[0]).get(f"/thesis/{topic['id']}") as c: assert c.status_code == 200 data = json.loads(c.text) - assert len(data["creatable_request_types"]) == 2 + assert len(data["creatable_request_types"]) == 3 assert data["creatable_request_types"]["edit_published_record"] == { "description": "Request re-opening of published record", "links": { From 2e5f4d89f0fdb7b98fc17ce3b68a02c51cb23e30 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 26 Aug 2024 13:36:51 +0200 Subject: [PATCH 2/3] topic links on request payload --- oarepo_requests/actions/edit_topic.py | 26 ++++++------------------ oarepo_requests/actions/generic.py | 18 ++++++++++++++++ oarepo_requests/actions/publish_draft.py | 25 +++++++---------------- tests/test_requests/test_edit.py | 5 +++++ 4 files changed, 36 insertions(+), 38 deletions(-) diff --git a/oarepo_requests/actions/edit_topic.py b/oarepo_requests/actions/edit_topic.py index 64e54669..800b94f0 100644 --- a/oarepo_requests/actions/edit_topic.py +++ b/oarepo_requests/actions/edit_topic.py @@ -1,30 +1,16 @@ from oarepo_runtime.datastreams.utils import get_record_service_for_record -from .generic import OARepoAcceptAction +from .generic import AddTopicLinksOnPayloadMixin, OARepoAcceptAction -class EditTopicAcceptAction(OARepoAcceptAction): +class EditTopicAcceptAction(AddTopicLinksOnPayloadMixin, OARepoAcceptAction): + self_link = "draft_record:links:self" + self_html_link = "draft_record:links:self_html" + def apply(self, identity, request_type, topic, uow, *args, **kwargs): topic_service = get_record_service_for_record(topic) if not topic_service: raise KeyError(f"topic {topic} service not found") edit_topic = topic_service.edit(identity, topic["id"], uow=uow) - # add links to the draft (edited) record - edit_topic_dict = edit_topic.to_dict() - - if "payload" not in self.request: - self.request["payload"] = {} - - # invenio does not allow non-string values in the payload, so using colon notation here - # client will need to handle this and convert to links structure - # can not use dot notation as marshmallow tries to be too smart and does not serialize dotted keys - self.request["payload"]["draft_record:links:self"] = edit_topic_dict[ - "links" - ]["self"] - self.request["payload"]["draft_record:links:self_html"] = ( - edit_topic_dict["links"]["self_html"] - ) - - return edit_topic._record - + return super().apply(identity, request_type, edit_topic, uow, *args, **kwargs) diff --git a/oarepo_requests/actions/generic.py b/oarepo_requests/actions/generic.py index 251bb1b4..51e19fc9 100644 --- a/oarepo_requests/actions/generic.py +++ b/oarepo_requests/actions/generic.py @@ -38,6 +38,24 @@ def execute(self, identity, uow, *args, **kwargs): ) +class AddTopicLinksOnPayloadMixin: + self_link = None + self_html_link = None + + def apply(self, identity, request_type, topic, uow, *args, **kwargs): + topic_dict = topic.to_dict() + + if "payload" not in self.request: + self.request["payload"] = {} + + # invenio does not allow non-string values in the payload, so using colon notation here + # client will need to handle this and convert to links structure + # can not use dot notation as marshmallow tries to be too smart and does not serialize dotted keys + self.request["payload"][self.self_link] = topic_dict["links"]["self"] + self.request["payload"][self.self_html_link] = topic_dict["links"]["self_html"] + return topic._record + + class OARepoSubmitAction(OARepoGenericActionMixin, actions.SubmitAction): """""" diff --git a/oarepo_requests/actions/publish_draft.py b/oarepo_requests/actions/publish_draft.py index 478370bf..55b250fa 100644 --- a/oarepo_requests/actions/publish_draft.py +++ b/oarepo_requests/actions/publish_draft.py @@ -1,9 +1,12 @@ from oarepo_runtime.datastreams.utils import get_record_service_for_record -from .generic import OARepoAcceptAction +from .generic import AddTopicLinksOnPayloadMixin, OARepoAcceptAction -class PublishDraftAcceptAction(OARepoAcceptAction): +class PublishDraftAcceptAction(AddTopicLinksOnPayloadMixin, OARepoAcceptAction): + self_link = "published_record:links:self" + self_html_link = "published_record:links:self_html" + def apply(self, identity, request_type, topic, uow, *args, **kwargs): topic_service = get_record_service_for_record(topic) if not topic_service: @@ -14,20 +17,6 @@ def apply(self, identity, request_type, topic, uow, *args, **kwargs): identity, id_, uow=uow, expand=False, *args, **kwargs ) - # add links to the published record - published_topic_dict = published_topic.to_dict() - - if "payload" not in self.request: - self.request["payload"] = {} - - # invenio does not allow non-string values in the payload, so using colon notation here - # client will need to handle this and convert to links structure - # can not use dot notation as marshmallow tries to be too smart and does not serialize dotted keys - self.request["payload"]["published_record:links:self"] = published_topic_dict[ - "links" - ]["self"] - self.request["payload"]["published_record:links:self_html"] = ( - published_topic_dict["links"]["self_html"] + return super().apply( + identity, request_type, published_topic, uow, *args, **kwargs ) - - return published_topic._record diff --git a/tests/test_requests/test_edit.py b/tests/test_requests/test_edit.py index 4c0af58a..d3c7a8cb 100644 --- a/tests/test_requests/test_edit.py +++ b/tests/test_requests/test_edit.py @@ -16,6 +16,7 @@ def test_edit_autoaccept( creator_client = logged_client(creator) record1 = record_factory(creator.identity) + id_ = record1["id"] resp_request_create = creator_client.post( urls["BASE_URL_REQUESTS"], @@ -33,6 +34,9 @@ def test_edit_autoaccept( assert not request["is_open"] assert request["is_closed"] + assert "draft_record:links:self" in request["payload"] + assert "draft_record:links:self_html" in request["payload"] + ThesisRecord.index.refresh() ThesisDraft.index.refresh() # edit action worked? @@ -43,3 +47,4 @@ def test_edit_autoaccept( ]["hits"] assert len(search) == 1 assert search[0]["links"]["self"].endswith("/draft") + assert search[0]["id"] == id_ From 7e4245cd9ecb5f8f3b7164250498b8a12603eb65 Mon Sep 17 00:00:00 2001 From: Ronald Krist Date: Mon, 26 Aug 2024 13:37:08 +0200 Subject: [PATCH 3/3] format --- oarepo_requests/types/edit_record.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index f94530fe..a2dc0fc2 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -1,3 +1,4 @@ +import marshmallow as ma from oarepo_runtime.i18n import lazy_gettext as _ from oarepo_requests.actions.edit_topic import EditTopicAcceptAction @@ -5,8 +6,6 @@ from .generic import NonDuplicableOARepoRequestType from .ref_types import ModelRefTypes -import marshmallow as ma - class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): type_id = "edit_published_record"