Skip to content

Commit

Permalink
requests: implement a new community manage record request
Browse files Browse the repository at this point in the history
  • Loading branch information
anikachurilova committed Sep 12, 2023
1 parent bda3665 commit 1d28b94
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 1 deletion.
1 change: 1 addition & 0 deletions site/setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ invenio_config.module =
zenodo_rdm = zenodo_rdm.config
invenio_requests.types =
legacy_record_upgrade = zenodo_rdm.legacy.requests:LegacyRecordUpgrade
community_manage_record = zenodo_rdm.legacy.requests:CommunityManageRecord
invenio_access.actions =
media_files_management_action = zenodo_rdm.generators:media_files_management_action

Expand Down
3 changes: 2 additions & 1 deletion site/zenodo_rdm/legacy/requests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""Request types for ZenodoRDM."""


from .community_manage_record import CommunityManageRecord
from .record_upgrade import LegacyRecordUpgrade

__all__ = ("LegacyRecordUpgrade",)
__all__ = ("LegacyRecordUpgrade", "CommunityManageRecord")
211 changes: 211 additions & 0 deletions site/zenodo_rdm/legacy/requests/community_manage_record.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2023 CERN.
#
# Zenodo is free software; you can redistribute it and/or modify
# it under the terms of the MIT License; see LICENSE file for more details.

"""Zenodo community manage record request."""

from flask_login import current_user
from invenio_access.permissions import system_identity
from invenio_communities.proxies import current_communities
from invenio_pidstore.models import PersistentIdentifier
from invenio_rdm_records.proxies import (
current_rdm_records_service,
current_record_communities_service,
)
from invenio_rdm_records.records import RDMParent, RDMRecord
from invenio_records_resources.services.uow import IndexRefreshOp, RecordCommitOp
from invenio_requests.customizations import CommentEventType, RequestType, actions
from invenio_requests.proxies import current_events_service, current_requests_service
from invenio_search.engine import dsl


def _remove_permission_flag(record, uow):
"""Remove can_community_manage_record permission flag."""
try:
record.parent.permission_flags.pop("can_community_manage_record")
record.parent.commit()
uow.register(RecordCommitOp(record.parent))
except (KeyError, AttributeError):
# if field or flag is absent just continue with an action
pass


def _remove_record_from_communities(record, communities_ids):
"""Remove record from communities."""
if communities_ids:
communities = []
for community_id in communities_ids:
communities.append({"id": community_id})

data = dict(communities=communities)
current_record_communities_service.remove(system_identity, record["id"], data)


def _create_comment(request, content, uow):
"""Create a comment event."""
comment = {"payload": {"content": content}}

current_events_service.create(
system_identity, request.id, comment, CommentEventType, uow=uow
)

# make event immediately available in search
uow.register(IndexRefreshOp(indexer=current_events_service.indexer))


def _get_legacy_records_by_user():
"""Find legacy records of a specific user."""
return current_rdm_records_service.search(
system_identity,
extra_filter=dsl.query.Bool(
"must",
must=[
dsl.Q("terms", **{"parent.access.owned_by.user": [current_user.id]}),
# indicator that record is a legacy one
dsl.Q(
"exists",
field="parent.permission_flags.can_community_manage_record",
),
],
),
)


def _resolve_record(legacy_record):
"""Get record byt its pid."""
record_pid = PersistentIdentifier.query.filter(
PersistentIdentifier.pid_value == legacy_record["id"]
).one()
record_id = str(record_pid.object_uuid)
return RDMRecord.get_record(record_id)


#
# Actions
#
class SubmitAction(actions.SubmitAction):
"""Submit action."""

def execute(self, identity, uow):
"""Execute the submit action."""
self.request["title"] = "Communities manage legacy records"

# example: "May 11, 2024"
expires_at = self.request.expires_at.strftime("%B %d, %Y")
self.request["description"] = (
"<h4>Some of your records, that are going through migration process are part "
"of the communities that don't belong to you.</br>Accept this request to keep the old "
"behaviour and <b>allow community curators</b> to manage (edit, create new version, add to "
"another community, etc.) corresponding record. </br>In case of declining this "
"request all your legacy records will be <b>removed from all communities</b> "
"that you are not an owner of. </br></br>If you do not perform any action by "
f"<b>{expires_at}</b>, the permission for community curators to manage the record "
"will automatically be fully granted.</h4>"
)

super().execute(identity, uow)


class AcceptAction(actions.AcceptAction):
"""Accept action."""

def execute(self, identity, uow):
"""Grant permission to manage all legacy records of a user to all the communities."""
legacy_records = _get_legacy_records_by_user()

for hit in legacy_records.hits:
# remove flag from record parent, permissions logic will do the rest
record = _resolve_record(hit)
_remove_permission_flag(record, uow)

comment = (
"You accepted the request. The community curators "
"can now manage all of your legacy records."
)
_create_comment(self.request, comment, uow)

super().execute(identity, uow)


class DeclineAction(actions.DeclineAction):
"""Decline action."""

def execute(self, identity, uow):
"""Deny access to manage legacy records for community curators."""
legacy_records = _get_legacy_records_by_user()

for legacy_record in legacy_records.hits:
record = _resolve_record(legacy_record)
communities = (
legacy_record["parent"].get("communities", None).get("ids", None)
)
if communities is not None:
not_my_comm = []
for community_id in communities:
com_members = current_communities.service.members.search(
system_identity,
community_id,
extra_filter=dsl.query.Bool(
"must",
must=[
dsl.Q("term", **{"role": "owner"}),
~dsl.Q("term", **{"user_id": current_user.id}),
],
),
)
if com_members.total > 0:
not_my_comm.append(community_id)
_remove_record_from_communities(record, not_my_comm)
_remove_permission_flag(record, uow)

super().execute(identity, uow)


class ExpireAction(actions.ExpireAction):
"""Expire action."""

def execute(self, identity, uow):
"""Grant permission to manage all legacy records of a user to all the communities."""
legacy_records = _get_legacy_records_by_user()

for hit in legacy_records.hits:
# remove flag from record parent, permissions logic will do the rest
record = _resolve_record(hit)
_remove_permission_flag(record, uow)

comment = (
"The request was expired. The community curators "
"can now manage all of your legacy records."
)
_create_comment(self.request, comment, uow)

super().execute(identity, uow)


#
# Request
#
class CommunityManageRecord(RequestType):
"""Request for granting permissions to manage a record for its community curators."""

type_id = "community-manage-record"
name = "Community manage record"

available_actions = {
"create": actions.CreateAction,
"submit": SubmitAction,
"delete": actions.DeleteAction,
"accept": AcceptAction,
"cancel": actions.CancelAction,
"decline": DeclineAction,
"expire": ExpireAction,
}

creator_can_be_none = False
topic_can_be_none = True

allowed_creator_ref_types = ["user"]
allowed_receiver_ref_types = ["user"]
24 changes: 24 additions & 0 deletions site/zenodo_rdm/legacy/requests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from invenio_requests.resolvers.registry import ResolverRegistry
from invenio_search.engine import dsl

from .community_manage_record import CommunityManageRecord
from .record_upgrade import LegacyRecordUpgrade


Expand Down Expand Up @@ -82,3 +83,26 @@ def submit_record_upgrade_request(record, uow, comment=None):
return current_requests_service.execute_action(
system_identity, request_item._request.id, "submit", data=comment, uow=uow
)


@unit_of_work()
def submit_community_manage_record_request(user_id, uow, comment=None):
"""Create and submit a CommunityManageRecord request."""
type_ = current_request_type_registry.lookup(CommunityManageRecord.type_id)
receiver = ResolverRegistry.resolve_entity_proxy({"user": user_id}).resolve()
expires_at = datetime.utcnow() + timedelta(weeks=20)

# create a request
request_item = current_requests_service.create(
system_identity,
{},
type_,
receiver,
expires_at=expires_at,
uow=uow,
)

# submit the request
return current_requests_service.execute_action(
system_identity, request_item._request.id, "submit", data=comment, uow=uow
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{# -*- coding: utf-8 -*-

This file is part of Invenio.
Copyright (C) 2023 CERN.

Invenio is free software; you can redistribute it and/or modify it
under the terms of the MIT License; see LICENSE file for more details.
#}

{#
Renders the Community manage record request details page.
#}

{% extends "invenio_requests/details/index.html" %}

{% set active_dashboard_menu_item = 'requests' %}

{%- block request_header %}
{% set back_button_url = url_for("invenio_app_rdm_users.requests") %}
{% from "invenio_requests/macros/request_header.html" import inclusion_request_header %}
{{ inclusion_request_header(
request=invenio_request,
accepted=request_is_accepted,
back_button_url=back_button_url,
back_button_text=_("Back to requests")
) }}
{%- endblock request_header %}

{% block request_timeline %}
<div class="ui container rdm-tab-container fluid rel-pt-2 ml-0-mobile mr-0-mobile">
<div
class="ui bottom attached tab segment active borderless p-0"
data-tab="conversation"
role="tabpanel"
aria-labelledby="conversation-tab"
id="conversation-tab-panel"
>
{{ super() }}
</div>
</div>
{% endblock request_timeline %}

0 comments on commit 1d28b94

Please sign in to comment.