Skip to content

Commit

Permalink
Merge pull request #40 from oarepo/miroslavsimek/be-397-add-ifrequest…
Browse files Browse the repository at this point in the history
…edby-generator-to-oarepo_requests

Adding IfRequestedBy
  • Loading branch information
mesemus authored Aug 12, 2024
2 parents 6fa74c4 + d996a7c commit d6e9003
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 76 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,5 @@ node_modules

dummy-record.js
jsconfig.json

forked_install.sh
11 changes: 11 additions & 0 deletions oarepo_requests/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,14 @@ def __init__(self, request_type):
def description(self):
"""Exception's description."""
return f"Unknown request type {self.request_type}."


class RequestTypeNotInWorkflow(Exception):
def __init__(self, request_type, workflow):
self.request_type = request_type
self.workflow = workflow

@property
def description(self):
"""Exception's description."""
return f"Request type {self.request_type} not in workflow {self.workflow}."
19 changes: 13 additions & 6 deletions oarepo_requests/receiver.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
from oarepo_requests.errors import RequestTypeNotInWorkflow


def default_workflow_receiver_function(record=None, request_type=None, **kwargs):
from oarepo_workflows.proxies import current_oarepo_workflows

workflow_id = current_oarepo_workflows.get_workflow_from_record(record)
if not workflow_id:
return None

try:

request = getattr(
current_oarepo_workflows.record_workflows[workflow_id].requests(),
request_type.type_id,
)
receiver = request.reference_receivers(
record=record, request_type=request_type, **kwargs
)
return receiver
except (KeyError, AttributeError):
return None
except AttributeError:
raise RequestTypeNotInWorkflow(request_type.type_id, workflow_id)

receiver = request.reference_receivers(
record=record, request_type=request_type, **kwargs
)
return receiver
3 changes: 2 additions & 1 deletion oarepo_requests/services/oarepo/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ def create(
raise UnknownRequestType(request_type)

if receiver is None:
# if explicit creator is not passed, use current identity - this is in sync with invenio_requests
receiver = current_oarepo_requests.default_request_receiver(
identity, type_, topic, creator, data
identity, type_, topic, creator or identity, data
)

if data is None:
Expand Down
77 changes: 46 additions & 31 deletions oarepo_requests/services/permissions/generators.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from invenio_records_permissions.generators import Generator
from invenio_records_permissions.generators import Generator, ConditionalGenerator
from invenio_search.engine import dsl

from oarepo_requests.services.permissions.identity import request_active
from oarepo_workflows.requests.policy import RecipientGeneratorMixin
from flask_principal import Identity

from invenio_requests.proxies import current_requests
from invenio_records_resources.references.entity_resolvers import EntityProxy


class RequestActive(Generator):
Expand Down Expand Up @@ -57,33 +62,43 @@ def query_filter(self, record=None, request_type=None, **kwargs):
pass


"""
#if needed, have to implement filter
class RecordRequestsReceivers(Generator):
def needs(self, record=None, **kwargs):
service = get_requests_service_for_records_service(
get_record_service_for_record(record)
)
reader = (
service.search_requests_for_draft
if getattr(record, "is_draft", False)
else service.search_requests_for_record
)
requests = list(reader(system_identity, record["id"]).hits)
needs = set()
for request in requests:
request_type_id = request["type"]
type_ = current_request_type_registry.lookup(request_type_id, quiet=True)
if not type_:
raise UnknownRequestType(request_type_id)
workflow_request = current_oarepo_workflows.get_workflow(record).requests()[
request_type_id
]
request_needs = {
need
for generator in workflow_request.recipients
for need in generator.needs(**kwargs)
}
needs |= request_needs
return needs
"""
class IfRequestedBy(RecipientGeneratorMixin, ConditionalGenerator):

def __init__(self, requesters, then_, else_):
super().__init__(then_, else_)
if not isinstance(requesters, (list, tuple)):
requesters = [requesters]
self.requesters = requesters

def _condition(self, *, request_type, creator, **kwargs):
"""Condition to choose generators set."""
# get needs
if isinstance(creator, Identity):
needs = creator.provides
else:
if not isinstance(creator, EntityProxy):
# convert to entityproxy
creator = current_requests.entity_resolvers_registry.reference_entity(creator)
needs = creator.get_needs()

for condition in self.requesters:
condition_needs = set(condition.needs(request_type=request_type, creator=creator, **kwargs))
condition_excludes = set(condition.excludes(request_type=request_type, creator=creator, **kwargs))

if not condition_needs.intersection(needs):
continue
if condition_excludes and condition_excludes.intersection(needs):
continue
return True
return False

def reference_receivers(self, record=None, request_type=None, **kwargs):
ret = []
for gen in self._generators(record=record, request_type=request_type, **kwargs):
if isinstance(gen, RecipientGeneratorMixin):
ret.extend(gen.reference_receivers(record=record, request_type=request_type, **kwargs))
return ret

def query_filter(self, **kwargs):
"""Search filters."""
raise NotImplementedError("Please use IfRequestedBy only in recipients, not elsewhere.")
10 changes: 8 additions & 2 deletions oarepo_requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
def allowed_request_types_for_record(record):
try:
from oarepo_workflows.proxies import current_oarepo_workflows

workflow_requests = current_oarepo_workflows.get_workflow(record).requests()
from oarepo_workflows.errors import MissingWorkflowError
try:
workflow_requests = current_oarepo_workflows.get_workflow(record).requests()
except MissingWorkflowError:
# workflow not defined on the record, probably not a workflow-enabled record
# so returning all matching request types
# TODO: is this correct?
workflow_requests = None
except ImportError:
workflow_requests = None
request_types = current_request_type_registry._registered_types
Expand Down
10 changes: 8 additions & 2 deletions run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,21 @@ BUILDER_VENV=.venv-builder
BUILD_TEST_DIR="tests"
CODE_TEST_DIR="tests"

curl -L -o forked_install.sh https://github.com/oarepo/nrp-devtools/raw/main/tests/forked_install.sh

if test -d $BUILDER_VENV ; then
rm -rf $BUILDER_VENV
fi

python3 -m venv $BUILDER_VENV
. $BUILDER_VENV/bin/activate
pip install -U setuptools pip wheel
pip install -U oarepo-model-builder-tests oarepo-model-builder-requests oarepo-model-builder-drafts
curl -L -o forked_install.sh https://github.com/oarepo/nrp-devtools/raw/main/tests/forked_install.sh
pip install -U oarepo-model-builder \
oarepo-model-builder-tests \
oarepo-model-builder-requests \
oarepo-model-builder-drafts \
oarepo-model-builder-workflows

if test -d ./$BUILD_TEST_DIR/$MODEL; then
rm -rf ./$BUILD_TEST_DIR/$MODEL
fi
Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = oarepo-requests
version = 2.0.1
version = 2.0.2
description =
authors = Ronald Krist <[email protected]>
readme = README.md
Expand Down
59 changes: 56 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
)
from oarepo_workflows.base import Workflow
from oarepo_workflows.requests import RecipientGeneratorMixin

from oarepo_requests.services.permissions.generators import IfRequestedBy
from thesis.proxies import current_service
from thesis.records.api import ThesisDraft

Expand Down Expand Up @@ -70,6 +72,17 @@ class DefaultRequests(WorkflowRequestPolicy):
)


class UserGenerator(RecipientGeneratorMixin, Generator):
def __init__(self, user_id):
self.user_id = user_id

def needs(self, **kwargs):
return [UserNeed(self.user_id)]

def reference_receivers(self, **kwargs):
return [{"user": str(self.user_id)}]


class RequestsWithApprove(WorkflowRequestPolicy):
publish_draft = WorkflowRequest(
requesters=[IfInState("approved", [AutoRequest()])],
Expand Down Expand Up @@ -99,6 +112,17 @@ class RequestsWithApprove(WorkflowRequestPolicy):
)


class RequestsWithCT(WorkflowRequestPolicy):
conditional_recipient_rt = WorkflowRequest(
requesters=[AnyUser()],
recipients=[IfRequestedBy(
UserGenerator(1),
[UserGenerator(2)],
[UserGenerator(3)]
)],
)


class ApproveRequestType(NonDuplicableOARepoRequestType):
type_id = "approve_draft"
name = _("Approve draft")
Expand All @@ -114,6 +138,21 @@ class ApproveRequestType(NonDuplicableOARepoRequestType):
allowed_topic_ref_types = ModelRefTypes(published=False, draft=True)


class ConditionalRecipientRequestType(NonDuplicableOARepoRequestType):
type_id = "conditional_recipient_rt"
name = _("Request type to test conditional recipients")

available_actions = {
**NonDuplicableOARepoRequestType.available_actions,
"accept": OARepoAcceptAction,
"submit": OARepoSubmitAction,
"decline": OARepoDeclineAction,
}
description = _("A no-op request to check conditional recipients")
receiver_can_be_none = False
allowed_topic_ref_types = ModelRefTypes(published=False, draft=True)


class TestWorkflowPolicy(DefaultWithRequestsWorkflowPermissionPolicy):
can_read = [
IfInState("draft", [RecordOwners()]),
Expand Down Expand Up @@ -144,6 +183,11 @@ class WithApprovalPermissionPolicy(DefaultWithRequestsWorkflowPermissionPolicy):
permission_policy_cls=WithApprovalPermissionPolicy,
request_policy_cls=RequestsWithApprove,
),
"with_ct": Workflow(
label=_("Workflow with approval process"),
permission_policy_cls=WithApprovalPermissionPolicy,
request_policy_cls=RequestsWithCT,
),
}


Expand Down Expand Up @@ -183,6 +227,15 @@ def ret_data(record_id):

return ret_data

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

return ret_data

@pytest.fixture()
def edit_record_data_function():
Expand Down Expand Up @@ -305,7 +358,7 @@ def app_config(app_config):
app_config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] = default_workflow_receiver_function

app_config["WORKFLOWS"] = WORKFLOWS
app_config["REQUESTS_REGISTERED_TYPES"] = [ApproveRequestType()]
app_config["REQUESTS_REGISTERED_TYPES"] = [ApproveRequestType(), ConditionalRecipientRequestType()]
return app_config


Expand Down Expand Up @@ -461,7 +514,7 @@ def _create_draft(
):
json = copy.deepcopy(default_workflow_json)
if custom_workflow:
json["parent"]["workflow_id"] = custom_workflow
json["parent"]["workflow"] = custom_workflow
if additional_data:
json |= additional_data
url = urls["BASE_URL"] + "?expand=true" if expand else urls["BASE_URL"]
Expand Down Expand Up @@ -515,4 +568,4 @@ def role_ui_serialization():

@pytest.fixture()
def default_workflow_json():
return {"parent": {"workflow_id": "default"}}
return {"parent": {"workflow": "default"}}
59 changes: 59 additions & 0 deletions tests/test_requests/test_conditional_recipient.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import pytest

from oarepo_requests.errors import OpenRequestAlreadyExists

from .utils import link_api2testclient


def test_conditional_receiver_creator_matches(
logged_client,
users,
urls,
conditional_recipient_request_data_function,
create_draft_via_resource,
search_clear,
):
# user[0] is creator, user[1] is receiver
# user[0] is not a creator, user[2] is receiver

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=conditional_recipient_request_data_function(draft1.json["id"]),
)

assert resp_request_create.status_code == 201
assert resp_request_create.json["receiver"] == {"user": '2'}


def test_conditional_receiver_creator_does_not_match(
logged_client,
users,
urls,
conditional_recipient_request_data_function,
create_draft_via_resource,
search_clear,
):
# user[0] is creator, user[1] is receiver
# user[0] is not a creator, user[2] is receiver

creator = users[1]
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=conditional_recipient_request_data_function(draft1.json["id"]),
)

assert resp_request_create.status_code == 201
assert resp_request_create.json["receiver"] == {"user": '3'}
Loading

0 comments on commit d6e9003

Please sign in to comment.