Skip to content

Commit

Permalink
Merge pull request #68 from oarepo/krist/be-475-delete-draft
Browse files Browse the repository at this point in the history
Krist/be 475 delete draft
  • Loading branch information
mesemus authored Oct 16, 2024
2 parents f658d32 + 895ecb4 commit 72a1874
Show file tree
Hide file tree
Showing 13 changed files with 237 additions and 49 deletions.
16 changes: 11 additions & 5 deletions oarepo_requests/actions/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,19 @@ class WorkflowTransitionComponent(RequestActionComponent):
@contextlib.contextmanager
def apply(self, identity, request_type, action, topic, uow, *args, **kwargs):
from oarepo_workflows.proxies import current_oarepo_workflows
from sqlalchemy.exc import NoResultFound

yield
transitions = (
current_oarepo_workflows.get_workflow(topic)
.requests()[request_type.type_id]
.transitions
)
try:
transitions = (
current_oarepo_workflows.get_workflow(topic)
.requests()[request_type.type_id]
.transitions
)
except (
NoResultFound
): # parent might be deleted - this is the case for delete_draft request type
return
target_state = transitions[action.status_to]
if (
target_state and not topic.model.is_deleted
Expand Down
11 changes: 11 additions & 0 deletions oarepo_requests/actions/delete_draft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from oarepo_runtime.datastreams.utils import get_record_service_for_record

from .generic import OARepoAcceptAction


class DeleteDraftAcceptAction(OARepoAcceptAction):
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")
topic_service.delete_draft(identity, topic["id"], uow=uow, *args, **kwargs)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from .generic import OARepoAcceptAction


class DeleteTopicAcceptAction(OARepoAcceptAction):
class DeletePublishedRecordAcceptAction(OARepoAcceptAction):
def apply(self, identity, request_type, topic, uow, *args, **kwargs):
topic_service = get_record_service_for_record(topic)
if not topic_service:
Expand Down
2 changes: 1 addition & 1 deletion oarepo_requests/actions/publish_draft.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def can_execute(self):
return False
try:
topic = self.request.topic.resolve()
except: # noqa: used for links, so ignore errors here
except: # noqa: used for links, so ignore errors here
return False
topic_service = get_record_service_for_record(topic)
try:
Expand Down
13 changes: 8 additions & 5 deletions oarepo_requests/services/permissions/generators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from flask_principal import Identity
from invenio_records_permissions.generators import ConditionalGenerator, Generator
from invenio_records_resources.references.entity_resolvers import EntityProxy
from invenio_requests.proxies import current_requests
from invenio_requests.resolvers.registry import ResolverRegistry
from invenio_search.engine import dsl
from oarepo_runtime.datastreams.utils import get_record_service_for_record
from oarepo_workflows.requests.policy import RecipientGeneratorMixin
Expand Down Expand Up @@ -30,6 +30,12 @@ def _condition(self, request_type, **kwargs):
return request_type.type_id in self.request_types


class IfEventOnRequestType(IfRequestType):

def _condition(self, request, **kwargs):
return request.type.type_id in self.request_types


class IfEventType(ConditionalGenerator):
def __init__(self, event_types, then_, else_=None):
else_ = [] if else_ is None else else_
Expand Down Expand Up @@ -142,10 +148,7 @@ def _condition(self, *, request_type, creator, **kwargs):
needs = creator.provides
else:
if not isinstance(creator, EntityProxy):
# convert to entityproxy
creator = current_requests.entity_resolvers_registry.reference_entity(
creator
)
creator = ResolverRegistry.reference_entity(creator)
needs = creator.get_needs()

for condition in self.requesters:
Expand Down
2 changes: 1 addition & 1 deletion oarepo_requests/types/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .delete_record import DeletePublishedRecordRequestType
from .delete_published_record import DeletePublishedRecordRequestType
from .edit_record import EditPublishedRecordRequestType
from .generic import NonDuplicableOARepoRequestType
from .publish_draft import PublishDraftRequestType
Expand Down
62 changes: 62 additions & 0 deletions oarepo_requests/types/delete_draft.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from oarepo_runtime.i18n import lazy_gettext as _
from typing_extensions import override

from ..actions.delete_draft import DeleteDraftAcceptAction
from ..utils import is_auto_approved, request_identity_matches
from .generic import NonDuplicableOARepoRequestType
from .ref_types import ModelRefTypes


class DeleteDraftRequestType(NonDuplicableOARepoRequestType):
type_id = "delete_draft"
name = _("Delete draft")

dangerous = True

@classmethod
@property
def available_actions(cls):
return {
**super().available_actions,
"accept": DeleteDraftAcceptAction,
}

description = _("Request deletion of draft")
receiver_can_be_none = True
allowed_topic_ref_types = ModelRefTypes(published=False, draft=True)

@override
def stateful_name(self, identity, *, topic=None, request=None):
if is_auto_approved(self, identity=identity, topic=topic):
return self.name
if not request:
return _("Request draft deletion")
match request.status:
case "submitted":
return _("Draft deletion requested")
case _:
return _("Request draft deletion")

@override
def stateful_description(self, identity, *, topic=None, request=None):
if is_auto_approved(self, identity=identity, topic=topic):
return _("Click to permanently delete the draft.")

if not request:
return _("Request permission to delete the draft.")
match request.status:
case "submitted":
if request_identity_matches(request.created_by, identity):
return _(
"Permission to delete draft requested. "
"You will be notified about the decision by email."
)
if request_identity_matches(request.receiver, identity):
return _(
"You have been asked to approve the request to permanently delete the draft. "
"You can approve or reject the request."
)
return _("Permission to delete draft (including files) requested. ")
case _:
if request_identity_matches(request.created_by, identity):
return _("Submit request to get permission to delete the draft.")
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from oarepo_runtime.i18n import lazy_gettext as _
from typing_extensions import override

from oarepo_requests.actions.delete_topic import DeleteTopicAcceptAction
from oarepo_requests.actions.delete_published_record import (
DeletePublishedRecordAcceptAction,
)

from ..utils import is_auto_approved, request_identity_matches
from .generic import NonDuplicableOARepoRequestType
Expand All @@ -19,7 +21,7 @@ class DeletePublishedRecordRequestType(NonDuplicableOARepoRequestType):
def available_actions(cls):
return {
**super().available_actions,
"accept": DeleteTopicAcceptAction,
"accept": DeletePublishedRecordAcceptAction,
}

description = _("Request deletion of published record")
Expand Down
3 changes: 2 additions & 1 deletion oarepo_requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ def get_receiver_for_request_type(request_type, identity, topic):
except KeyError:
return None


receivers = workflow_request.reference_receivers(
identity=identity, topic=topic, request_type=request_type
identity=identity, record=topic, request_type=request_type, creator=identity
)
if not receivers:
return None
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ invenio_assets.webpack =
invenio_i18n.translations =
oarepo_requests_ui = oarepo_requests
invenio_requests.types =
delete_published_record = oarepo_requests.types.delete_record:DeletePublishedRecordRequestType
delete_published_record = oarepo_requests.types.delete_published_record:DeletePublishedRecordRequestType
delete_draft = oarepo_requests.types.delete_draft:DeleteDraftRequestType
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
Expand Down
44 changes: 44 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class DefaultRequests(WorkflowRequestPolicy):
submitted="deleting", accepted="deleted", declined="published"
),
)
delete_draft = WorkflowRequest(
requesters=[IfInState("draft", [RecordOwners()])],
recipients=[AutoApprove()],
transitions=WorkflowTransitions(),
)
edit_published_record = WorkflowRequest(
requesters=[IfNoEditDraft([IfInState("published", [RecordOwners()])])],
recipients=[AutoApprove()],
Expand Down Expand Up @@ -346,6 +351,17 @@ def ret_data(record_id):
return ret_data


@pytest.fixture()
def delete_draft_function():
def ret_data(record_id):
return {
"request_type": "delete_draft",
"topic": {"thesis_draft": record_id},
}

return ret_data


@pytest.fixture()
def serialization_result():
def _result(topic_id, request_id):
Expand Down Expand Up @@ -660,3 +676,31 @@ def role_ui_serialization():
@pytest.fixture()
def default_workflow_json():
return {"parent": {"workflow": "default"}, "metadata": {"title": "blabla"}}


@pytest.fixture()
def get_request_type():
"""
gets request create link from serialized request types
"""

def _get_request_type(request_types_json, request_type):
selected_entry = [
entry for entry in request_types_json if entry["type_id"] == request_type
][0]
return selected_entry

return _get_request_type


@pytest.fixture()
def get_request_link(get_request_type):
"""
gets request create link from serialized request types
"""

def _create_request_from_link(request_types_json, request_type):
selected_entry = get_request_type(request_types_json, request_type)
return selected_entry["links"]["actions"]["create"]

return _create_request_from_link
86 changes: 54 additions & 32 deletions tests/test_requests/test_allowed_request_types_link_and_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,26 @@ def test_allowed_request_types_on_draft_service(
creator.identity, draft1.json["id"]
)
)
assert allowed_request_types.to_dict() == {
"hits": {
"hits": [
{
"links": {
"actions": {
"create": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/publish_draft'
}
},
"type_id": "publish_draft",
assert sorted(
allowed_request_types.to_dict()["hits"]["hits"], key=lambda x: x["type_id"]
) == [
{
"links": {
"actions": {
"create": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/delete_draft'
}
],
"total": 1,
},
"type_id": "delete_draft",
},
"links": {
"self": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/applicable'
{
"links": {
"actions": {
"create": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/publish_draft'
}
},
"type_id": "publish_draft",
},
}
]


def test_allowed_request_types_on_draft_resource(
Expand All @@ -72,24 +74,26 @@ def test_allowed_request_types_on_draft_resource(
allowed_request_types = creator_client.get(
link_api2testclient(applicable_requests_link)
)
assert allowed_request_types.json == {
"hits": {
"hits": [
{
"links": {
"actions": {
"create": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/publish_draft'
}
},
"type_id": "publish_draft",
assert sorted(
allowed_request_types.json["hits"]["hits"], key=lambda x: x["type_id"]
) == [
{
"links": {
"actions": {
"create": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/delete_draft'
}
],
"total": 1,
},
"type_id": "delete_draft",
},
"links": {
"self": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/applicable'
{
"links": {
"actions": {
"create": f'https://127.0.0.1:5000/api/thesis/{draft1.json["id"]}/draft/requests/publish_draft'
}
},
"type_id": "publish_draft",
},
}
]


def publish_record(
Expand Down Expand Up @@ -223,7 +227,25 @@ def test_ui_serialization(
headers={"Accept": "application/vnd.inveniordm.v1+json"},
)

assert allowed_request_types_draft.json["hits"]["hits"] == [
sorted_draft_list = allowed_request_types_draft.json["hits"]["hits"]
sorted_draft_list.sort(key=lambda serialized_rt: serialized_rt["type_id"])

assert sorted_draft_list == [
{
"dangerous": True,
"description": "Request deletion of draft",
"editable": True,
"has_form": False,
"links": {
"actions": {
"create": f"https://127.0.0.1:5000/api/thesis/{draft_id}/draft/requests/delete_draft"
}
},
"name": "Delete draft",
"stateful_description": "Click to permanently delete the draft.",
"stateful_name": "Delete draft",
"type_id": "delete_draft",
},
{
"description": "Request publishing of a draft",
"links": {
Expand All @@ -242,7 +264,7 @@ def test_ui_serialization(
"possible until the request is accepted or declined. "
"You will be notified about the decision by email.",
"stateful_name": "Submit for review",
}
},
]
sorted_published_list = allowed_request_types_published.json["hits"]["hits"]
sorted_published_list.sort(key=lambda serialized_rt: serialized_rt["type_id"])
Expand Down
Loading

0 comments on commit 72a1874

Please sign in to comment.