From dbf1ce450d47730b45662d3dfe08bf3970dc6368 Mon Sep 17 00:00:00 2001 From: Guillaume Viger Date: Mon, 17 Jun 2024 13:45:46 -0400 Subject: [PATCH] members service: [#855] 5) display membership discussion link --- invenio_communities/members/records/api.py | 17 +++++- .../members/services/service.py | 7 +++ .../invenio_communities/details/header.html | 26 +++++--- invenio_communities/views/communities.py | 16 +++++ tests/members/test_members_services.py | 59 ++++++++++++++++++- 5 files changed, 114 insertions(+), 11 deletions(-) diff --git a/invenio_communities/members/records/api.py b/invenio_communities/members/records/api.py index 5bf3ac28b..d74f581a8 100644 --- a/invenio_communities/members/records/api.py +++ b/invenio_communities/members/records/api.py @@ -19,7 +19,7 @@ from invenio_records_resources.records.systemfields import IndexField from invenio_requests.records.api import Request from invenio_users_resources.records.api import GroupAggregate, UserAggregate -from sqlalchemy import or_ +from sqlalchemy import or_, select from ..errors import InvalidMemberError from .models import ArchivedInvitationModel, MemberModel @@ -160,6 +160,21 @@ def has_members(cls, community_id, role=None): """Get members of a community.""" return cls.model_cls.count_members(community_id, role=role) + @classmethod + def get_pending_request_id_if_any(cls, user_id, community_id): + """Return request id of membership request/invitation still pending. + + Return type UUID. + """ + stmt = ( + select(cls.model_cls.request_id) + .where(cls.model_cls.user_id == user_id) + .where(cls.model_cls.community_id == community_id) + .where(cls.model_cls.active == False) + ) + request_id = db.session.scalars(stmt).one_or_none() + return request_id + class Member(Record, MemberMixin): """A member/invitation record. diff --git a/invenio_communities/members/services/service.py b/invenio_communities/members/services/service.py index 24255467b..db1db57f8 100644 --- a/invenio_communities/members/services/service.py +++ b/invenio_communities/members/services/service.py @@ -857,3 +857,10 @@ def close_membership_request(self, identity, request_id, uow=None): member = self.record_cls.get_member_by_request(request_id) assert member.active is False uow.register(RecordDeleteOp(member, indexer=self.indexer, force=True)) + + def get_pending_request_id_if_any(self, user_id, community_id): + """Utility function to get associated active request id. + + Only pending members fit this. In other cases return None. + """ + return self.record_cls.get_pending_request_id_if_any(user_id, community_id) diff --git a/invenio_communities/templates/semantic-ui/invenio_communities/details/header.html b/invenio_communities/templates/semantic-ui/invenio_communities/details/header.html index 0f5f9ebbf..ab237f5fc 100644 --- a/invenio_communities/templates/semantic-ui/invenio_communities/details/header.html +++ b/invenio_communities/templates/semantic-ui/invenio_communities/details/header.html @@ -11,15 +11,23 @@ {%- from "invenio_theme/macros/truncate.html" import truncate_text %} {%- from "invenio_communities/details/macros/access-status-label.html" import access_status_label -%} -{% macro button_to_request_membership(community) %} - {% if permissions.can_request_membership %} - {# TODO: Add relation_to_community for other flows #} -
+ + {{ _("Membership discussion") }} + + {% elif permissions.can_request_membership %} +
-
+
+ {% else %} + {# show nothing #} {% endif %} {% endmacro %} @@ -120,7 +128,7 @@

{{ community.metadata.title }}

- {{ button_to_request_membership(community) }} + {{ button_for_membership(community) }} {%- if not community_use_jinja_header %} diff --git a/invenio_communities/views/communities.py b/invenio_communities/views/communities.py index 22446d043..a6c775230 100644 --- a/invenio_communities/views/communities.py +++ b/invenio_communities/views/communities.py @@ -391,6 +391,8 @@ def members(pid_value, community, community_ui): if not permissions["can_members_search_public"]: raise PermissionDeniedError() + members_service = current_communities.service.members + return render_community_theme_template( "invenio_communities/details/members/members.html", theme=community_ui.get("theme", {}), @@ -398,6 +400,9 @@ def members(pid_value, community, community_ui): permissions=permissions, roles_can_update=_get_roles_can_update(community.id), roles_can_invite=_get_roles_can_invite(community.id), + associated_request_id=( + members_service.get_pending_request_id_if_any(g.identity.id, community.id) + ), ) @@ -423,12 +428,18 @@ def communities_about(pid_value, community, community_ui): if not permissions["can_read"]: raise PermissionDeniedError() + members_service = current_communities.service.members + return render_community_theme_template( "invenio_communities/details/about/index.html", theme=community_ui.get("theme", {}), community=community_ui, permissions=permissions, custom_fields_ui=load_custom_fields(dump_only_required=False)["ui"], + associated_request_id=( + members_service.get_pending_request_id_if_any( + g.identity.id, community.id) + ), ) @@ -436,6 +447,7 @@ def communities_about(pid_value, community, community_ui): def communities_curation_policy(pid_value, community, community_ui): """Community curation policy page.""" permissions = community.has_permissions_to(HEADER_PERMISSIONS) + members_service = current_communities.service.members if not permissions["can_read"]: raise PermissionDeniedError() return render_community_theme_template( @@ -443,6 +455,10 @@ def communities_curation_policy(pid_value, community, community_ui): theme=community_ui.get("theme", {}), community=community_ui, permissions=permissions, + associated_request_id=( + members_service.get_pending_request_id_if_any( + g.identity.id, community.id) + ), ) diff --git a/tests/members/test_members_services.py b/tests/members/test_members_services.py index 7f66c0e89..854e7f2c6 100644 --- a/tests/members/test_members_services.py +++ b/tests/members/test_members_services.py @@ -17,7 +17,7 @@ from invenio_cache import current_cache from invenio_notifications.proxies import current_notifications_manager from invenio_records_resources.services.errors import PermissionDeniedError -from invenio_requests.records.api import RequestEvent +from invenio_requests.records.api import Request, RequestEvent from marshmallow import ValidationError from invenio_communities.members.errors import AlreadyMemberError, InvalidMemberError @@ -1208,6 +1208,63 @@ def test_request_cancel_request_flow( ) +def test_get_pending_request_id_if_any( + member_service, + community, + owner, + create_user, + requests_service, + db, + search_clear, +): + user = create_user() + + # Case no membership (no associated request id) + request_id = member_service.get_pending_request_id_if_any( + user.id, community._record.id) + assert request_id is None + + # Case pending membership (associated request id) + membership_request = member_service.request_membership( + user.identity, + community._record.id, + {"message": "Can I join the club?"}, + ) + request_id = member_service.get_pending_request_id_if_any( + user.id, community._record.id) + assert membership_request.id == str(request_id) + + # Case (sanity check) pending membership from invitation (associated request id) + requests_service.execute_action( + user.identity, membership_request.id, "cancel" + ) + data = { + "members": [{"type": "user", "id": str(user.id)}], + "role": "reader", + "message": "Welcome to the club!", + } + member_service.invite(owner.identity, community._record.id, data) + # get invitation_request_id + Request.index.refresh() + results = requests_service.search( + user.identity, + receiver={"user": user.id}, + type="community-invitation", + ).to_dict() + invitation_request_id = results["hits"]["hits"][0]["id"] + request_id = member_service.get_pending_request_id_if_any( + user.id, community._record.id) + assert invitation_request_id == str(request_id) + + # Case membership established (associated request id but not pending) + requests_service.execute_action( + user.identity, invitation_request_id, "accept" + ) + request_id = member_service.get_pending_request_id_if_any( + user.id, community._record.id) + assert request_id is None + + # # Change notifications #