Skip to content

Commit 0b7fe79

Browse files
authored
Merge pull request #11329 from Johnetordoff/fix/update-v2-subscriptions
[ENG-9005] Fix/update v2 subscriptions
2 parents fd453ec + caca24c commit 0b7fe79

File tree

5 files changed

+61
-43
lines changed

5 files changed

+61
-43
lines changed

api/subscriptions/serializers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ def get_absolute_url(self, obj):
3737
return obj.absolute_api_v2_url
3838

3939
def update(self, instance, validated_data):
40-
instance.message_frequency = validated_data.get['frequency']
40+
freq = validated_data.get('message_frequency')
41+
if freq is None:
42+
freq = validated_data.get('frequency')
43+
instance.message_frequency = freq
44+
instance.save()
4145
return instance
4246

4347

api/subscriptions/views.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.db.models import Value, When, Case, F, Q, OuterRef, Subquery
1+
from django.db.models import Value, When, Case, Q, 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
@@ -56,35 +56,38 @@ def get_queryset(self):
5656
id=Cast(OuterRef('object_id'), IntegerField()),
5757
).values('guids___id')[:1]
5858

59-
return NotificationSubscription.objects.filter(user=self.request.user).annotate(
59+
return NotificationSubscription.objects.filter(
60+
notification_type__in=[
61+
NotificationType.Type.USER_FILE_UPDATED.instance,
62+
NotificationType.Type.NODE_FILE_UPDATED.instance,
63+
NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance,
64+
],
65+
user=self.request.user,
66+
).annotate(
6067
event_name=Case(
6168
When(
62-
notification_type__name=NotificationType.Type.NODE_FILES_UPDATED.value,
69+
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
6370
then=Value('files_updated'),
6471
),
6572
When(
66-
notification_type__name=NotificationType.Type.USER_FILE_UPDATED.value,
67-
then=Value('global_file_updated'),
73+
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
74+
then=Value(f'{user_guid}_global_file_updated'),
6875
),
69-
default=F('notification_type__name'),
70-
output_field=CharField(),
7176
),
7277
legacy_id=Case(
7378
When(
74-
notification_type__name=NotificationType.Type.NODE_FILES_UPDATED.value,
79+
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
7580
then=Concat(Subquery(node_subquery), Value('_file_updated')),
7681
),
7782
When(
78-
notification_type__name=NotificationType.Type.USER_FILE_UPDATED.value,
79-
then=Value(f'{user_guid}_global'),
83+
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
84+
then=Value(f'{user_guid}_global_file_updated'),
8085
),
8186
When(
82-
Q(notification_type__name=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value) &
87+
Q(notification_type=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance) &
8388
Q(content_type=provider_ct),
8489
then=Concat(Subquery(provider_subquery), Value('_new_pending_submissions')),
8590
),
86-
default=F('notification_type__name'),
87-
output_field=CharField(),
8891
),
8992
)
9093

@@ -137,7 +140,11 @@ def get_object(self):
137140
obj_filter = Q(
138141
object_id=getattr(subscription_obj, 'id', None),
139142
content_type=ContentType.objects.get_for_model(subscription_obj.__class__),
140-
notification_type__name__icontains=event,
143+
notification_type__in=[
144+
NotificationType.Type.USER_FILE_UPDATED.instance,
145+
NotificationType.Type.NODE_FILE_UPDATED.instance,
146+
NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance,
147+
],
141148
)
142149
else:
143150
obj_filter = Q()
@@ -146,13 +153,13 @@ def get_object(self):
146153
obj = NotificationSubscription.objects.annotate(
147154
legacy_id=Case(
148155
When(
149-
notification_type__name=NotificationType.Type.NODE_FILES_UPDATED.value,
156+
notification_type__name=NotificationType.Type.NODE_FILE_UPDATED.value,
150157
content_type=node_ct,
151158
then=Concat(Subquery(node_subquery), Value('_file_updated')),
152159
),
153160
When(
154161
notification_type__name=NotificationType.Type.USER_FILE_UPDATED.value,
155-
then=Value(f'{user_guid}_global'),
162+
then=Value(f'{user_guid}_global_file_updated'),
156163
),
157164
When(
158165
notification_type__name=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value,

api_tests/subscriptions/views/test_subscriptions_detail.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import pytest
2+
from django.contrib.contenttypes.models import ContentType
23

34
from api.base.settings.defaults import API_BASE
5+
from osf.models import NotificationType
46
from osf_tests.factories import (
57
AuthUserFactory,
68
NotificationSubscriptionFactory
@@ -19,7 +21,12 @@ def user_no_auth(self):
1921

2022
@pytest.fixture()
2123
def notification(self, user):
22-
return NotificationSubscriptionFactory(user=user)
24+
return NotificationSubscriptionFactory(
25+
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
26+
object_id=user.id,
27+
content_type_id=ContentType.objects.get_for_model(user).id,
28+
user=user
29+
)
2330

2431
@pytest.fixture()
2532
def url(self, notification):
@@ -75,7 +82,7 @@ def test_subscription_detail_valid_user(
7582
res = app.get(url, auth=user.auth)
7683
notification_id = res.json['data']['id']
7784
assert res.status_code == 200
78-
assert notification_id == f'{user._id}_global'
85+
assert notification_id == f'{user._id}_global_file_updated'
7986

8087
def test_subscription_detail_invalid_notification_id_no_user(
8188
self, app, user, user_no_auth, notification, url, url_invalid, payload, payload_invalid

api_tests/subscriptions/views/test_subscriptions_list.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from django.contrib.contenttypes.models import ContentType
23

34
from api.base.settings.defaults import API_BASE
45
from osf.models.notification_type import NotificationType
@@ -31,21 +32,26 @@ def node(self, user):
3132
def global_user_notification(self, user):
3233
return NotificationSubscriptionFactory(
3334
notification_type=NotificationType.Type.USER_FILE_UPDATED.instance,
35+
object_id=user.id,
36+
content_type_id=ContentType.objects.get_for_model(user).id,
3437
user=user,
3538
)
3639

3740
@pytest.fixture()
3841
def file_updated_notification(self, node, user):
3942
return NotificationSubscriptionFactory(
40-
notification_type=NotificationType.Type.NODE_FILES_UPDATED.instance,
41-
subscribed_object=node,
43+
notification_type=NotificationType.Type.NODE_FILE_UPDATED.instance,
44+
object_id=node.id,
45+
content_type_id=ContentType.objects.get_for_model(node).id,
4246
user=user,
4347
)
4448

4549
@pytest.fixture()
4650
def provider_notification(self, provider, user):
4751
return NotificationSubscriptionFactory(
4852
notification_type=NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.instance,
53+
object_id=provider.id,
54+
content_type_id=ContentType.objects.get_for_model(provider).id,
4955
subscribed_object=provider,
5056
user=user,
5157
)
@@ -60,16 +66,14 @@ def test_list_complete(
6066
user,
6167
provider,
6268
node,
63-
global_user_notification,
64-
provider_notification,
6569
file_updated_notification,
6670
url
6771
):
6872
res = app.get(url, auth=user.auth)
6973
notification_ids = [item['id'] for item in res.json['data']]
7074
# There should only be 3 notifications: users' global, node's file updates and provider's preprint added.
7175
assert len(notification_ids) == 3
72-
assert f'{user._id}_global' in notification_ids
76+
assert f'{user._id}_global_file_updated' in notification_ids
7377
assert f'{provider._id}_new_pending_submissions' in notification_ids
7478
assert f'{node._id}_file_updated' in notification_ids
7579

@@ -87,7 +91,7 @@ def test_cannot_post_patch_put_or_delete(self, app, url, user):
8791
assert put_res.status_code == 405
8892
assert delete_res.status_code == 405
8993

90-
def test_multiple_values_filter(self, app, url, global_user_notification, file_updated_notification, user):
94+
def test_multiple_values_filter(self, app, url, file_updated_notification, user):
9195
res = app.get(url + '?filter[event_name]=comments,file_updated', auth=user.auth)
9296
assert len(res.json['data']) == 2
9397
for subscription in res.json['data']:

osf/models/notification_subscription.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@
44
from django.contrib.contenttypes.fields import GenericForeignKey
55
from django.contrib.contenttypes.models import ContentType
66
from django.core.exceptions import ValidationError
7-
from osf.models.notification_type import get_default_frequency_choices
7+
from osf.models.notification_type import get_default_frequency_choices, NotificationType
88
from osf.models.notification import Notification
99
from api.base import settings
10+
from api.base.utils import absolute_reverse
1011

1112
from .base import BaseModel
1213

@@ -106,29 +107,24 @@ def emit(
106107

107108
@property
108109
def absolute_api_v2_url(self):
109-
from api.base.utils import absolute_reverse
110-
return absolute_reverse('institutions:institution-detail', kwargs={'institution_id': self._id, 'version': 'v2'})
110+
return absolute_reverse(
111+
'subscriptions:notification-subscription-detail',
112+
kwargs={
113+
'subscription_id': self._id, 'version': 'v2'
114+
}
115+
)
111116

112117
@property
113118
def _id(self):
114119
"""
115120
Legacy subscription id for API compatibility.
116-
Provider: <short_name>_<event>
117-
User/global: <user_id>_global_<event>
118-
Node/etc: <guid>_<event>
119121
"""
120-
# Safety checks
121-
event = self.notification_type.name
122-
ct = self.notification_type.object_content_type
123-
match getattr(ct, 'model', None):
124-
case 'preprintprovider' | 'collectionprovider' | 'registrationprovider':
125-
# Providers: use subscribed_object._id (which is the provider short name, e.g. 'mindrxiv')
122+
match self.notification_type.name:
123+
case NotificationType.Type.PROVIDER_NEW_PENDING_SUBMISSIONS.value:
126124
return f'{self.subscribed_object._id}_new_pending_submissions'
127-
case 'node' | 'collection' | 'preprint':
128-
# Node-like objects: use object_id (guid)
129-
return f'{self.subscribed_object._id}_{event}'
130-
case 'osfuser' | 'user' | None:
131-
# Global: <user_id>_global
132-
return f'{self.user._id}_global'
125+
case NotificationType.Type.USER_FILE_UPDATED.value:
126+
return f'{self.user._id}_file_updated'
127+
case NotificationType.Type.NODE_FILE_UPDATED.value:
128+
return f'{self.subscribed_object._id}_file_updated'
133129
case _:
134130
raise NotImplementedError()

0 commit comments

Comments
 (0)