diff --git a/oarepo_requests/actions/publish_draft.py b/oarepo_requests/actions/publish_draft.py index 48526455..9fd7d33b 100644 --- a/oarepo_requests/actions/publish_draft.py +++ b/oarepo_requests/actions/publish_draft.py @@ -1,6 +1,7 @@ -from ..utils import get_matching_service_for_record from invenio_requests.customizations import actions +from ..utils import get_matching_service_for_record + def publish_draft(draft, identity, uow, *args, **kwargs): topic_service = get_matching_service_for_record(draft) diff --git a/oarepo_requests/config.py b/oarepo_requests/config.py index d391b6ca..2786b7ff 100644 --- a/oarepo_requests/config.py +++ b/oarepo_requests/config.py @@ -1,8 +1,15 @@ -from oarepo_requests.resolvers.ui import UserEntityReferenceUIResolver, FallbackEntityReferenceUIResolver, \ - GroupEntityReferenceUIResolver -from oarepo_requests.types import DeletePublishedRecordRequestType, EditPublishedRecordRequestType, PublishDraftRequestType -from invenio_users_resources.entity_resolvers import UserResolver, GroupResolver +from invenio_users_resources.entity_resolvers import GroupResolver, UserResolver +from oarepo_requests.resolvers.ui import ( + FallbackEntityReferenceUIResolver, + GroupEntityReferenceUIResolver, + UserEntityReferenceUIResolver, +) +from oarepo_requests.types import ( + DeletePublishedRecordRequestType, + EditPublishedRecordRequestType, + PublishDraftRequestType, +) REQUESTS_REGISTERED_TYPES = [ DeletePublishedRecordRequestType(), @@ -10,9 +17,7 @@ PublishDraftRequestType(), ] -REQUESTS_ALLOWED_RECEIVERS = [ - "user", "role" -] +REQUESTS_ALLOWED_RECEIVERS = ["user", "group"] REQUESTS_ENTITY_RESOLVERS = [ UserResolver(), @@ -25,4 +30,4 @@ "group": GroupEntityReferenceUIResolver("group"), } -REQUESTS_UI_SERIALIZATION_REFERENCED_FIELDS = ["created_by", "receiver", "topic"] \ No newline at end of file +REQUESTS_UI_SERIALIZATION_REFERENCED_FIELDS = ["created_by", "receiver", "topic"] diff --git a/oarepo_requests/ext.py b/oarepo_requests/ext.py index c8058358..6fa9316a 100644 --- a/oarepo_requests/ext.py +++ b/oarepo_requests/ext.py @@ -39,13 +39,23 @@ def default_request_receiver(self, identity, request_type, topic, creator, data) # otherwise use the default receiver return obj_or_import_string( self.app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] - )(identity=identity, request_type=request_type, topic=topic, creator=creator, data=data) + )( + identity=identity, + request_type=request_type, + topic=topic, + creator=creator, + data=data, + ) @cached_property def allowed_topic_ref_types(self): entity_resolvers = self.app.config.get("REQUESTS_ENTITY_RESOLVERS", []) return {x.type_key for x in entity_resolvers} + @cached_property + def requests_entity_resolvers(self): + return self.app.config.get("REQUESTS_ENTITY_RESOLVERS", []) + @property def allowed_receiver_ref_types(self): return self.app.config.get("REQUESTS_ALLOWED_RECEIVERS", []) @@ -80,10 +90,19 @@ def init_config(self, app): """Initialize configuration.""" from . import config - app.config.setdefault("REQUESTS_REGISTERED_TYPES", []).extend(config.REQUESTS_REGISTERED_TYPES) - app.config.setdefault("REQUESTS_ALLOWED_RECEIVERS", []).extend(config.REQUESTS_ALLOWED_RECEIVERS) - app.config.setdefault("REQUESTS_ENTITY_RESOLVERS", []).extend(config.REQUESTS_ENTITY_RESOLVERS) - app.config.setdefault("ENTITY_REFERENCE_UI_RESOLVERS", {}).update(config.ENTITY_REFERENCE_UI_RESOLVERS) + + app.config.setdefault("REQUESTS_REGISTERED_TYPES", []).extend( + config.REQUESTS_REGISTERED_TYPES + ) + app.config.setdefault("REQUESTS_ALLOWED_RECEIVERS", []).extend( + config.REQUESTS_ALLOWED_RECEIVERS + ) + app.config.setdefault("REQUESTS_ENTITY_RESOLVERS", []).extend( + config.REQUESTS_ENTITY_RESOLVERS + ) + app.config.setdefault("ENTITY_REFERENCE_UI_RESOLVERS", {}).update( + config.ENTITY_REFERENCE_UI_RESOLVERS + ) app.config.setdefault("REQUESTS_UI_SERIALIZATION_REFERENCED_FIELDS", []).extend( config.REQUESTS_UI_SERIALIZATION_REFERENCED_FIELDS ) diff --git a/oarepo_requests/invenio_patches.py b/oarepo_requests/invenio_patches.py index 01bfb18f..a5202144 100644 --- a/oarepo_requests/invenio_patches.py +++ b/oarepo_requests/invenio_patches.py @@ -28,7 +28,7 @@ def apply(self, identity, search, params): class RequestReceiverFilterParam(FilterParam): def apply(self, identity, search, params): value = params.pop(self.param_name, None) - my_groups = [n.value for n in identity.provides if n.method == "role"] + my_roles = [n.value for n in identity.provides if n.method == "role"] if value is not None: search = search.filter( Bool( @@ -37,8 +37,8 @@ def apply(self, identity, search, params): Term(**{f"{self.field_name}.user": identity.id}), # my roles *[ - Term(**{f"{self.field_name}.group": group_id}) - for group_id in my_groups + Term(**{f"{self.field_name}.group": role_id}) + for role_id in my_roles ], # TODO: add my communities where I have a role to accept requests ], diff --git a/oarepo_requests/services/results.py b/oarepo_requests/services/results.py index 4bfb9dc5..d44f8e8c 100644 --- a/oarepo_requests/services/results.py +++ b/oarepo_requests/services/results.py @@ -11,6 +11,8 @@ class RequestTypesComponent(ResultsComponent): def update_data(self, identity, record, projection, expand): + if not expand: + return request_types_list = [] allowed_request_types = allowed_request_types_for_record(record) for request_name, request_type in allowed_request_types.items(): @@ -28,22 +30,24 @@ def update_data(self, identity, record, projection, expand): ) request_type_link = data request_types_list.append(request_type_link) - projection["request_types"] = request_types_list + projection["expanded"]["request_types"] = request_types_list class RequestsComponent(ResultsComponent): def update_data(self, identity, record, projection, expand): - if expand: - service = get_requests_service_for_records_service( - get_matching_service_for_record(record) - ) - reader = ( - service.search_requests_for_draft - if getattr(record, "is_draft", False) - else service.search_requests_for_record - ) - try: - requests = list(reader(identity, record["id"]).hits) - except PermissionDeniedError: - requests = [] - projection["requests"] = requests + if not expand: + return + + service = get_requests_service_for_records_service( + get_matching_service_for_record(record) + ) + reader = ( + service.search_requests_for_draft + if getattr(record, "is_draft", False) + else service.search_requests_for_record + ) + try: + requests = list(reader(identity, record["id"]).hits) + except PermissionDeniedError: + requests = [] + projection["expanded"]["requests"] = requests diff --git a/oarepo_requests/services/ui_schema.py b/oarepo_requests/services/ui_schema.py index 76b1020e..cb9ea35f 100644 --- a/oarepo_requests/services/ui_schema.py +++ b/oarepo_requests/services/ui_schema.py @@ -13,7 +13,6 @@ from oarepo_requests.resolvers.ui import resolve from oarepo_requests.services.schema import ( NoneReceiverGenericRequestSchema, - RequestsSchemaMixin, RequestTypeSchema, get_links_schema, ) @@ -98,9 +97,22 @@ def add_type_details(self, data, **kwargs): return data -class UIRequestsSerializationMixin(RequestsSchemaMixin): - requests = ma.fields.List(ma.fields.Nested(UIBaseRequestSchema)) - request_types = ma.fields.List(ma.fields.Nested(UIRequestTypeSchema)) +class UIRequestsSerializationMixin(ma.Schema): + + @ma.post_dump() + def add_request_types(self, data, **kwargs): + expanded = data.get("expanded", {}) + if not expanded: + return data + if "request_types" in expanded: + expanded["request_types"] = UIRequestTypeSchema(context=self.context).dump( + expanded["request_types"], many=True + ) + if "requests" in expanded: + expanded["requests"] = UIBaseRequestSchema(context=self.context).dump( + expanded["requests"], many=True + ) + return data class UIBaseRequestEventSchema(BaseRecordSchema): diff --git a/oarepo_requests/types/__init__.py b/oarepo_requests/types/__init__.py index f92c08f5..86eb7fca 100644 --- a/oarepo_requests/types/__init__.py +++ b/oarepo_requests/types/__init__.py @@ -1,14 +1,14 @@ -from .ref_types import ModelRefTypes, ReceiverRefTypes from .delete_record import DeletePublishedRecordRequestType from .edit_record import EditPublishedRecordRequestType -from .publish_draft import PublishDraftRequestType from .generic import NonDuplicableOARepoRequestType +from .publish_draft import PublishDraftRequestType +from .ref_types import ModelRefTypes, ReceiverRefTypes __all__ = [ - 'ModelRefTypes', - 'ReceiverRefTypes', - 'DeletePublishedRecordRequestType', - 'EditPublishedRecordRequestType', - 'PublishDraftRequestType', - 'NonDuplicableOARepoRequestType', -] \ No newline at end of file + "ModelRefTypes", + "ReceiverRefTypes", + "DeletePublishedRecordRequestType", + "EditPublishedRecordRequestType", + "PublishDraftRequestType", + "NonDuplicableOARepoRequestType", +] diff --git a/oarepo_requests/types/delete_record.py b/oarepo_requests/types/delete_record.py index 86e8ad46..fe266c49 100644 --- a/oarepo_requests/types/delete_record.py +++ b/oarepo_requests/types/delete_record.py @@ -3,11 +3,13 @@ from oarepo_requests.actions.delete_topic import DeleteTopicAcceptAction +from . import ModelRefTypes from .generic import NonDuplicableOARepoRequestType class DeletePublishedRecordRequestType(NonDuplicableOARepoRequestType): type_id = "delete-published-record" + name = _("Delete record") available_actions = { **RequestType.available_actions, @@ -15,3 +17,4 @@ class DeletePublishedRecordRequestType(NonDuplicableOARepoRequestType): } description = _("Request deletion of published record") receiver_can_be_none = True + allowed_topic_ref_types = ModelRefTypes(published=True, draft=False) diff --git a/oarepo_requests/types/edit_record.py b/oarepo_requests/types/edit_record.py index c7e1b4b7..ac1f4c39 100644 --- a/oarepo_requests/types/edit_record.py +++ b/oarepo_requests/types/edit_record.py @@ -4,11 +4,13 @@ from oarepo_requests.actions.edit_topic import EditTopicAcceptAction from oarepo_requests.actions.generic import AutoAcceptSubmitAction +from . import ModelRefTypes from .generic import NonDuplicableOARepoRequestType class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): type_id = "edit-published-record" + name = _("Edit record") available_actions = { **RequestType.available_actions, @@ -17,3 +19,4 @@ class EditPublishedRecordRequestType(NonDuplicableOARepoRequestType): } description = _("Request re-opening of published record") receiver_can_be_none = True + allowed_topic_ref_types = ModelRefTypes(published=True, draft=False) diff --git a/oarepo_requests/types/publish_draft.py b/oarepo_requests/types/publish_draft.py index 0ed74ddd..6454b1d5 100644 --- a/oarepo_requests/types/publish_draft.py +++ b/oarepo_requests/types/publish_draft.py @@ -3,11 +3,13 @@ from oarepo_requests.actions.publish_draft import PublishDraftAcceptAction +from . import ModelRefTypes from .generic import NonDuplicableOARepoRequestType class PublishDraftRequestType(NonDuplicableOARepoRequestType): type_id = "publish-draft" + name = _("Publish draft") available_actions = { **RequestType.available_actions, @@ -15,3 +17,4 @@ class PublishDraftRequestType(NonDuplicableOARepoRequestType): } description = _("Request publishing of a draft") receiver_can_be_none = True + allowed_topic_ref_types = ModelRefTypes(published=False, draft=True) diff --git a/oarepo_requests/types/ref_types.py b/oarepo_requests/types/ref_types.py index fff6913c..de50d992 100644 --- a/oarepo_requests/types/ref_types.py +++ b/oarepo_requests/types/ref_types.py @@ -1,3 +1,5 @@ +from invenio_records_resources.references import RecordResolver + from oarepo_requests.proxies import current_oarepo_requests @@ -7,9 +9,21 @@ class ModelRefTypes: The list of ref types is taken from the configuration (configuration key REQUESTS_ALLOWED_TOPICS). """ + def __init__(self, published=False, draft=False): + self.published = published + self.draft = draft + def __get__(self, obj, owner): """Property getter, returns the list of allowed reference types.""" - return current_oarepo_requests.allowed_topic_ref_types + ret = [] + for ref_type in current_oarepo_requests.requests_entity_resolvers: + if not isinstance(ref_type, RecordResolver): + continue + if self.published and not ref_type.record_cls.is_draft: + ret.append(ref_type.type_key) + elif self.draft and ref_type.record_cls.is_draft: + ret.append(ref_type.type_key) + return ret class ReceiverRefTypes: diff --git a/oarepo_requests/ui/resources/__init__.py b/oarepo_requests/ui/resources/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/oarepo_requests/ui/resources/components.py b/oarepo_requests/ui/resources/components.py deleted file mode 100644 index df1ac689..00000000 --- a/oarepo_requests/ui/resources/components.py +++ /dev/null @@ -1,59 +0,0 @@ -import copy - -from invenio_records_resources.services.errors import PermissionDeniedError -from invenio_requests import current_request_type_registry, current_requests_service -from invenio_requests.customizations import RequestActions -from oarepo_ui.resources.components import UIResourceComponent - - -# TODO deprecatated -class AllowedRequestsComponent(UIResourceComponent): - """Service component which sets all data in the record.""" - - def _add_available_requests(self, identity, record, dict_to_save_result, kwargs): - # todo discriminate requests from other stuff which can be on parent in the future - # todo what to throw if parent doesn't exist - - if not record: - return - - available_requests = {} - kwargs[dict_to_save_result]["allowed_requests"] = available_requests - - parent_copy = copy.deepcopy(record.data["parent"]) - requests = { - k: v - for k, v in parent_copy.items() - if isinstance(v, dict) and "receiver" in v - } # todo more sensible request identification - - for request_name, request_dict in requests.items(): - request = current_requests_service.record_cls.get_record(request_dict["id"]) - request_type = current_request_type_registry.lookup(request_dict["type"]) - for action_name, action in request_type.available_actions.items(): - try: - current_requests_service.require_permission( - identity, f"action_{action_name}", request=request - ) - except PermissionDeniedError: - continue - action = RequestActions.get_action(request, action_name) - if not action.can_execute(): - continue - if request_name not in available_requests: - saved_request_data = copy.deepcopy(request_dict) - saved_request_data["actions"] = [action_name] - available_requests[request_name] = saved_request_data - else: - saved_request_data["actions"].append( - action_name - ) # noqa we are sure that saved_request_data exists - - def before_ui_detail(self, identity, api_record=None, errors=None, **kwargs): - self._add_available_requests(identity, api_record, "extra_context", kwargs) - - def before_ui_edit(self, identity, api_record=None, errors=None, **kwargs): - self._add_available_requests(identity, api_record, "extra_context", kwargs) - - def form_config(self, identity, api_record=None, errors=None, **kwargs): - self._add_available_requests(identity, api_record, "form_config", kwargs) diff --git a/run-tests.sh b/run-tests.sh index ad5f714d..707bf288 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -38,6 +38,7 @@ pip install -U setuptools pip wheel pip install "oarepo[tests]==$OAREPO_VERSION.*" pip install -e "./$BUILD_TEST_DIR/${MODEL}" pip install oarepo-ui +pip install deepdiff pip install -e . # local override diff --git a/setup.cfg b/setup.cfg index 381831e1..ae2159f7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = oarepo-requests -version = 1.1.19 +version = 1.2.0 description = authors = Ronald Krist readme = README.md diff --git a/tests/conftest.py b/tests/conftest.py index e36e3202..ccef32ea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -36,7 +36,7 @@ def urls(): def publish_request_data_function(): def ret_data(record_id): return { - "request_type": "thesis_publish_draft", + "request_type": "publish-draft", "topic": {"thesis_draft": record_id}, } @@ -47,7 +47,7 @@ def ret_data(record_id): def edit_record_data_function(): def ret_data(record_id): return { - "request_type": "thesis_edit_record", + "request_type": "edit-published-record", "topic": {"thesis": record_id}, } @@ -58,7 +58,7 @@ def ret_data(record_id): def delete_record_data_function(): def ret_data(record_id): return { - "request_type": "thesis_delete_record", + "request_type": "delete-published-record", "topic": {"thesis": record_id}, } @@ -80,7 +80,7 @@ def _result(topic_id, request_id): "timeline": f"https://127.0.0.1:5000/api/requests/extended/{request_id}/timeline", }, "revision_id": 3, - "type": "thesis_publish_draft", + "type": "publish-draft", "title": "", "number": "1", "status": "submitted", @@ -136,7 +136,7 @@ def _result(topic_id, request_id): "reference": {"thesis_draft": topic_id}, "type": "thesis_draft", }, - "type": "thesis_publish_draft", + "type": "publish-draft", # 'updated': '2024-01-26T10:06:18.084317' } @@ -161,18 +161,12 @@ def app_config(app_config): ) app_config["CACHE_TYPE"] = "SimpleCache" - def default_receiver(*args, **kwargs): - return {"user": "2"} - - def none_receiver(*args, **kwargs): + def default_receiver(*args, request_type=None, **kwargs): + if request_type != "edit-published-record": + return {"user": "2"} return None - app_config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] = { - "thesis_publish_draft": default_receiver, - "thesis_delete_record": default_receiver, - "thesis_non_duplicable": default_receiver, - "thesis_edit_record": none_receiver, - } + app_config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] = default_receiver """ app_config.setdefault("ENTITY_REFERENCE_UI_RESOLVERS", {}).update({ @@ -354,7 +348,7 @@ def events_resource_data(): from invenio_accounts.proxies import current_datastore -def _create_group(id, name, description, is_managed, database): +def _create_role(id, name, description, is_managed, database): """Creates a Role/Group.""" r = current_datastore.create_role( id=id, name=name, description=description, is_managed=is_managed @@ -364,9 +358,9 @@ def _create_group(id, name, description, is_managed, database): @pytest.fixture() -def group(database): +def role(database): """A single group.""" - r = _create_group( + r = _create_role( id="it-dep", name="it-dep", description="IT Department", @@ -377,7 +371,7 @@ def group(database): @pytest.fixture() -def group_ui_serialization(): +def role_ui_serialization(): return { "label": "it-dep", "links": {"self": "https://127.0.0.1:5000/api/groups/it-dep"}, diff --git a/tests/test_requests/test_create_conditions.py b/tests/test_requests/test_create_conditions.py index 1baa430a..ca758e81 100644 --- a/tests/test_requests/test_create_conditions.py +++ b/tests/test_requests/test_create_conditions.py @@ -59,7 +59,9 @@ def test_can_create( f"{urls['BASE_URL']}{draft2.json['id']}/draft?expand=true" ) decline = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["decline"]), + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] + ), ) resp_request_create_again = creator_client.post( @@ -86,7 +88,7 @@ def test_can_possibly_create( draft2 = creator_client.post(urls["BASE_URL"], json={}) record_resp_no_request = receiver_client.get( - f"{urls['BASE_URL']}{draft1.json['id']}/draft" + f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) resp_request_create = creator_client.post( urls["BASE_URL_REQUESTS"], @@ -104,20 +106,20 @@ def find_request_type(requests, type): return None record_resp_with_request = receiver_client.get( - f"{urls['BASE_URL']}{draft1.json['id']}/draft" + f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) record_resp_draft2 = receiver_client.get( - f"{urls['BASE_URL']}{draft2.json['id']}/draft" + f"{urls['BASE_URL']}{draft2.json['id']}/draft?expand=true" ) assert find_request_type( - record_resp_no_request.json["request_types"], "thesis_publish_draft" + record_resp_no_request.json["expanded"]["request_types"], "publish-draft" ) assert find_request_type( - record_resp_draft2.json["request_types"], "thesis_publish_draft" + record_resp_draft2.json["expanded"]["request_types"], "publish-draft" ) assert ( find_request_type( - record_resp_with_request.json["request_types"], "thesis_publish_draft" + record_resp_with_request.json["expanded"]["request_types"], "publish-draft" ) is None ) diff --git a/tests/test_requests/test_create_inmodel.py b/tests/test_requests/test_create_inmodel.py index e5e5b78c..828be4fb 100644 --- a/tests/test_requests/test_create_inmodel.py +++ b/tests/test_requests/test_create_inmodel.py @@ -24,12 +24,12 @@ def test_record( receiver_client = logged_client(receiver) record1 = record_factory(creator.identity) - record1 = creator_client.get(f"{urls['BASE_URL']}{record1['id']}") + record1 = creator_client.get(f"{urls['BASE_URL']}{record1['id']}?expand=true") link = link_api2testclient( - pick_request_type(record1.json["request_types"], "thesis_delete_record")[ - "links" - ]["actions"]["create"] + pick_request_type( + record1.json["expanded"]["request_types"], "delete-published-record" + )["links"]["actions"]["create"] ) resp_request_create = creator_client.post(link) @@ -40,7 +40,9 @@ def test_record( record = receiver_client.get(f"{urls['BASE_URL']}{record1.json['id']}?expand=true") delete = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["accept"]) + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] + ) ) ThesisRecord.index.refresh() lst = creator_client.get(urls["BASE_URL"]) @@ -60,9 +62,9 @@ def test_draft( creator_client = logged_client(creator) receiver_client = logged_client(receiver) - draft1 = creator_client.post(urls["BASE_URL"], json={}) + draft1 = creator_client.post(urls["BASE_URL"] + "?expand=true", json={}) link = link_api2testclient( - pick_request_type(draft1.json["request_types"], "thesis_publish_draft")[ + pick_request_type(draft1.json["expanded"]["request_types"], "publish-draft")[ "links" ]["actions"]["create"] ) @@ -76,7 +78,9 @@ def test_draft( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) delete = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["accept"]) + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] + ) ) ThesisRecord.index.refresh() lst = creator_client.get(urls["BASE_URL"]) diff --git a/tests/test_requests/test_delete.py b/tests/test_requests/test_delete.py index 105713be..bdf8edc4 100644 --- a/tests/test_requests/test_delete.py +++ b/tests/test_requests/test_delete.py @@ -35,12 +35,14 @@ def test_delete( print() record = receiver_client.get(f"{urls['BASE_URL']}{record1['id']}?expand=true") - assert record.json["requests"][0]["links"]["actions"].keys() == { + assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { "accept", "decline", } delete = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["accept"]), + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] + ), ) ThesisRecord.index.refresh() @@ -57,7 +59,9 @@ def test_delete( ) record = receiver_client.get(f"{urls['BASE_URL']}{record2['id']}?expand=true") decline = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["decline"]) + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] + ) ) declined_request = creator_client.get( f"{urls['BASE_URL_REQUESTS']}{resp_request_create.json['id']}" @@ -72,9 +76,13 @@ def test_delete( link_api2testclient(resp_request_create.json["links"]["actions"]["submit"]), ) record = creator_client.get(f"{urls['BASE_URL']}{record3['id']}?expand=true") - assert record.json["requests"][0]["links"]["actions"].keys() == {"cancel"} + assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { + "cancel" + } cancel = creator_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["cancel"]), + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["cancel"] + ), ) canceled_request = creator_client.get( f"{urls['BASE_URL_REQUESTS']}{resp_request_create.json['id']}" diff --git a/tests/test_requests/test_expand.py b/tests/test_requests/test_expand.py index 517078dd..0e59deb4 100644 --- a/tests/test_requests/test_expand.py +++ b/tests/test_requests/test_expand.py @@ -15,9 +15,9 @@ def test_requests_field( creator_client = logged_client(creator) receiver_client = logged_client(receiver) - draft1 = creator_client.post(urls["BASE_URL"], json={}) + draft1 = creator_client.post(urls["BASE_URL"] + "?expand=true", json={}) link = link_api2testclient( - pick_request_type(draft1.json["request_types"], "thesis_publish_draft")[ + pick_request_type(draft1.json["expanded"]["request_types"], "publish-draft")[ "links" ]["actions"]["create"] ) @@ -32,5 +32,5 @@ def test_requests_field( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) - assert "requests" not in record.json - assert "requests" in expanded_record.json + assert "requests" not in record.json.get("expanded", {}) + assert "requests" in expanded_record.json["expanded"] diff --git a/tests/test_requests/test_extended.py b/tests/test_requests/test_extended.py index ad64bdd1..dbe98f2b 100644 --- a/tests/test_requests/test_extended.py +++ b/tests/test_requests/test_extended.py @@ -105,7 +105,7 @@ def test_update_self_link( f"{urls['BASE_URL']}{example_topic_draft['id']}/draft?expand=true", ) link_to_extended = link_api2testclient( - read_from_record.json["requests"][0]["links"]["self"] + read_from_record.json["expanded"]["requests"][0]["links"]["self"] ) assert link_to_extended.startswith(f"{urls['BASE_URL_REQUESTS']}extended") @@ -151,10 +151,10 @@ def test_events_resource( ) comments_link = link_api2testclient( - read_from_record.json["requests"][0]["links"]["comments"] + read_from_record.json["expanded"]["requests"][0]["links"]["comments"] ) timeline_link = link_api2testclient( - read_from_record.json["requests"][0]["links"]["timeline"] + read_from_record.json["expanded"]["requests"][0]["links"]["timeline"] ) assert comments_link.startswith("/requests/extended") diff --git a/tests/test_requests/test_publish.py b/tests/test_requests/test_publish.py index ea4a7a2e..eeabf909 100644 --- a/tests/test_requests/test_publish.py +++ b/tests/test_requests/test_publish.py @@ -42,12 +42,14 @@ def test_publish( record = receiver_client.get( f"{urls['BASE_URL']}{draft1.json['id']}/draft?expand=true" ) - assert record.json["requests"][0]["links"]["actions"].keys() == { + assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { "accept", "decline", } publish = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["accept"]), + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["accept"] + ), ) record = receiver_client.get(f"{urls['BASE_URL']}{draft2.json['id']}/draft") assert "publish_draft" not in record.json["parent"] @@ -70,7 +72,9 @@ def test_publish( f"{urls['BASE_URL']}{draft2.json['id']}/draft?expand=true" ) decline = receiver_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["decline"]), + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["decline"] + ), ) declined_request = creator_client.get( f"{urls['BASE_URL_REQUESTS']}{resp_request_create.json['id']}" @@ -88,9 +92,13 @@ def test_publish( record = creator_client.get( f"{urls['BASE_URL']}{draft3.json['id']}/draft?expand=true" ) - assert record.json["requests"][0]["links"]["actions"].keys() == {"cancel"} + assert record.json["expanded"]["requests"][0]["links"]["actions"].keys() == { + "cancel" + } cancel = creator_client.post( - link_api2testclient(record.json["requests"][0]["links"]["actions"]["cancel"]), + link_api2testclient( + record.json["expanded"]["requests"][0]["links"]["actions"]["cancel"] + ), ) canceled_request = logged_client(creator).get( f"{urls['BASE_URL_REQUESTS']}{resp_request_create.json['id']}" diff --git a/tests/test_requests/test_timeline.py b/tests/test_requests/test_timeline.py index 655a9270..7d901ad7 100644 --- a/tests/test_requests/test_timeline.py +++ b/tests/test_requests/test_timeline.py @@ -15,9 +15,9 @@ def test_timeline( creator = users[0] creator_client = logged_client(creator) - draft1 = creator_client.post(urls["BASE_URL"], json={}) + draft1 = creator_client.post(urls["BASE_URL"] + "?expand=true", json={}) link = link_api2testclient( - pick_request_type(draft1.json["request_types"], "thesis_publish_draft")[ + pick_request_type(draft1.json["expanded"]["request_types"], "publish-draft")[ "links" ]["actions"]["create"] ) diff --git a/tests/test_requests/test_ui_serialialization.py b/tests/test_requests/test_ui_serialialization.py index 3b2b5fbe..2eb01230 100644 --- a/tests/test_requests/test_ui_serialialization.py +++ b/tests/test_requests/test_ui_serialialization.py @@ -1,5 +1,7 @@ import copy +from deepdiff import DeepDiff + from oarepo_requests.resolvers.ui import FallbackEntityReferenceUIResolver from thesis.records.api import ThesisDraft, ThesisRecord @@ -35,11 +37,12 @@ def test_publish( f"{urls['BASE_URL']}{draft_id}/draft?expand=true", headers={"Accept": "application/vnd.inveniordm.v1+json"}, ).json - - assert is_valid_subdict( - ui_serialization_result(draft_id, ui_record["requests"][0]["id"]), - ui_record["requests"][0], + diff = DeepDiff( + ui_serialization_result(draft_id, ui_record["expanded"]["requests"][0]["id"]), + ui_record["expanded"]["requests"][0], ) + assert "dictionary_item_removed" not in diff + assert "dictionary_item_changed" not in diff def test_resolver_fallback( @@ -76,53 +79,58 @@ def test_resolver_fallback( f"{urls['BASE_URL']}{draft_id}/draft?expand=true", headers={"Accept": "application/vnd.inveniordm.v1+json"}, ).json - expected_result = ui_serialization_result(draft_id, ui_record["requests"][0]["id"]) + 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 assert is_valid_subdict( expected_result, - ui_record["requests"][0], + ui_record["expanded"]["requests"][0], ) app.config["ENTITY_REFERENCE_UI_RESOLVERS"] = config_restore -def test_group( +def test_role( app, users, - group, + role, urls, publish_request_data_function, logged_client, - group_ui_serialization, + role_ui_serialization, search_clear, ): - def default_group_receiver(*args, **kwargs): - return {"group": group.id} + config_restore = app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] - config_restore = copy.deepcopy(app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"]) - app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"][ - "thesis_publish_draft" - ] = default_group_receiver + def current_receiver(identity, request_type, topic, creator, data): + if request_type == "publish-draft": + return role + return config_restore(identity, request_type, topic, creator, data) - creator = users[0] - creator_client = logged_client(creator) + try: + app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] = current_receiver - draft1 = creator_client.post(urls["BASE_URL"], json={}) - draft_id = draft1.json["id"] - ThesisRecord.index.refresh() - ThesisDraft.index.refresh() + creator = users[0] + creator_client = logged_client(creator) - resp_request_create = creator_client.post( - urls["BASE_URL_REQUESTS"], - json=publish_request_data_function(draft1.json["id"]), - ) + draft1 = creator_client.post(urls["BASE_URL"], json={}) + draft_id = draft1.json["id"] + ThesisRecord.index.refresh() + ThesisDraft.index.refresh() - ui_record = creator_client.get( - f"{urls['BASE_URL']}{draft_id}/draft?expand=true", - headers={"Accept": "application/vnd.inveniordm.v1+json"}, - ).json + resp_request_create = creator_client.post( + urls["BASE_URL_REQUESTS"], + json=publish_request_data_function(draft1.json["id"]), + ) + + ui_record = creator_client.get( + f"{urls['BASE_URL']}{draft_id}/draft?expand=true", + headers={"Accept": "application/vnd.inveniordm.v1+json"}, + ).json - assert ui_record["requests"][0]["receiver"] == group_ui_serialization - app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] = config_restore + assert ui_record["expanded"]["requests"][0]["receiver"] == role_ui_serialization + finally: + app.config["OAREPO_REQUESTS_DEFAULT_RECEIVER"] = config_restore diff --git a/tests/test_ui/model.py b/tests/test_ui/model.py index ed9e69e3..e2d63d66 100644 --- a/tests/test_ui/model.py +++ b/tests/test_ui/model.py @@ -1,9 +1,12 @@ +from flask import g +from flask_principal import PermissionDenied from oarepo_ui.resources import ( BabelComponent, RecordsUIResource, RecordsUIResourceConfig, ) from oarepo_ui.resources.components import PermissionsComponent +from werkzeug.exceptions import Forbidden from thesis.resources.records.ui import ThesisUIJSONSerializer @@ -28,4 +31,18 @@ class ModelUIResourceConfig(RecordsUIResourceConfig): class ModelUIResource(RecordsUIResource): - pass + + def _get_record(self, resource_requestctx, allow_draft=False): + try: + if allow_draft: + read_method = ( + getattr(self.api_service, "read_draft") or self.api_service.read + ) + else: + read_method = self.api_service.read + + return read_method( + g.identity, resource_requestctx.view_args["pid_value"], expand=True + ) + except PermissionDenied as e: + raise Forbidden() from e diff --git a/tests/test_ui/templates/TestDetail.jinja b/tests/test_ui/templates/TestDetail.jinja index 3c74e830..2434161d 100644 --- a/tests/test_ui/templates/TestDetail.jinja +++ b/tests/test_ui/templates/TestDetail.jinja @@ -2,7 +2,7 @@ { "available_requests": { -{% for request in record.requests %} +{% for request in record.expanded.requests %} "{{ request.name }}": { "type": {{ request.type|tojson|safe }}, "status": {{ request.status|tojson|safe }}, @@ -15,7 +15,7 @@ }, "creatable_request_types": { -{% for request_type in record.request_types %} +{% for request_type in record.expanded.request_types %} "{{ request_type.type_id }}": { "name": {{ request_type.name|tojson|safe }}, "description": {{ request_type.description|tojson|safe }}, diff --git a/tests/test_ui/templates/TestEdit.jinja b/tests/test_ui/templates/TestEdit.jinja index ba5690d8..c629922b 100644 --- a/tests/test_ui/templates/TestEdit.jinja +++ b/tests/test_ui/templates/TestEdit.jinja @@ -2,7 +2,7 @@ { "available_requests": { -{% for request in record.requests %} +{% for request in record.expanded.requests %} "{{ request.name }}": { "type": {{ request.type|tojson|safe }}, "status": {{ request.status|tojson|safe }}, @@ -15,7 +15,7 @@ }, "creatable_request_types": { -{% for request_type in record.request_types %} +{% for request_type in record.expanded.request_types %} "{{ request_type.type_id }}": { "name": {{ request_type.name|tojson|safe }}, "description": {{ request_type.description|tojson|safe }}, diff --git a/tests/test_ui/test_ui_resource.py b/tests/test_ui/test_ui_resource.py index 924c1bdd..de8e3fac 100644 --- a/tests/test_ui/test_ui_resource.py +++ b/tests/test_ui/test_ui_resource.py @@ -2,7 +2,7 @@ from invenio_requests.proxies import current_requests_service -from thesis.records.requests.edit_record.types import EditPublishedRecordRequestType +from oarepo_requests.types import EditPublishedRecordRequestType allowed_actions = ["submit", "delete"] @@ -13,11 +13,12 @@ def test_draft_publish_request_present( with logged_client(users[0]).get(f"/thesis/{example_topic_draft['id']}/edit") as c: assert c.status_code == 200 data = json.loads(c.text) - assert data["creatable_request_types"]["thesis_publish_draft"] == { + print(data) + assert data["creatable_request_types"]["publish-draft"] == { "description": "Request publishing of a draft", "links": { "actions": { - "create": f"https://127.0.0.1:5000/api/thesis/{example_topic_draft['id']}/draft/requests/thesis_publish_draft" + "create": f"https://127.0.0.1:5000/api/thesis/{example_topic_draft['id']}/draft/requests/publish-draft" } }, "name": "Publish draft", @@ -40,20 +41,20 @@ def test_record_delete_request_present( assert c.status_code == 200 data = json.loads(c.text) assert len(data["creatable_request_types"]) == 2 - assert data["creatable_request_types"]["thesis_edit_record"] == { + assert data["creatable_request_types"]["edit-published-record"] == { "description": "Request re-opening of published record", "links": { "actions": { - "create": f"https://127.0.0.1:5000/api/thesis/{example_topic['id']}/requests/thesis_edit_record" + "create": f"https://127.0.0.1:5000/api/thesis/{example_topic['id']}/requests/edit-published-record" } }, "name": "Edit record", } - assert data["creatable_request_types"]["thesis_delete_record"] == { + assert data["creatable_request_types"]["delete-published-record"] == { "description": "Request deletion of published record", "links": { "actions": { - "create": f"https://127.0.0.1:5000/api/thesis/{example_topic['id']}/requests/thesis_delete_record" + "create": f"https://127.0.0.1:5000/api/thesis/{example_topic['id']}/requests/delete-published-record" } }, "name": "Delete record", @@ -97,4 +98,4 @@ def test_request_detail_page( # TODO: fix this test when the "detail" resource is merged # with creator_client.get(f"/requests/{request_id}") as c: # assert c.status_code == 200 - # print(c.text) \ No newline at end of file + # print(c.text) diff --git a/tests/thesis.yaml b/tests/thesis.yaml index 4d57fb1d..3ca1f7f5 100644 --- a/tests/thesis.yaml +++ b/tests/thesis.yaml @@ -5,28 +5,7 @@ record: qualified: thesis permissions: presets: [ 'everyone' ] - - draft: - requests: - types: - publish-draft: - base-classes: - - PublishDraftRequestType - imports: - - import: oarepo_requests.types.publish_draft.PublishDraftRequestType - allowed-receiver-ref-types: ["user", "group"] - requests: - types: - delete-record: - base-classes: - - DeleteRecordRequestType - imports: - - import: oarepo_requests.types.delete_record.DeleteRecordRequestType - edit-record: - base-classes: - - EditRecordRequestType - imports: - - import: oarepo_requests.types.edit_record.EditRecordRequestType + draft: {} properties: metadata: properties: