From 9968d832cf4de3f1505642d7861e085fbf6d5852 Mon Sep 17 00:00:00 2001 From: Carlin MacKenzie Date: Mon, 18 Nov 2024 14:29:17 +0100 Subject: [PATCH 1/5] request: add subcommunity invitation request --- .../subcommunities/services/__init__.py | 7 ++- .../subcommunities/services/request.py | 46 +++++++++++++++++- .../subcommunity-invitation/index.html | 47 +++++++++++++++++++ setup.cfg | 1 + 4 files changed, 98 insertions(+), 3 deletions(-) create mode 100644 invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html diff --git a/invenio_communities/subcommunities/services/__init__.py b/invenio_communities/subcommunities/services/__init__.py index 0543e7479..2af4a1b95 100644 --- a/invenio_communities/subcommunities/services/__init__.py +++ b/invenio_communities/subcommunities/services/__init__.py @@ -8,5 +8,10 @@ from .config import SubCommunityServiceConfig from .service import SubCommunityService +from .request import SubCommunityInvitationRequest -__all__ = ("SubCommunityService", "SubCommunityServiceConfig") +__all__ = ( + "SubCommunityService", + "SubCommunityServiceConfig", + "SubCommunityInvitationRequest", +) diff --git a/invenio_communities/subcommunities/services/request.py b/invenio_communities/subcommunities/services/request.py index 6c2464342..ee422b08e 100644 --- a/invenio_communities/subcommunities/services/request.py +++ b/invenio_communities/subcommunities/services/request.py @@ -52,10 +52,10 @@ def execute(self, identity, uow): class SubCommunityRequest(RequestType): - """Request to add a subcommunity to a community.""" + """Request to join a parent community as a subcommunity.""" type_id = "subcommunity" - name = _("Subcommunity Request") + name = _("Subcommunity request") creator_can_be_none = False topic_can_be_none = False @@ -80,6 +80,35 @@ class SubCommunityRequest(RequestType): } +class SubCommunityInvitationRequest(RequestType): + """Request from a parent community to community to join.""" + + type_id = "subcommunity-invitation" + name = _("Subcommunity invitation") + + creator_can_be_none = False + topic_can_be_none = True + allowed_creator_ref_types = ["community"] + allowed_receiver_ref_types = ["community"] + allowed_topic_ref_types = ["community"] + + available_actions = { + "delete": actions.DeleteAction, + "create": actions.CreateAndSubmitAction, + "cancel": actions.CancelAction, + # Custom implemented actions + "accept": AcceptSubcommunity, + "decline": DeclineSubcommunity, + } + + needs_context = { + "community_roles": [ + "owner", + "manager", + ] + } + + def subcommunity_request_type(app): """Return the subcommunity request type. @@ -91,3 +120,16 @@ def subcommunity_request_type(app): if not app: return return app.config.get("COMMUNITIES_SUB_REQUEST_CLS", SubCommunityRequest) + + +def subcommunity_invitation_request_type(app): + """Return the subcommunity request type. + + Since it can be overridden by the application, this function should be used + as the entry point for the request type. + + It must return a class that inherits from `RequestType`. + """ + if not app: + return + return app.config.get("COMMUNITIES_SUB_INVITATION_REQUEST_CLS", SubCommunityInvitationRequest) diff --git a/invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html b/invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html new file mode 100644 index 000000000..ee6c0cf3e --- /dev/null +++ b/invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html @@ -0,0 +1,47 @@ +{# -*- coding: utf-8 -*- + +This file is part of Invenio. +Copyright (C) 2024 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. +#} + +{% extends "invenio_communities/details/base.html" %} + +{% set active_community_header_menu_item= 'browse' %} +{% set search_endpoint = community["links"]["subcommunities"] %} +{%- set title = community.metadata.title + _(" subcommunities") -%} + +{%- block javascript %} +{{ super() }} +{{ webpack['invenio-communities-subcommunities.js'] }} +{%- endblock %} + +{%- block page_body %} +{{ super() }} +
+
+

{{ config.get("APP_RDM_SUBCOMMUNITIES_LABEL", _("Subcommunities")) }}

+
+
+ +
+ +
+ + +{%- endblock page_body %} diff --git a/setup.cfg b/setup.cfg index ca550b1b1..77e02c2b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -83,6 +83,7 @@ invenio_requests.entity_resolvers = invenio_requests.types = community_invitation = invenio_communities.members.services.request:CommunityInvitation subcommunity = invenio_communities.subcommunities.services.request:subcommunity_request_type + subcommunity_invitation = invenio_communities.subcommunities.services.request:subcommunity_invitation_request_type membership_request_request_type = invenio_communities.members.services.request:MembershipRequestRequestType invenio_i18n.translations = messages = invenio_communities From be320b3b7b025ba4e9fe33a78ed572e76871d04b Mon Sep 17 00:00:00 2001 From: Carlin MacKenzie Date: Wed, 20 Nov 2024 11:41:47 +0100 Subject: [PATCH 2/5] subcommunities: add invitation notifications --- invenio_communities/notifications/builders.py | 45 ++++++++++++++ .../subcommunities/services/__init__.py | 2 +- .../subcommunities/services/request.py | 4 +- ...bcommunity-invitation-request.accept.jinja | 59 +++++++++++++++++++ ...bcommunity-invitation-request.create.jinja | 57 ++++++++++++++++++ ...community-invitation-request.decline.jinja | 59 +++++++++++++++++++ 6 files changed, 224 insertions(+), 2 deletions(-) create mode 100644 invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja create mode 100644 invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja create mode 100644 invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja diff --git a/invenio_communities/notifications/builders.py b/invenio_communities/notifications/builders.py index 39a40672e..eca26ecfc 100644 --- a/invenio_communities/notifications/builders.py +++ b/invenio_communities/notifications/builders.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- # +# Copyright (C) 2024 CERN. # Copyright (C) 2023 Graz University of Technology. # # Invenio-Communities is free software; you can redistribute it and/or modify @@ -216,3 +217,47 @@ class SubCommunityDecline(SubCommunityBuilderBase): """Notification builder for subcommunity request decline.""" type = f"{SubCommunityBuilderBase.type}.decline" + + +class SubComInvitationBuilderBase(SubCommunityBuilderBase): + """Base notification builder for subcommunity invitation requests.""" + + type = "subcommunity-invitation-request" + + context = [ + EntityResolve("request"), + EntityResolve("request.created_by"), + EntityResolve("request.receiver"), + # EntityResolve("executing_user") executing via script only for now + ] + + recipients = [ + CommunityMembersRecipient("request.created_by", roles=["owner", "manager"]), + CommunityMembersRecipient("request.receiver", roles=["owner", "manager"]), + ] + + +class SubComInvitationCreate(SubComInvitationBuilderBase): + """Notification builder for subcommunity request creation.""" + + type = f"{SubComInvitationBuilderBase.type}.create" + + recipient_filters = [ + UserPreferencesRecipientFilter(), + # TODO: We exceptionally DON'T filter out the executing user here, because we + # don't have a clear place where they can see the created request. + # See also: https://github.com/inveniosoftware/invenio-communities/issues/1158 + # UserRecipientFilter("executing_user"), + ] + + +class SubComInvitationAccept(SubComInvitationBuilderBase): + """Notification builder for subcommunity request accept.""" + + type = f"{SubComInvitationBuilderBase.type}.accept" + + +class SubComInvitationDecline(SubComInvitationBuilderBase): + """Notification builder for subcommunity request decline.""" + + type = f"{SubComInvitationBuilderBase.type}.decline" diff --git a/invenio_communities/subcommunities/services/__init__.py b/invenio_communities/subcommunities/services/__init__.py index 2af4a1b95..939e2a188 100644 --- a/invenio_communities/subcommunities/services/__init__.py +++ b/invenio_communities/subcommunities/services/__init__.py @@ -7,8 +7,8 @@ """Subcommunities module service.""" from .config import SubCommunityServiceConfig -from .service import SubCommunityService from .request import SubCommunityInvitationRequest +from .service import SubCommunityService __all__ = ( "SubCommunityService", diff --git a/invenio_communities/subcommunities/services/request.py b/invenio_communities/subcommunities/services/request.py index ee422b08e..84b49b11c 100644 --- a/invenio_communities/subcommunities/services/request.py +++ b/invenio_communities/subcommunities/services/request.py @@ -132,4 +132,6 @@ def subcommunity_invitation_request_type(app): """ if not app: return - return app.config.get("COMMUNITIES_SUB_INVITATION_REQUEST_CLS", SubCommunityInvitationRequest) + return app.config.get( + "COMMUNITIES_SUB_INVITATION_REQUEST_CLS", SubCommunityInvitationRequest + ) diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja new file mode 100644 index 000000000..8fccdebbe --- /dev/null +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja @@ -0,0 +1,59 @@ +{% set request = notification.context.request %} +{% set parent_community = request.created_by %} +{% set community = request.receiver %} +{% set user = notification.context.executing_user %} +{% set ui = config.SITE_UI_URL %} +{% set msg_ctx = { + "community_id": community.slug, + "community_title": community.metadata.title, + "parent_community_id": parent_community.slug, + "parent_community_title": parent_community.metadata.title, + "request_id": request.id, + "username": user.username or user.profile.full_name, + "ui": ui, +} %} + +{# TODO: use request.links.self_html when issue issue is resolved: https://github.com/inveniosoftware/invenio-rdm-records/issues/1327 #} +{% set request_link = "{ui}/communities/{community_id}/requests/{request_id}".format(**msg_ctx) %} +{% set account_settings_link = "{ui}/account/settings/notifications".format(**msg_ctx) %} + +{%- block subject -%} +{{ _('✅ Invitation accepted for "{community_title}" to join the {parent_community_title} community').format(**msg_ctx) }} +{%- endblock subject -%} + +{%- block html_body -%} + + + + + + + + + + + + + +
{{ _('@{username} accepted the invitation for "{community_title}" to join as a subcommunity of "{parent_community_title}".').format(**msg_ctx) }} +
{{ _("View the request")}}
{{ _("This is an auto-generated message. To manage notifications, visit your")}} {{ _("account settings")}}.
+{%- endblock html_body %} + +{%- block plain_body -%} +{{ _('@{username} accepted the invitation for "{community_title}" to join as a subcommunity of "{parent_community_title}".').format(**msg_ctx) }} + +{{ _("View the request at:") }} {{ request_link }} + +— +{{ _("This is an auto-generated message. To manage notifications, visit your account settings at: ") }}{{ account_settings_link }} +{%- endblock plain_body %} + +{# Markdown for Slack/Mattermost/chat #} +{%- block md_body -%} +{{ _('@{username} accepted the invitation the request for "{community_title}" to join as a subcommunity of "{parent_community_title}".').format(**msg_ctx) }} + +[{{ _("View the request") }}]({{ request_link }}) + +— +{{ _("This is an auto-generated message. To manage notifications, visit your") }} [{{ _("account settings") }}]({{ account_settings_link }}). +{%- endblock md_body %} diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja new file mode 100644 index 000000000..2a320abe9 --- /dev/null +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja @@ -0,0 +1,57 @@ +{% set request = notification.context.request %} +{% set parent_community = request.created_by %} +{% set community = request.receiver %} +{% set ui = config.SITE_UI_URL %} +{% set msg_ctx = { + "community_id": community.slug, + "community_title": community.metadata.title, + "parent_community_id": parent_community.slug, + "parent_community_title": parent_community.metadata.title, + "request_id": request.id, + "ui": ui, +} %} + +{# TODO: use request.links.self_html when issue issue is resolved: https://github.com/inveniosoftware/invenio-rdm-records/issues/1327 #} +{% set request_link = "{ui}/communities/{community_id}/requests/{request_id}".format(**msg_ctx) %} +{% set account_settings_link = "{ui}/account/settings/notifications".format(**msg_ctx) %} + +{%- block subject -%} +{{ _('📬 Invitation to join the {parent_community_title} community').format(**msg_ctx) }} +{%- endblock subject -%} + +{%- block html_body -%} + + + + + + + + + + + + + +
{{ _('We would like to invite your community "{community_title}" to join as a subcommunity of "{parent_community_title}"').format(**msg_ctx) }} +
{{ _("View the request")}}
{{ _("This is an auto-generated message. To manage notifications, visit your")}} {{ _("account settings")}}.
+{%- endblock html_body %} + +{%- block plain_body -%} +{{ _('We would like to invite your community "{community_title}" to join as a subcommunity of "{parent_community_title}"').format(**msg_ctx) }} + +{{ _("View the request at:") }} {{ request_link }} + +— +{{ _("This is an auto-generated message. To manage notifications, visit your account settings at: ") }}{{ account_settings_link }} +{%- endblock plain_body %} + +{# Markdown for Slack/Mattermost/chat #} +{%- block md_body -%} +{{ _('The community *{community_title}* has requested to join as a subcommunity of *{parent_community_title}*').format(**msg_ctx) }} + +[{{ _("View the request") }}]({{ request_link }}) + +— +{{ _("This is an auto-generated message. To manage notifications, visit your") }} [{{ _("account settings") }}]({{ account_settings_link }}). +{%- endblock md_body %} diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja new file mode 100644 index 000000000..d8372e87d --- /dev/null +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja @@ -0,0 +1,59 @@ +{% set request = notification.context.request %} +{% set parent_community = request.created_by %} +{% set community = request.receiver %} +{% set user = notification.context.executing_user %} +{% set ui = config.SITE_UI_URL %} +{% set msg_ctx = { + "community_id": community.slug, + "community_title": community.metadata.title, + "parent_community_id": parent_community.slug, + "parent_community_title": parent_community.metadata.title, + "request_id": request.id, + "username": user.username or user.profile.full_name, + "ui": ui, +} %} + +{# TODO: use request.links.self_html when issue issue is resolved: https://github.com/inveniosoftware/invenio-rdm-records/issues/1327 #} +{% set request_link = "{ui}/communities/{community_id}/requests/{request_id}".format(**msg_ctx) %} +{% set account_settings_link = "{ui}/account/settings/notifications".format(**msg_ctx) %} + +{%- block subject -%} +{{ _('⛔️ Subcommunity invitation declined for "{community_title}" to join "{parent_community_title}"').format(**msg_ctx) }} +{%- endblock subject -%} + +{%- block html_body -%} + + + + + + + + + + + + + +
{{ _('@{username} declined the invitation for "{community_title}" to join as a subcommunity of "{parent_community_title}".').format(**msg_ctx) }} +
{{ _("View the request")}}
{{ _("This is an auto-generated message. To manage notifications, visit your")}} {{ _("account settings")}}.
+{%- endblock html_body %} + +{%- block plain_body -%} +{{ _('@{username} declined the invitation for "{community_title}" to join as a subcommunity of "{parent_community_title}".').format(**msg_ctx) }} + +{{ _("View the request at:") }} {{ request_link }} + +— +{{ _("This is an auto-generated message. To manage notifications, visit your account settings at: ") }}{{ account_settings_link }} +{%- endblock plain_body %} + +{# Markdown for Slack/Mattermost/chat #} +{%- block md_body -%} +{{ _('@{username} declined the invitation for "{community_title}" to join as a subcommunity of "{parent_community_title}".').format(**msg_ctx) }} + +[{{ _("View the request") }}]({{ request_link }}) + +— +{{ _("This is an auto-generated message. To manage notifications, visit your") }} [{{ _("account settings") }}]({{ account_settings_link }}). +{%- endblock md_body %} From 1aee5a0a58db9da4f4ee2400ae77c79be4e76d98 Mon Sep 17 00:00:00 2001 From: Carlin MacKenzie Date: Wed, 20 Nov 2024 14:48:32 +0100 Subject: [PATCH 3/5] subcommunities: add invitation actions --- invenio_communities/notifications/builders.py | 40 +++++++++++--- .../subcommunities/services/request.py | 54 +++++++++++++++++-- ...bcommunity-invitation-request.accept.jinja | 2 +- ...community-invitation-request.decline.jinja | 2 +- ...bcommunity-invitation-request.expire.jinja | 49 +++++++++++++++++ 5 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.expire.jinja diff --git a/invenio_communities/notifications/builders.py b/invenio_communities/notifications/builders.py index eca26ecfc..2a92b1bc4 100644 --- a/invenio_communities/notifications/builders.py +++ b/invenio_communities/notifications/builders.py @@ -224,13 +224,6 @@ class SubComInvitationBuilderBase(SubCommunityBuilderBase): type = "subcommunity-invitation-request" - context = [ - EntityResolve("request"), - EntityResolve("request.created_by"), - EntityResolve("request.receiver"), - # EntityResolve("executing_user") executing via script only for now - ] - recipients = [ CommunityMembersRecipient("request.created_by", roles=["owner", "manager"]), CommunityMembersRecipient("request.receiver", roles=["owner", "manager"]), @@ -242,6 +235,13 @@ class SubComInvitationCreate(SubComInvitationBuilderBase): type = f"{SubComInvitationBuilderBase.type}.create" + context = [ + EntityResolve("request"), + EntityResolve("request.created_by"), + EntityResolve("request.receiver"), + # EntityResolve("executing_user") creating via script only for now + ] + recipient_filters = [ UserPreferencesRecipientFilter(), # TODO: We exceptionally DON'T filter out the executing user here, because we @@ -254,10 +254,36 @@ class SubComInvitationCreate(SubComInvitationBuilderBase): class SubComInvitationAccept(SubComInvitationBuilderBase): """Notification builder for subcommunity request accept.""" + context = [ + EntityResolve("request"), + EntityResolve("request.created_by"), + EntityResolve("request.receiver"), + EntityResolve("executing_user"), + ] + type = f"{SubComInvitationBuilderBase.type}.accept" class SubComInvitationDecline(SubComInvitationBuilderBase): """Notification builder for subcommunity request decline.""" + context = [ + EntityResolve("request"), + EntityResolve("request.created_by"), + EntityResolve("request.receiver"), + EntityResolve("executing_user"), + ] + type = f"{SubComInvitationBuilderBase.type}.decline" + + +class SubComInvitationExpire(SubComInvitationBuilderBase): + """Notification builder for subcommunity invitation expire.""" + + context = [ + EntityResolve("request"), + EntityResolve("request.created_by"), + EntityResolve("request.receiver"), + ] + + type = f"{SubComInvitationBuilderBase.type}.expire" diff --git a/invenio_communities/subcommunities/services/request.py b/invenio_communities/subcommunities/services/request.py index 84b49b11c..ef1ee184f 100644 --- a/invenio_communities/subcommunities/services/request.py +++ b/invenio_communities/subcommunities/services/request.py @@ -10,6 +10,7 @@ from invenio_i18n import lazy_gettext as _ from invenio_notifications.services.uow import NotificationOp from invenio_requests.customizations import RequestType, actions +from marshmallow.exceptions import ValidationError import invenio_communities.notifications.builders as notifications from invenio_communities.proxies import current_communities @@ -80,6 +81,53 @@ class SubCommunityRequest(RequestType): } +class CreateSubcommunityInvitation(actions.CreateAndSubmitAction): + """Represents an accept action used to accept a subcommunity.""" + + def execute(self, identity, uow): + """Execute approve action.""" + parent = self.request.created_by.resolve() + if not parent.children.allow: + raise ValidationError("Assigned parent is not allowed to be a parent.") + super().execute(identity, uow) + + +class AcceptSubcommunityInvitation(actions.AcceptAction): + """Represents an accept action used to accept a subcommunity.""" + + def execute(self, identity, uow): + """Execute approve action.""" + child = self.request.receiver.resolve().id + parent = self.request.created_by.resolve().id + current_communities.service.bulk_update_parent( + system_identity, [child], parent_id=parent, uow=uow + ) + uow.register( + NotificationOp( + notifications.SubComInvitationAccept.build( + identity=identity, request=self.request + ) + ) + ) + super().execute(identity, uow) + + +class DeclineSubcommunityInvitation(actions.DeclineAction): + """Represents a decline action used to decline a subcommunity.""" + + def execute(self, identity, uow): + """Execute decline action.""" + # We override just to send a notification + uow.register( + NotificationOp( + notifications.SubComInvitationDecline.build( + identity=identity, request=self.request + ) + ) + ) + super().execute(identity, uow) + + class SubCommunityInvitationRequest(RequestType): """Request from a parent community to community to join.""" @@ -94,11 +142,11 @@ class SubCommunityInvitationRequest(RequestType): available_actions = { "delete": actions.DeleteAction, - "create": actions.CreateAndSubmitAction, "cancel": actions.CancelAction, # Custom implemented actions - "accept": AcceptSubcommunity, - "decline": DeclineSubcommunity, + "create": CreateSubcommunityInvitation, + "accept": AcceptSubcommunityInvitation, + "decline": DeclineSubcommunityInvitation, } needs_context = { diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja index 8fccdebbe..a350fff8a 100644 --- a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.accept.jinja @@ -18,7 +18,7 @@ {% set account_settings_link = "{ui}/account/settings/notifications".format(**msg_ctx) %} {%- block subject -%} -{{ _('✅ Invitation accepted for "{community_title}" to join the {parent_community_title} community').format(**msg_ctx) }} +{{ _('✅ Invitation accepted for {community_title} to join the {parent_community_title} community').format(**msg_ctx) }} {%- endblock subject -%} {%- block html_body -%} diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja index d8372e87d..c472cf1f9 100644 --- a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.decline.jinja @@ -18,7 +18,7 @@ {% set account_settings_link = "{ui}/account/settings/notifications".format(**msg_ctx) %} {%- block subject -%} -{{ _('⛔️ Subcommunity invitation declined for "{community_title}" to join "{parent_community_title}"').format(**msg_ctx) }} +{{ _('⛔️ Subcommunity invitation declined for {community_title} to join the {parent_community_title} community').format(**msg_ctx) }} {%- endblock subject -%} {%- block html_body -%} diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.expire.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.expire.jinja new file mode 100644 index 000000000..9fc3a0f11 --- /dev/null +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.expire.jinja @@ -0,0 +1,49 @@ +{% set request = notification.context.request %} +{% set parent_community = request.created_by %} +{% set community = request.receiver %} +{% set ui = config.SITE_UI_URL %} +{% set msg_ctx = { + "community_id": community.slug, + "community_title": community.metadata.title, + "parent_community_title": parent_community.metadata.title, + "request_id": request.id, + "ui": ui, +} %} + +{# TODO: use request.links.self_html when issue issue is resolved: https://github.com/inveniosoftware/invenio-rdm-records/issues/1327 #} +{% set request_link = "{ui}/communities/{community_id}/requests/{request_id}".format(**msg_ctx) %} +{% set account_settings_link = "{ui}/account/settings/notifications".format(**msg_ctx) %} + +{%- block subject -%} + {{ _("⌛️ The invitation for {community_title} to join the '{parent_community_title}' community expired").format(**msg_ctx) }} +{%- endblock subject -%} + +{%- block html_body -%} + + + + + + + + + + + + + +
{{ _("The invitation for '{community_title}' to join the '{parent_community_title}' community has expired.").format(**msg_ctx) }}
{{ _("Check out the invitation")}}
_
{{ _("This is an auto-generated message. To manage notifications, visit your")}} {{ _("account settings")}}.
+{%- endblock html_body %} + +{%- block plain_body -%} +{{ _("The invitation for {community_title} to join the '{parent_community_title}' community has expired.").format(**msg_ctx) }} + +{{ _("Check out the invitation:") }} {{ request_link }} +{%- endblock plain_body %} + +{# Markdown for Slack/Mattermost/chat #} +{%- block md_body -%} +{{ _("The invitation for *{community_title}* to join community *{parent_community_title}* has expired.").format(**msg_ctx) }} + +[{{ _("Check out the invitation") }}]({{ request_link }}) +{%- endblock md_body %} From c63efa8862416a12602f40ab1a8dc5d92cefa7d0 Mon Sep 17 00:00:00 2001 From: Carlin MacKenzie Date: Fri, 22 Nov 2024 16:10:27 +0100 Subject: [PATCH 4/5] service: add create_subcommunity_invitation_request refactor and fix bugs in sending emails --- invenio_communities/notifications/builders.py | 47 ++++++++++--------- .../subcommunities/services/request.py | 9 ++++ .../subcommunities/services/service.py | 28 ++++++++++- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/invenio_communities/notifications/builders.py b/invenio_communities/notifications/builders.py index 2a92b1bc4..4e5a293f3 100644 --- a/invenio_communities/notifications/builders.py +++ b/invenio_communities/notifications/builders.py @@ -224,6 +224,13 @@ class SubComInvitationBuilderBase(SubCommunityBuilderBase): type = "subcommunity-invitation-request" + context = [ + EntityResolve("request"), + EntityResolve("request.created_by"), + EntityResolve("request.receiver"), + EntityResolve("executing_user"), + ] + recipients = [ CommunityMembersRecipient("request.created_by", roles=["owner", "manager"]), CommunityMembersRecipient("request.receiver", roles=["owner", "manager"]), @@ -242,48 +249,46 @@ class SubComInvitationCreate(SubComInvitationBuilderBase): # EntityResolve("executing_user") creating via script only for now ] - recipient_filters = [ - UserPreferencesRecipientFilter(), - # TODO: We exceptionally DON'T filter out the executing user here, because we - # don't have a clear place where they can see the created request. - # See also: https://github.com/inveniosoftware/invenio-communities/issues/1158 - # UserRecipientFilter("executing_user"), + recipients = [ + CommunityMembersRecipient("request.receiver", roles=["owner", "manager"]), ] class SubComInvitationAccept(SubComInvitationBuilderBase): """Notification builder for subcommunity request accept.""" - context = [ - EntityResolve("request"), - EntityResolve("request.created_by"), - EntityResolve("request.receiver"), - EntityResolve("executing_user"), - ] - type = f"{SubComInvitationBuilderBase.type}.accept" + recipient_filters = [ + UserPreferencesRecipientFilter(), + # Don't send notifications to the user performing the action + UserRecipientFilter("executing_user"), + ] + class SubComInvitationDecline(SubComInvitationBuilderBase): """Notification builder for subcommunity request decline.""" - context = [ - EntityResolve("request"), - EntityResolve("request.created_by"), - EntityResolve("request.receiver"), - EntityResolve("executing_user"), - ] - type = f"{SubComInvitationBuilderBase.type}.decline" + recipient_filters = [ + UserPreferencesRecipientFilter(), + # Don't send notifications to the user performing the action + UserRecipientFilter("executing_user"), + ] + class SubComInvitationExpire(SubComInvitationBuilderBase): """Notification builder for subcommunity invitation expire.""" + type = f"{SubComInvitationBuilderBase.type}.expire" + context = [ EntityResolve("request"), EntityResolve("request.created_by"), EntityResolve("request.receiver"), ] - type = f"{SubComInvitationBuilderBase.type}.expire" + recipients = [ + CommunityMembersRecipient("request.receiver", roles=["owner", "manager"]), + ] diff --git a/invenio_communities/subcommunities/services/request.py b/invenio_communities/subcommunities/services/request.py index ef1ee184f..c4ed86bf2 100644 --- a/invenio_communities/subcommunities/services/request.py +++ b/invenio_communities/subcommunities/services/request.py @@ -89,6 +89,15 @@ def execute(self, identity, uow): parent = self.request.created_by.resolve() if not parent.children.allow: raise ValidationError("Assigned parent is not allowed to be a parent.") + + uow.register( + NotificationOp( + notifications.SubComInvitationCreate.build( + identity=identity, request=self.request + ) + ) + ) + super().execute(identity, uow) diff --git a/invenio_communities/subcommunities/services/service.py b/invenio_communities/subcommunities/services/service.py index bd15acb94..6d9e0a6b7 100644 --- a/invenio_communities/subcommunities/services/service.py +++ b/invenio_communities/subcommunities/services/service.py @@ -6,20 +6,22 @@ # it under the terms of the MIT License; see LICENSE file for more details. """Subcommunities service.""" +from datetime import datetime, timedelta, timezone + from invenio_i18n import gettext as _ from invenio_notifications.services.uow import NotificationOp from invenio_records_resources.services.base import LinksTemplate, Service -from invenio_records_resources.services.records.links import pagination_links from invenio_records_resources.services.records.schema import ServiceSchemaWrapper from invenio_records_resources.services.uow import unit_of_work +from invenio_requests import current_request_type_registry from invenio_requests.proxies import current_requests_service as requests_service -from invenio_search.engine import dsl from werkzeug.local import LocalProxy import invenio_communities.notifications.builders as notifications from invenio_communities.proxies import current_communities, current_roles from .errors import ParentChildrenNotAllowed +from .request import SubCommunityInvitationRequest community_service = LocalProxy(lambda: current_communities.service) @@ -150,3 +152,25 @@ def join(self, identity, id_, data, uow=None): # expandable_fields=self.expandable_fields, # expand=True, # ) + + @unit_of_work() + def create_subcommunity_invitation_request( + self, identity, parent_community_id, child_community_id, uow=None + ): + """Create and submit a SubCommunityInvitation request.""" + type_ = current_request_type_registry.lookup( + SubCommunityInvitationRequest.type_id + ) + parent_community = community_service.record_cls.pid.resolve(parent_community_id) + child_community = community_service.record_cls.pid.resolve(child_community_id) + expires_at = datetime.now(timezone.utc) + timedelta(days=30) + + requests_service.create( + identity, + {}, + type_, + creator=parent_community, + receiver=child_community, + expires_at=expires_at, + uow=uow, + ) From 22eb671e6e1cb0f0a9182eae16182bcddb07acf4 Mon Sep 17 00:00:00 2001 From: Carlin MacKenzie Date: Tue, 26 Nov 2024 14:48:27 +0100 Subject: [PATCH 5/5] service: add data to inivitation req delete unused file and add expiry to email --- .../subcommunities/services/service.py | 8 ++-- .../subcommunity-invitation/index.html | 47 ------------------- ...bcommunity-invitation-request.create.jinja | 6 +++ 3 files changed, 10 insertions(+), 51 deletions(-) delete mode 100644 invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html diff --git a/invenio_communities/subcommunities/services/service.py b/invenio_communities/subcommunities/services/service.py index 6d9e0a6b7..3d5a7eaa6 100644 --- a/invenio_communities/subcommunities/services/service.py +++ b/invenio_communities/subcommunities/services/service.py @@ -155,7 +155,7 @@ def join(self, identity, id_, data, uow=None): @unit_of_work() def create_subcommunity_invitation_request( - self, identity, parent_community_id, child_community_id, uow=None + self, identity, parent_community_id, child_community_id, data, uow=None ): """Create and submit a SubCommunityInvitation request.""" type_ = current_request_type_registry.lookup( @@ -166,9 +166,9 @@ def create_subcommunity_invitation_request( expires_at = datetime.now(timezone.utc) + timedelta(days=30) requests_service.create( - identity, - {}, - type_, + identity=identity, + data=data, + request_type=type_, creator=parent_community, receiver=child_community, expires_at=expires_at, diff --git a/invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html b/invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html deleted file mode 100644 index ee6c0cf3e..000000000 --- a/invenio_communities/templates/semantic-ui/invenio_communities/details/subcommunity-invitation/index.html +++ /dev/null @@ -1,47 +0,0 @@ -{# -*- coding: utf-8 -*- - -This file is part of Invenio. -Copyright (C) 2024 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. -#} - -{% extends "invenio_communities/details/base.html" %} - -{% set active_community_header_menu_item= 'browse' %} -{% set search_endpoint = community["links"]["subcommunities"] %} -{%- set title = community.metadata.title + _(" subcommunities") -%} - -{%- block javascript %} -{{ super() }} -{{ webpack['invenio-communities-subcommunities.js'] }} -{%- endblock %} - -{%- block page_body %} -{{ super() }} -
-
-

{{ config.get("APP_RDM_SUBCOMMUNITIES_LABEL", _("Subcommunities")) }}

-
-
- - - - -{%- endblock page_body %} diff --git a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja index 2a320abe9..bd60c3ff2 100644 --- a/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja +++ b/invenio_communities/templates/semantic-ui/invenio_notifications/subcommunity-invitation-request.create.jinja @@ -8,6 +8,7 @@ "parent_community_id": parent_community.slug, "parent_community_title": parent_community.metadata.title, "request_id": request.id, + "expires_at": request.expires_at | from_isodatetime | dateformat(format="long"), "ui": ui, } %} @@ -28,6 +29,9 @@ {{ _("View the request")}} + + {{ _('This invitation will expire on {expires_at}.').format(**msg_ctx) }} + @@ -42,6 +46,7 @@ {{ _("View the request at:") }} {{ request_link }} +{{ _('This invitation will expire on {expires_at}.').format(**msg_ctx) }} — {{ _("This is an auto-generated message. To manage notifications, visit your account settings at: ") }}{{ account_settings_link }} {%- endblock plain_body %} @@ -52,6 +57,7 @@ [{{ _("View the request") }}]({{ request_link }}) +{{ _('This invitation will expire on {expires_at}.').format(**msg_ctx) }} — {{ _("This is an auto-generated message. To manage notifications, visit your") }} [{{ _("account settings") }}]({{ account_settings_link }}). {%- endblock md_body %}