Skip to content

Commit c6dae8f

Browse files
committed
membership-requests [inveniosoftware#855]: notify on membership-request changes
1 parent 0496277 commit c6dae8f

10 files changed

+686
-16
lines changed

Diff for: invenio_communities/members/services/request.py

+24-4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
CommunityInvitationCancelNotificationBuilder,
2020
CommunityInvitationDeclineNotificationBuilder,
2121
CommunityInvitationExpireNotificationBuilder,
22+
CommunityMembershipRequestAcceptNotificationBuilder,
23+
CommunityMembershipRequestCancelNotificationBuilder,
24+
CommunityMembershipRequestDeclineNotificationBuilder,
25+
CommunityMembershipRequestExpireNotificationBuilder,
2226
)
2327

2428
from ...proxies import current_communities
@@ -142,7 +146,11 @@ class CancelMembershipRequestAction(actions.CancelAction):
142146
def execute(self, identity, uow):
143147
"""Execute action."""
144148
service().close_member_request(system_identity, self.request.id, uow=uow)
145-
# TODO: Notification flow: Investigate notifications
149+
uow.register(
150+
NotificationOp(
151+
CommunityMembershipRequestCancelNotificationBuilder.build(self.request)
152+
)
153+
)
146154
super().execute(identity, uow)
147155

148156

@@ -152,7 +160,11 @@ class DeclineMembershipRequestAction(actions.DeclineAction):
152160
def execute(self, identity, uow):
153161
"""Execute action."""
154162
service().close_member_request(system_identity, self.request.id, uow=uow)
155-
# TODO: Notification flow: Investigate notifications
163+
uow.register(
164+
NotificationOp(
165+
CommunityMembershipRequestDeclineNotificationBuilder.build(self.request)
166+
)
167+
)
156168
super().execute(identity, uow)
157169

158170

@@ -165,7 +177,11 @@ class ExpireMembershipRequestAction(actions.ExpireAction):
165177
def execute(self, identity, uow):
166178
"""Execute action."""
167179
service().close_member_request(system_identity, self.request.id, uow=uow)
168-
# TODO: Notification flow: Investigate notifications
180+
uow.register(
181+
NotificationOp(
182+
CommunityMembershipRequestExpireNotificationBuilder.build(self.request)
183+
)
184+
)
169185
super().execute(identity, uow)
170186

171187

@@ -175,7 +191,11 @@ class AcceptMembershipRequestAction(actions.AcceptAction):
175191
def execute(self, identity, uow):
176192
"""Execute action."""
177193
service().accept_member_request(system_identity, self.request.id, uow=uow)
178-
# TODO: Notification flow: Investigate notifications
194+
uow.register(
195+
NotificationOp(
196+
CommunityMembershipRequestAcceptNotificationBuilder.build(self.request)
197+
)
198+
)
179199
super().execute(identity, uow)
180200

181201

Diff for: invenio_communities/members/services/service.py

+16-12
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
from sqlalchemy.exc import IntegrityError
3636
from werkzeug.local import LocalProxy
3737

38-
from ...notifications.builders import CommunityInvitationSubmittedNotificationBuilder
38+
from ...notifications.builders import (
39+
CommunityInvitationSubmittedNotificationBuilder,
40+
CommunityMembershipRequestSubmittedNotificationBuilder,
41+
)
3942
from ...proxies import current_roles
4043
from ..errors import AlreadyMemberError, InvalidMemberError
4144
from ..records.api import ArchivedInvitation
@@ -839,22 +842,23 @@ def request_membership(self, identity, community_id, data, uow=None):
839842
)
840843

841844
# TODO: Notification flow: Add notification mechanism
842-
# uow.register(
843-
# NotificationOp(
844-
# MembershipRequestSubmittedNotificationBuilder.build(
845-
# request=request_item._request,
846-
# # explicit string conversion to get the value of LazyText
847-
# role=str(role.title),
848-
# message=message,
849-
# )
850-
# )
851-
# )
845+
role = current_roles["reader"]
846+
uow.register(
847+
NotificationOp(
848+
CommunityMembershipRequestSubmittedNotificationBuilder.build(
849+
request=request_item._record,
850+
# explicit string conversion to get the value of LazyText
851+
role=str(role.title),
852+
message=message,
853+
)
854+
)
855+
)
852856

853857
# Create an inactive member entry linked to the request.
854858
self._add_factory(
855859
identity,
856860
community=community,
857-
role=current_roles["reader"],
861+
role=role,
858862
visible=False,
859863
member={"type": "user", "id": str(identity.user.id)},
860864
message=message,

Diff for: invenio_communities/notifications/builders.py

+86
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,89 @@ class SubCommunityDecline(SubCommunityBuilderBase):
216216
"""Notification builder for subcommunity request decline."""
217217

218218
type = f"{SubCommunityBuilderBase.type}.decline"
219+
220+
221+
class MembershipRequestBaseNotificationBuilder(BaseNotificationBuilder):
222+
"""Base membership request notification builder."""
223+
type = "community-membership-request"
224+
225+
@classmethod
226+
def build(cls, request, message=None):
227+
"""Build notification with request context."""
228+
return Notification(
229+
type=cls.type,
230+
context={
231+
"request": EntityResolverRegistry.reference_entity(request),
232+
},
233+
)
234+
235+
236+
class CommunityMembershipRequestSubmittedNotificationBuilder(MembershipRequestBaseNotificationBuilder):
237+
"""Notification builder for community membership request submission."""
238+
239+
# identifier
240+
type = f"{MembershipRequestBaseNotificationBuilder.type}.submit"
241+
recipients = [
242+
CommunityMembersRecipient(key="request.receiver", roles=["owner", "manager"]),
243+
]
244+
245+
@classmethod
246+
def build(cls, request, role, message=None):
247+
"""Build notification with request context."""
248+
return Notification(
249+
type=cls.type,
250+
context={
251+
"request": EntityResolverRegistry.reference_entity(request),
252+
"role": role,
253+
"message": message,
254+
},
255+
)
256+
257+
258+
class CommunityMembershipRequestCancelNotificationBuilder(
259+
MembershipRequestBaseNotificationBuilder
260+
):
261+
"""Notification builder for community membership request cancel action."""
262+
263+
# identifier
264+
type = f"{MembershipRequestBaseNotificationBuilder.type}.cancel"
265+
recipients = [
266+
CommunityMembersRecipient(key="request.receiver", roles=["owner", "manager"]),
267+
]
268+
269+
270+
class CommunityMembershipRequestDeclineNotificationBuilder(
271+
MembershipRequestBaseNotificationBuilder
272+
):
273+
"""Notification builder for community membership request decline action."""
274+
275+
# identifier
276+
type = f"{MembershipRequestBaseNotificationBuilder.type}.decline"
277+
recipients = [
278+
UserRecipient(key="request.created_by"),
279+
]
280+
281+
282+
class CommunityMembershipRequestExpireNotificationBuilder(
283+
MembershipRequestBaseNotificationBuilder
284+
):
285+
"""Notification builder for community membership request expire action."""
286+
287+
# identifier
288+
type = f"{MembershipRequestBaseNotificationBuilder.type}.expire"
289+
recipients = [
290+
CommunityMembersRecipient(key="request.receiver", roles=["owner", "manager"]),
291+
UserRecipient(key="request.created_by"),
292+
]
293+
294+
295+
class CommunityMembershipRequestAcceptNotificationBuilder(
296+
MembershipRequestBaseNotificationBuilder
297+
):
298+
"""Notification builder for community membership request accept action."""
299+
300+
# identifier
301+
type = f"{MembershipRequestBaseNotificationBuilder.type}.accept"
302+
recipients = [
303+
UserRecipient(key="request.created_by"),
304+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{% set request = notification.context.request %}
2+
{% set community = request.receiver %}
3+
{% set created_by = request.created_by %}
4+
{% set request_id = request.id %}
5+
{# TODO: Action-based notifications don't pass `message` so this will always be empty #}
6+
{% set message = notification.context.message | safe if notification.context.message else '' %}
7+
{% set community_title = community.metadata.title %}
8+
{# This email is sent to the requester only so omitted requester's name #}
9+
10+
{# TODO: use request.links.self_html when this issue is resolved: https://github.com/inveniosoftware/invenio-rdm-records/issues/1327 #}
11+
{% set request_link = "{ui}/me/requests/{id}".format(ui=config.SITE_UI_URL, id=request_id) %}
12+
{# "/account/settings/notifications" is hardcoded in invenio-notifications
13+
and not publicly exposed so ok to refer to it directly for now #}
14+
{% set account_settings_link = "{ui}/account/settings/notifications".format(ui=config.SITE_UI_URL) %}
15+
16+
{%- block subject -%}
17+
{{ _("✅ Request to join the community '{community_title}' was accepted").format(community_title=community_title) }}
18+
{%- endblock subject -%}
19+
20+
{%- block html_body -%}
21+
<table style="font-family:'Lato',Helvetica,Arial,sans-serif;border-spacing:15px">
22+
<tr>
23+
<td>{{ _("The membership request to join the community '{community_title}' was accepted").format(community_title=community_title) }}
24+
{% if message %}{{ _(" with the following message:")}}{% endif %}
25+
</td>
26+
</tr>
27+
<tr>
28+
{% if message %}
29+
<td><em>"{{message}}"</em></td>
30+
{% endif %}
31+
</tr>
32+
<tr>
33+
<td><a href="{{ request_link }}" class="button">{{ _("Check out the membership request")}}</a></td>
34+
</tr>
35+
<tr>
36+
<td><strong>_</strong></td>
37+
</tr>
38+
<tr>
39+
<td style="font-size:smaller">{{ _("This is an auto-generated message. To manage notifications, visit your")}} <a href="{{account_settings_link}}">{{ _("account settings")}}</a>.</td>
40+
</tr>
41+
</table>
42+
{%- endblock html_body %}
43+
44+
{%- block plain_body -%}
45+
{{ _("The membership request to join the community '{community_title}' was accepted").format(community_title=community_title) }}
46+
{% if message %}{{ _("with the following message:")}} {{message}}{% endif %}
47+
48+
{{ _("Check out the membership request:") }} {{ request_link }}
49+
{%- endblock plain_body %}
50+
51+
{# Markdown for Slack/Mattermost/chat #}
52+
{%- block md_body -%}
53+
{{ _("The membership request to join the community *{community_title}* was accepted").format(community_title=community_title) }}
54+
{% if message %}{{ _("with the following message:")}} {{ message }}{% endif %}
55+
56+
[{{ _("Check out the membership request") }}]({{ request_link }})
57+
{%- endblock md_body %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{% set request = notification.context.request %}
2+
{% set community = request.receiver %}
3+
{% set created_by = request.created_by %}
4+
{% set requester_name = created_by.username or created_by.profile.full_name %}
5+
{% set request_id = request.id %}
6+
{% set message = notification.context.message | safe if notification.context.message else '' %}
7+
{% set community_title = community.metadata.title %}
8+
9+
{# WARNING: this uses a config set by invenio-app-rdm (not a dependency) to at least not hardcode the URL #}
10+
{# invenio-app-rdm should have been using REQUESTS_ROUTES a config of a dependency however. #}
11+
{% set request_link_path = config.RDM_REQUESTS_ROUTES["community-dashboard-request-details"].lstrip("/") %}
12+
{% set request_link_path = request_link_path.replace("<pid_value>", community.id).replace("<request_pid_value>", request_id) %}
13+
{% set request_link = "{ui}/{request_link_path}".format(ui=config.SITE_UI_URL, request_link_path=request_link_path) %}
14+
15+
{%- block subject -%}
16+
{{ _("❌ Request for '@{requester_name}' to join the community '{community_title}' was cancelled").format(requester_name=requester_name, community_title=community_title) }}
17+
{%- endblock subject -%}
18+
19+
{%- block html_body -%}
20+
<table style="font-family:'Lato',Helvetica,Arial,sans-serif;border-spacing:15px">
21+
<tr>
22+
<td>{{ _("The membership request for '@{requester_name}' to join the community '{community_title}' was cancelled").format(requester_name=requester_name, community_title=community_title) }}
23+
{% if message %}{{ _(" with the following message:")}}{% endif %}
24+
</td>
25+
</tr>
26+
<tr>
27+
{% if message %}
28+
<td><em>"{{message}}"</em></td>
29+
{% endif %}
30+
</tr>
31+
<tr>
32+
<td><a href="{{ request_link }}" class="button">{{ _("Check out the membership request")}}</a></td>
33+
</tr>
34+
<tr>
35+
<td><strong>_</strong></td>
36+
</tr>
37+
<tr>
38+
<td style="font-size:smaller">{{ _("This is an auto-generated message. To manage notifications, visit your")}} <a href="{{account_settings_link}}">{{ _("account settings")}}</a>.</td>
39+
</tr>
40+
</table>
41+
{%- endblock html_body %}
42+
43+
{%- block plain_body -%}
44+
{{ _("The membership request for '@{requester_name}' to join the community '{community_title}' was cancelled").format(requester_name=requester_name, community_title=community_title) }}
45+
{% if message %}{{ _(" with the following message:")}} {{ message }}{% endif %}
46+
47+
{{ _("Check out the membership request:") }} {{ request_link }}
48+
{%- endblock plain_body %}
49+
50+
{# Markdown for Slack/Mattermost/chat #}
51+
{%- block md_body -%}
52+
{{ _("The membership_request for *@{requester_name}* to join the community *{community_title}* was cancelled").format(requester_name=requester_name, community_title=community_title) }}
53+
{% if message %}{{ _("with the following message:")}} {{message}}{% endif %}
54+
55+
[{{ _("Check out the membership request") }}]({{ request_link }})
56+
{%- endblock md_body %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
{% set request = notification.context.request %}
2+
{% set community = request.receiver %}
3+
{% set created_by = request.created_by %}
4+
{% set request_id = request.id %}
5+
{# TODO: Action-based notifications don't pass `message` so this will always be empty #}
6+
{% set message = notification.context.message | safe if notification.context.message else '' %}
7+
{% set community_title = community.metadata.title %}
8+
{# This email is sent to the requester only so omitted requester's name #}
9+
10+
{# TODO: use request.links.self_html when this issue is resolved: https://github.com/inveniosoftware/invenio-rdm-records/issues/1327 #}
11+
{% set request_link = "{ui}/me/requests/{id}".format(ui=config.SITE_UI_URL, id=request_id) %}
12+
{# "/account/settings/notifications" is hardcoded in invenio-notifications
13+
and not publicly exposed so ok to refer to it directly for now #}
14+
{% set account_settings_link = "{ui}/account/settings/notifications".format(ui=config.SITE_UI_URL) %}
15+
16+
{%- block subject -%}
17+
{{ _("⛔️ Request to join the community '{community_title}' was declined").format(community_title=community_title) }}
18+
{%- endblock subject -%}
19+
20+
{%- block html_body -%}
21+
<table style="font-family:'Lato',Helvetica,Arial,sans-serif;border-spacing:15px">
22+
<tr>
23+
<td>{{ _("The membership request to join the community '{community_title}' was declined").format(community_title=community_title) }}
24+
{% if message %}{{ _(" with the following message:")}}{% endif %}
25+
</td>
26+
</tr>
27+
<tr>
28+
{% if message %}
29+
<td><em>"{{message}}"</em></td>
30+
{% endif %}
31+
</tr>
32+
<tr>
33+
<td><a href="{{ request_link }}" class="button">{{ _("Check out the membership request")}}</a></td>
34+
</tr>
35+
<tr>
36+
<td><strong>_</strong></td>
37+
</tr>
38+
<tr>
39+
<td style="font-size:smaller">{{ _("This is an auto-generated message. To manage notifications, visit your")}} <a href="{{account_settings_link}}">{{ _("account settings")}}</a>.</td>
40+
</tr>
41+
</table>
42+
{%- endblock html_body %}
43+
44+
{%- block plain_body -%}
45+
{{ _("The membership request to join the community '{community_title}' was declined").format(community_title=community_title) }}
46+
{% if message %}{{ _("with the following message:")}} {{message}}{% endif %}
47+
48+
{{ _("Check out the membership request:") }} {{ request_link }}
49+
{%- endblock plain_body %}
50+
51+
{# Markdown for Slack/Mattermost/chat #}
52+
{%- block md_body -%}
53+
{{ _("The membership request to join the community *{community_title}* was declined").format(community_title=community_title) }}
54+
{% if message %}{{ _("with the following message:")}} {{ message }}{% endif %}
55+
56+
[{{ _("Check out the membership request") }}]({{ request_link }})
57+
{%- endblock md_body %}

0 commit comments

Comments
 (0)