Skip to content

Commit 25ce6c2

Browse files
fix: refactor notification subscriptions and digest handling
1 parent ad6943a commit 25ce6c2

File tree

10 files changed

+343
-125
lines changed

10 files changed

+343
-125
lines changed

api/subscriptions/views.py

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.db.models import Value, When, Case, Q, OuterRef, Subquery
1+
from django.db.models import Value, When, Case, OuterRef, Subquery
22
from django.db.models.fields import CharField, IntegerField
33
from django.db.models.functions import Concat, Cast
44
from django.contrib.contenttypes.models import ContentType
@@ -24,6 +24,7 @@
2424
RegistrationProvider,
2525
AbstractProvider,
2626
AbstractNode,
27+
Guid,
2728
)
2829
from osf.models.notification_type import NotificationType
2930
from osf.models.notification_subscription import NotificationSubscription
@@ -44,47 +45,72 @@ class SubscriptionList(JSONAPIBaseView, generics.ListAPIView, ListFilterMixin):
4445

4546
def get_queryset(self):
4647
user_guid = self.request.user._id
47-
provider_ct = ContentType.objects.get(app_label='osf', model='abstractprovider')
4848

4949
node_subquery = AbstractNode.objects.filter(
5050
id=Cast(OuterRef('object_id'), IntegerField()),
5151
).values('guids___id')[:1]
5252

53+
_global_file_updated = [
54+
NotificationType.Type.USER_FILE_UPDATED.value,
55+
NotificationType.Type.FILE_ADDED.value,
56+
NotificationType.Type.FILE_REMOVED.value,
57+
NotificationType.Type.ADDON_FILE_COPIED.value,
58+
NotificationType.Type.ADDON_FILE_RENAMED.value,
59+
NotificationType.Type.ADDON_FILE_MOVED.value,
60+
NotificationType.Type.ADDON_FILE_REMOVED.value,
61+
NotificationType.Type.FOLDER_CREATED.value,
62+
]
63+
_global_reviews = [
64+
NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value,
65+
NotificationType.Type.PROVIDER_REVIEWS_SUBMISSION_CONFIRMATION.value,
66+
NotificationType.Type.PROVIDER_REVIEWS_RESUBMISSION_CONFIRMATION.value,
67+
NotificationType.Type.PROVIDER_NEW_PENDING_WITHDRAW_REQUESTS.value,
68+
NotificationType.Type.REVIEWS_SUBMISSION_STATUS.value,
69+
]
70+
_node_file_updated = [
71+
NotificationType.Type.NODE_FILE_UPDATED.value,
72+
NotificationType.Type.FILE_ADDED.value,
73+
NotificationType.Type.FILE_REMOVED.value,
74+
NotificationType.Type.ADDON_FILE_COPIED.value,
75+
NotificationType.Type.ADDON_FILE_RENAMED.value,
76+
NotificationType.Type.ADDON_FILE_MOVED.value,
77+
NotificationType.Type.ADDON_FILE_REMOVED.value,
78+
NotificationType.Type.FOLDER_CREATED.value,
79+
]
80+
5381
qs = NotificationSubscription.objects.filter(
54-
notification_type__in=[
55-
NotificationType.Type.USER_FILE_UPDATED.instance,
56-
NotificationType.Type.NODE_FILE_UPDATED.instance,
57-
NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance,
58-
],
82+
notification_type__name__in=[
83+
NotificationType.Type.USER_FILE_UPDATED.value,
84+
NotificationType.Type.NODE_FILE_UPDATED.value,
85+
NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value,
86+
] + _global_reviews + _global_file_updated + _node_file_updated,
5987
user=self.request.user,
6088
).annotate(
6189
event_name=Case(
6290
When(
63-
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
91+
notification_type__name__in=_node_file_updated,
6492
then=Value('files_updated'),
6593
),
6694
When(
67-
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
95+
notification_type__name__in=_global_file_updated,
6896
then=Value('global_file_updated'),
6997
),
7098
When(
71-
Q(notification_type=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance) &
72-
Q(content_type=provider_ct),
99+
notification_type__name__in=_global_reviews,
73100
then=Value('global_reviews'),
74101
),
75102
),
76103
legacy_id=Case(
77104
When(
78-
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
105+
notification_type__name__in=_node_file_updated,
79106
then=Concat(Subquery(node_subquery), Value('_file_updated')),
80107
),
81108
When(
82-
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
109+
notification_type__name__in=_global_file_updated,
83110
then=Value(f'{user_guid}_global_file_updated'),
84111
),
85112
When(
86-
Q(notification_type=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance) &
87-
Q(content_type=provider_ct),
113+
notification_type__name__in=_global_reviews,
88114
then=Value(f'{user_guid}_global_reviews'),
89115
),
90116
),
@@ -182,7 +208,7 @@ def update(self, request, *args, **kwargs):
182208
NotificationType.Type.ADDON_FILE_REMOVED.value,
183209
NotificationType.Type.FOLDER_CREATED.value,
184210
],
185-
)
211+
).exclude(content_type=ContentType.objects.get_for_model(AbstractNode))
186212
if not qs.exists():
187213
raise PermissionDenied
188214

@@ -211,6 +237,34 @@ def update(self, request, *args, **kwargs):
211237
serializer.is_valid(raise_exception=True)
212238
self.perform_update(serializer)
213239
return Response(serializer.data)
240+
elif '_files_updated' in self.kwargs['subscription_id']:
241+
# Copy _files_updated subscription changes to all node file subscriptions
242+
node_id = Guid.load(self.kwargs['subscription_id'].split('_files_updated')[0]).object_id
243+
244+
qs = NotificationSubscription.objects.filter(
245+
user=self.request.user,
246+
content_type=ContentType.objects.get_for_model(AbstractNode),
247+
object_id=node_id,
248+
notification_type__name__in=[
249+
NotificationType.Type.NODE_FILE_UPDATED.value,
250+
NotificationType.Type.FILE_ADDED.value,
251+
NotificationType.Type.FILE_REMOVED.value,
252+
NotificationType.Type.ADDON_FILE_COPIED.value,
253+
NotificationType.Type.ADDON_FILE_RENAMED.value,
254+
NotificationType.Type.ADDON_FILE_MOVED.value,
255+
NotificationType.Type.ADDON_FILE_REMOVED.value,
256+
NotificationType.Type.FOLDER_CREATED.value,
257+
],
258+
)
259+
if not qs.exists():
260+
raise PermissionDenied
261+
262+
for instance in qs:
263+
serializer = self.get_serializer(instance=instance, data=request.data, partial=True)
264+
serializer.is_valid(raise_exception=True)
265+
self.perform_update(serializer)
266+
return Response(serializer.data)
267+
214268
else:
215269
return super().update(request, *args, **kwargs)
216270

notifications/file_event_notifications.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def perform(self):
5555
name=self.action
5656
).emit(
5757
user=self.user,
58+
subscribed_object=self.node,
5859
event_context={
5960
'user_fullname': self.user.fullname,
6061
'profile_image_url': self.profile_image_url,

notifications/listeners.py

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
@project_created.connect
1111
def subscribe_creator(resource):
12-
from osf.models import NotificationSubscription, NotificationType
12+
from osf.models import NotificationSubscription, NotificationType, Preprint
1313

1414
from django.contrib.contenttypes.models import ContentType
1515

@@ -30,24 +30,25 @@ def subscribe_creator(resource):
3030
)
3131
except NotificationSubscription.MultipleObjectsReturned:
3232
pass
33-
try:
34-
NotificationSubscription.objects.get_or_create(
35-
user=user,
36-
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
37-
object_id=resource.id,
38-
content_type=ContentType.objects.get_for_model(resource),
39-
_is_digest=True,
40-
defaults={
41-
'message_frequency': 'instantly',
42-
}
43-
)
44-
except NotificationSubscription.MultipleObjectsReturned:
45-
pass
33+
if not isinstance(resource, Preprint):
34+
try:
35+
NotificationSubscription.objects.get_or_create(
36+
user=user,
37+
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
38+
object_id=resource.id,
39+
content_type=ContentType.objects.get_for_model(resource),
40+
_is_digest=True,
41+
defaults={
42+
'message_frequency': 'instantly',
43+
}
44+
)
45+
except NotificationSubscription.MultipleObjectsReturned:
46+
pass
4647

4748
@contributor_added.connect
4849
def subscribe_contributor(resource, contributor, auth=None, *args, **kwargs):
4950
from django.contrib.contenttypes.models import ContentType
50-
from osf.models import NotificationSubscription, NotificationType
51+
from osf.models import NotificationSubscription, NotificationType, Preprint
5152

5253
from osf.models import Node
5354
if isinstance(resource, Node):
@@ -67,19 +68,20 @@ def subscribe_contributor(resource, contributor, auth=None, *args, **kwargs):
6768
)
6869
except NotificationSubscription.MultipleObjectsReturned:
6970
pass
70-
try:
71-
NotificationSubscription.objects.get_or_create(
72-
user=contributor,
73-
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
74-
object_id=resource.id,
75-
content_type=ContentType.objects.get_for_model(resource),
76-
_is_digest=True,
77-
defaults={
78-
'message_frequency': 'instantly',
79-
}
80-
)
81-
except NotificationSubscription.MultipleObjectsReturned:
82-
pass
71+
if not isinstance(resource, Preprint):
72+
try:
73+
NotificationSubscription.objects.get_or_create(
74+
user=contributor,
75+
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
76+
object_id=resource.id,
77+
content_type=ContentType.objects.get_for_model(resource),
78+
_is_digest=True,
79+
defaults={
80+
'message_frequency': 'instantly',
81+
}
82+
)
83+
except NotificationSubscription.MultipleObjectsReturned:
84+
pass
8385

8486

8587
# Handle email notifications to notify moderators of new submissions.

0 commit comments

Comments
 (0)